找回密码
 立即注册→加入我们

QQ登录

只需一步,快速开始

搜索
热搜: 下载 VB C 实现 编写
查看: 2897|回复: 10

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

[复制链接]
发表于 2018-12-4 20:18:45 | 显示全部楼层 |阅读模式

欢迎访问技术宅的结界,请注册或者登录吧。

您需要 登录 才可以下载或查看,没有账号?立即注册→加入我们

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

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

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

  2. void pass()
  3. {
  4.        
  5.        
  6. }


  7. int main(void)
  8. {
  9.         int a = 123;
  10.         printf("%d\n",a);
  11.         pass();
  12.         printf("%d\n",a);
  13.         return 0;
  14. }
复制代码


我一开始想不出来这个怎么通过C语言来用子函数控制主函数中的作用域的变量。
这个就很恼火了。那么就来汇编吧,我是空想想不出来,于是在课本上半沿写了写函数开头都有的反汇编,发现自己只知道大概思路,但是具体的每一步居然写不出来。
只有回到宿舍来用电脑看看了。
这个程序中主函数中的变量a所对应的汇编中的地址为ebp-8,大家可以用vs2013或者IDA自己反汇编看看。小弟在这里贴一下题目中main函数在IDA中的反汇编:
  1. _main           proc near               ; CODE XREF: _main_0↑j
  2. .text:004113C0
  3. .text:004113C0 var_CC          = byte ptr -0CCh
  4. [b][u].text:004113C0 a               = dword ptr -8[/u][/b]
  5. .text:004113C0
  6. .text:004113C0                 push    ebp
  7. .text:004113C1                 mov     ebp, esp
  8. .text:004113C3                 sub     esp, 0CCh
  9. .text:004113C9                 push    ebx
  10. .text:004113CA                 push    esi
  11. .text:004113CB                 push    edi
  12. .text:004113CC                 lea     edi, [ebp+var_CC]
  13. .text:004113D2                 mov     ecx, 33h
  14. .text:004113D7                 mov     eax, 0CCCCCCCCh
  15. .text:004113DC                 rep stosd
  16. [u][b].text:004113DE                 mov     [ebp+a], 7Bh[/b][/u]
  17. .text:004113E5                 mov     esi, esp
  18. .text:004113E7                 mov     eax, [ebp+a]
  19. .text:004113EA                 push    eax
  20. .text:004113EB                 push    offset Format   ; "%d\n"
  21. .text:004113F0                 call    ds:__imp__printf
  22. .text:004113F6                 add     esp, 8
  23. .text:004113F9                 cmp     esi, esp
  24. .text:004113FB                 call    j___RTC_CheckEsp
  25. .text:00411400                 call    j__pass
  26. .text:00411405                 mov     esi, esp
  27. .text:00411407                 mov     eax, [ebp+a]
  28. .text:0041140A                 push    eax
  29. .text:0041140B                 push    offset Format   ; "%d\n"
  30. .text:00411410                 call    ds:__imp__printf
  31. .text:00411416                 add     esp, 8
  32. .text:00411419                 cmp     esi, esp
  33. .text:0041141B                 call    j___RTC_CheckEsp
  34. .text:00411420                 xor     eax, eax
  35. .text:00411422                 pop     edi
  36. .text:00411423                 pop     esi
  37. .text:00411424                 pop     ebx
  38. .text:00411425                 add     esp, 0CCh
  39. .text:0041142B                 cmp     ebp, esp
  40. .text:0041142D                 call    j___RTC_CheckEsp
  41. .text:00411432                 mov     esp, ebp
  42. .text:00411434                 pop     ebp
  43. .text:00411435                 retn
  44. .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来寻找他们的关系,我就把上面的程序稍微修改了一下:
  1. #include <stdio.h>

  2. void pass()
  3. {
  4.         int i = 1;                //设置变量,用来存储pass中的ebp的值
  5.         _asm
  6.         {
  7.                 lea eax,[ebp]
  8.                 mov [i],eax
  9.         }

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


  12. int main(void)
  13. {
  14.         int a = 123;
  15.         printf("%d\n", a);
  16.         int b = 0;                //设置变量,用来存储main中ebp的值
  17.        
  18.         _asm
  19.         {
  20.                 lea eax,[ebp]
  21.                 mov [b],eax
  22.         }

  23.         printf("main中的ebp:%d\n", b);
  24.         pass();
  25.         printf("%d\n", a);
  26.         return 0;
  27. }
复制代码


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

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

  13. 第三次运行结果:
  14. 123
  15. main中的ebp:15726676
  16. pass中的ebp:15726440
  17. 123
  18. 请按任意键继续. . .
复制代码

我们可以发现,通过多次的运行,在这个程序中,main中的ebp和pass中的ebp总是相差一个常数,那就是236。好了,我想我找到答案了,我们在pass中给ebp加上236就是main中的ebp,然后给ebp-8就是main中变量a的地址,通过修改ebp-8所对应的值来达到修改main中变量a的值。

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

  2. void pass()
  3. {
  4.         int i = 1;               
  5.         _asm
  6.         {
  7.                 mov dword ptr [ebp+228],234                //通过ebp+236是main中ebp的位置,然后ebp+236-8是main中变量a的地址。
  8.         }

  9. }


  10. int main(void)
  11. {
  12.         int a = 123;
  13.         printf("%d\n", a);
  14.         int b;
  15.         pass();
  16.         printf("%d\n", a);
  17.         return 0;
  18. }
复制代码

运行结果为:
  1. 123
  2. 234
  3. 请按任意键继续. . .
复制代码


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

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

使用道具 举报

发表于 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,此时[esp-0xCC]==ebp

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

  1. #include <stdio.h>

  2. void pass()
  3. {
  4.         _asm
  5.         {
  6.                 push ebp;
  7.                 mov ebp, dword ptr[esp + 0xD0];
  8.                 mov dword ptr[ebp - 0x8], 234;
  9.                 pop ebp;
  10.         }

  11. }

  12. int main(void)
  13. {
  14.         int a = 123;
  15.         printf("%d\n", a);
  16.         pass();
  17.         printf("%d\n", a);
  18.         getchar();
  19.         return 0;
  20. }
复制代码


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

使用道具 举报

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

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

使用道具 举报

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

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

使用道具 举报

 楼主| 发表于 2018-12-5 00:51:39 | 显示全部楼层
好的,这篇帖子就此到一段落,刚刚小弟又减少变量重新找了一下pass中ebp和main中ebp的关系
最后程序如下:
  1. #include <stdio.h>


  2. void pass()
  3. {
  4.         _asm
  5.         {
  6.                 mov [ebp+216],234                //ebp+224-8
  7.         }
  8. }

  9. int main(void)
  10. {
  11.         int a = 123;
  12.         printf("%d\n", a);
  13.         pass();
  14.         printf("%d\n", a);
  15.         return 0;
  16. }
复制代码


运行结果:
  1. 123
  2. 234
  3. 请按任意键继续. . .
复制代码


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

使用道具 举报

发表于 2018-12-5 09:15:00 | 显示全部楼层
遍历 或者 memmov 伪代码
void pass()
{
     int a[];
     for (int i =0;i < 256;i++)
     {
     if (a[i]=123) {a[i]=234;break}
      }
     
}
回复 赞! 靠!

使用道具 举报

发表于 2018-12-5 18:03:24 | 显示全部楼层
Ayala 发表于 2018-12-5 09:15
遍历 或者 memmov 伪代码
void pass()
{

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

使用道具 举报

发表于 2018-12-5 20:20:34 | 显示全部楼层
这样可以编译吧 上面那个是伪代码
  1. void pass()
  2. {
  3.         int a[1];
  4.         int i ;
  5.         for ( i =0;i < 256;i++)
  6.         {
  7.                 if ( a[i]==123)
  8.                 {
  9.                         a[i]=234;
  10.                 }
  11.         }
  12. }
复制代码
回复 赞! 靠!

使用道具 举报

 楼主| 发表于 2018-12-5 20:29:09 | 显示全部楼层
tomwillow 发表于 2018-12-5 17:53
我做了你的题。

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

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

使用道具 举报

 楼主| 发表于 2018-12-5 20:36:27 | 显示全部楼层
本帖最后由 watermelon 于 2018-12-5 20:40 编辑

[quote]Ayala 发表于 2018-12-5 20:20
这样可以编译吧 上面那个是伪代码
  1. void pass()
  2. {
  3. [/quote]

  4. 顶版主,我试验了的确可以,数组溢出还能这么用.....,请教一下版主,那个for循环里的i<256,256有什么特别的含义么?
  5. 还有小弟我用野指针试了试结果不理想:
  6. [code]
  7. #include <stdio.h>
  8. #include <stdlib.h>

  9. void pass()
  10. {

  11.         int *a = malloc(sizeof(int));
  12.         int i;
  13.         for (i = 0; i < 256; i++)
  14.         {
  15.                 if (a[i] == 123)
  16.                 {
  17.                         a[i] = 234;
  18.                 }
  19.         }
  20. }

  21. int main(void)
  22. {
  23.         int a = 123;
  24.         printf("%d\n", a);
  25.         pass();
  26.         printf("%d\n", a);
  27.         return 0;
  28. }
复制代码

运行结果:
123
123
请按任意键继续. . .
请问一下有什么问题么
回复 赞! 靠!

使用道具 举报

发表于 2018-12-6 07:27:31 | 显示全部楼层
256没啥特殊含义 遍历指针需要从栈上开始 代码本质是遍历栈
回复 赞! 靠!

使用道具 举报

本版积分规则

QQ|Archiver|小黑屋|技术宅的结界 ( 滇ICP备16008837号 )|网站地图

GMT+8, 2024-11-21 20:27 , Processed in 0.035851 second(s), 23 queries , Gzip On.

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表