watermelon 发表于 2018-12-4 20:18:45

【猎奇】我从来没有这么想过

今天中午睡午觉的时候日常在床上玩会手机,:D.我开始看QQ浏览器的新闻,忽然我看到QQ浏览器有给我推送一篇关于C/C++的面试题,我当时点进去看完后感觉没有多大意义,就退出来了。
但是中午醒来准备上课,上课的过程中我才发现原来那个东西比上课有意思,所以我就又开始回想那个推送的面试题。

注意:以下操作是在win10,VS2013(win32 Debug模式)和IDA中完成的。

题目大概是这样的:如何填写pass函数中的内容,使main函数中的第二个printf打印234,代码如下:
#include <stdio.h>

void pass()
{
       
       
}


int main(void)
{
        int a = 123;
        printf("%d\n",a);
        pass();
        printf("%d\n",a);
        return 0;
}


我一开始想不出来这个怎么通过C语言来用子函数控制主函数中的作用域的变量。
这个就很恼火了。那么就来汇编吧,我是空想想不出来,于是在课本上半沿写了写函数开头都有的反汇编,发现自己只知道大概思路,但是具体的每一步居然写不出来。
只有回到宿舍来用电脑看看了。
这个程序中主函数中的变量a所对应的汇编中的地址为ebp-8,大家可以用vs2013或者IDA自己反汇编看看。小弟在这里贴一下题目中main函数在IDA中的反汇编:
_main         proc near               ; CODE XREF: _main_0↑j
.text:004113C0
.text:004113C0 var_CC          = byte ptr -0CCh
.text:004113C0 a               = dword ptr -8
.text:004113C0
.text:004113C0               push    ebp
.text:004113C1               mov   ebp, esp
.text:004113C3               sub   esp, 0CCh
.text:004113C9               push    ebx
.text:004113CA               push    esi
.text:004113CB               push    edi
.text:004113CC               lea   edi,
.text:004113D2               mov   ecx, 33h
.text:004113D7               mov   eax, 0CCCCCCCCh
.text:004113DC               rep stosd
.text:004113DE               mov   , 7Bh
.text:004113E5               mov   esi, esp
.text:004113E7               mov   eax,
.text:004113EA               push    eax
.text:004113EB               push    offset Format   ; "%d\n"
.text:004113F0               call    ds:__imp__printf
.text:004113F6               add   esp, 8
.text:004113F9               cmp   esi, esp
.text:004113FB               call    j___RTC_CheckEsp
.text:00411400               call    j__pass
.text:00411405               mov   esi, esp
.text:00411407               mov   eax,
.text:0041140A               push    eax
.text:0041140B               push    offset Format   ; "%d\n"
.text:00411410               call    ds:__imp__printf
.text:00411416               add   esp, 8
.text:00411419               cmp   esi, esp
.text:0041141B               call    j___RTC_CheckEsp
.text:00411420               xor   eax, eax
.text:00411422               pop   edi
.text:00411423               pop   esi
.text:00411424               pop   ebx
.text:00411425               add   esp, 0CCh
.text:0041142B               cmp   ebp, esp
.text:0041142D               call    j___RTC_CheckEsp
.text:00411432               mov   esp, ebp
.text:00411434               pop   ebp
.text:00411435               retn
.text:00411435 _main         endp

可以很清晰的看出来文中加粗和加下划线中main函数中变量a所对应的地址在反汇编中是ebp-8

好的,那我就想我在pass函数中通过ebp的偏移,找到main函数中的ebp的位置,然后修改地址ebp-8的所对应的值来达到修改main函数中a的值可能会成功(我也不知道,只有试了才知道)。

一开始我想通过pop edi, pop esi, pop ebx, pop ebp来获取ebp的值,但是发现那样根本行不通,通过栈的偏移来找ebp的值也不行(程序会有逻辑错误)。我想我得换个法子了。

于是我通过打印pass中的ebp和main中的ebp来寻找他们的关系,我就把上面的程序稍微修改了一下:
#include <stdio.h>

void pass()
{
        int i = 1;                //设置变量,用来存储pass中的ebp的值
        _asm
        {
                lea eax,
                mov ,eax
        }

        printf("pass中的ebp:%d\n", i);
}


int main(void)
{
        int a = 123;
        printf("%d\n", a);
        int b = 0;                //设置变量,用来存储main中ebp的值
       
        _asm
        {
                lea eax,
                mov ,eax
        }

        printf("main中的ebp:%d\n", b);
        pass();
        printf("%d\n", a);
        return 0;
}


运行结果如下:
第一次运行结果:
123
main中的ebp:5241420
pass中的ebp:5241184
123
请按任意键继续. . .

第二次运行结果:
123
main中的ebp:13892528
pass中的ebp:13892292
123
请按任意键继续. . .

第三次运行结果:
123
main中的ebp:15726676
pass中的ebp:15726440
123
请按任意键继续. . .
我们可以发现,通过多次的运行,在这个程序中,main中的ebp和pass中的ebp总是相差一个常数,那就是236。好了,我想我找到答案了,我们在pass中给ebp加上236就是main中的ebp,然后给ebp-8就是main中变量a的地址,通过修改ebp-8所对应的值来达到修改main中变量a的值。

最终程序如下:
#include <stdio.h>

void pass()
{
        int i = 1;               
        _asm
        {
                mov dword ptr ,234                //通过ebp+236是main中ebp的位置,然后ebp+236-8是main中变量a的地址。
        }

}


int main(void)
{
        int a = 123;
        printf("%d\n", a);
        int b;
        pass();
        printf("%d\n", a);
        return 0;
}

运行结果为:
123
234
请按任意键继续. . .

可以看到最后的这个程序还是有两点不足:
1.程序最后的时候main函数中必须要有一个int b;(初不初始化都行,但是必须要有,否则要崩溃,但是这么做就不符合题目的本意了)
2.tangptr说不喜欢C/C++来内嵌汇编来写,但是小弟不知道能不能不内嵌汇编,用C语言的方式完成这道题目?

小弟知识浅薄,望各位大佬多多指正,不必留情。

tomwillow 发表于 2018-12-5 17:53:09

本帖最后由 tomwillow 于 2018-12-5 18:00 编辑

我做了你的题。

我用OD查看pass()函数,pass里面先是
push ebp
mov ebp,esp
之后
sub esp,0xC0
之后
pop ebx
pop esi
pop edi

所以,esp此时减少了0x04+0xC0+3*0x04=0xD0,而第一次push ebp时esp减少了第一次0x04,此时==ebp

所以,在pass里面先加入push ebp,此时esp减少0x04,此时最初ebp==
之后mov ebp,dword ptr ;此时ebp==最初ebp
mov dword ptr,234 ; ebp-0x08即为主函数中a的地址
pop ebp
完工。

#include <stdio.h>

void pass()
{
        _asm
        {
                push ebp;
                mov ebp, dword ptr;
                mov dword ptr, 234;
                pop ebp;
        }

}

int main(void)
{
        int a = 123;
        printf("%d\n", a);
        pass();
        printf("%d\n", a);
        getchar();
        return 0;
}

补充:上面是Debug设置编译的,我用Release又试了一下,Release下不行,我看了反汇编,Release下pass函数直接展开了,而且第二个printf前直接push 0x7B,不好改了。

0xAA55 发表于 2018-12-5 00:00:15

内联汇编后,你这就不能算是正儿八经的C语言了——你这代码在我的单片机编译器上,pass会被直接内联,然后对应ebp的寄存器是零,因为栈帧直接被省略掉了。
事实上,main里面的int a不一定就存储在内存里,它很可能就不在内存里,而只用esi、edi来存储。
但其实你写的是“int a = 123;”而非“int a; scanf("%d", &a);”C编译器会把你的a优化成立即数那样的东西,甚至影子都见不到。

你研究的东西有点接近于信息安全,这玩意儿坑很深,而且基本上是劝退性质的。

watermelon 发表于 2018-12-5 00:36:53

0xAA55 发表于 2018-12-5 00:00
内联汇编后,你这就不能算是正儿八经的C语言了——你这代码在我的单片机编译器上,pass会被直接内联,然后 ...

好的,谢谢站长指导,小弟我也是感觉挺有意思的就像弄一弄,因为原先从来没有想过子函数中来操作main函数中的变量。我底下也看了看,ida的反汇编是在看不懂他是怎么算的pass和main中ebp的差值,小弟get了

watermelon 发表于 2018-12-5 00:51:39

好的,这篇帖子就此到一段落,刚刚小弟又减少变量重新找了一下pass中ebp和main中ebp的关系
最后程序如下:
#include <stdio.h>


void pass()
{
        _asm
        {
                mov ,234                //ebp+224-8
        }
}

int main(void)
{
        int a = 123;
        printf("%d\n", a);
        pass();
        printf("%d\n", a);
        return 0;
}


运行结果:
123
234
请按任意键继续. . .

至于为什么ebp+216,那是试验出来的,不是小弟算出来的。

Ayala 发表于 2018-12-5 09:15:00

遍历 或者 memmov 伪代码
void pass()
{
   int a[];
   for (int i =0;i < 256;i++)
   {
   if (a=123) {a=234;break}
      }
   
}

tomwillow 发表于 2018-12-5 18:03:24

Ayala 发表于 2018-12-5 09:15
遍历 或者 memmov 伪代码
void pass()
{


版主,这个通不过编译吧?
通过设置a为野指针,搜索123换成234。但是野指针没有指定开始位置呢。

Ayala 发表于 2018-12-5 20:20:34

这样可以编译吧 上面那个是伪代码
void pass()
{
        int a;
        int i ;
        for ( i =0;i < 256;i++)
        {
                if ( a==123)
                {
                        a=234;
                }
        }
}

watermelon 发表于 2018-12-5 20:29:09

tomwillow 发表于 2018-12-5 17:53
我做了你的题。

我用OD查看pass()函数,pass里面先是


啊,小弟我一开始是想着找ebp的,帖子里我想了两个办法,第一个是一路pop,最后ebp给pop出来用,结果发现效果不理想,第二种方法是用esp来算偏移量,结果发现我寻址寻不对,老哥你第一个push ebp是我所没有想到的,所以我才想了其他的笨方法,学习了!

watermelon 发表于 2018-12-5 20:36:27

本帖最后由 watermelon 于 2018-12-5 20:40 编辑

Ayala 发表于 2018-12-5 20:20
这样可以编译吧 上面那个是伪代码
void pass()
{


顶版主,我试验了的确可以,数组溢出还能这么用.....,请教一下版主,那个for循环里的i<256,256有什么特别的含义么?
还有小弟我用野指针试了试结果不理想:

#include <stdio.h>
#include <stdlib.h>

void pass()
{

        int *a = malloc(sizeof(int));
        int i;
        for (i = 0; i < 256; i++)
        {
                if (a == 123)
                {
                        a = 234;
                }
        }
}

int main(void)
{
        int a = 123;
        printf("%d\n", a);
        pass();
        printf("%d\n", a);
        return 0;
}
运行结果:
123
123
请按任意键继续. . .
请问一下有什么问题么

Ayala 发表于 2018-12-6 07:27:31

256没啥特殊含义 遍历指针需要从栈上开始 代码本质是遍历栈
页: [1]
查看完整版本: 【猎奇】我从来没有这么想过