【猎奇】我从来没有这么想过
今天中午睡午觉的时候日常在床上玩会手机,: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 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,不好改了。 内联汇编后,你这就不能算是正儿八经的C语言了——你这代码在我的单片机编译器上,pass会被直接内联,然后对应ebp的寄存器是零,因为栈帧直接被省略掉了。
事实上,main里面的int a不一定就存储在内存里,它很可能就不在内存里,而只用esi、edi来存储。
但其实你写的是“int a = 123;”而非“int a; scanf("%d", &a);”C编译器会把你的a优化成立即数那样的东西,甚至影子都见不到。
你研究的东西有点接近于信息安全,这玩意儿坑很深,而且基本上是劝退性质的。 0xAA55 发表于 2018-12-5 00:00
内联汇编后,你这就不能算是正儿八经的C语言了——你这代码在我的单片机编译器上,pass会被直接内联,然后 ...
好的,谢谢站长指导,小弟我也是感觉挺有意思的就像弄一弄,因为原先从来没有想过子函数中来操作main函数中的变量。我底下也看了看,ida的反汇编是在看不懂他是怎么算的pass和main中ebp的差值,小弟get了 好的,这篇帖子就此到一段落,刚刚小弟又减少变量重新找了一下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,那是试验出来的,不是小弟算出来的。 遍历 或者 memmov 伪代码
void pass()
{
int a[];
for (int i =0;i < 256;i++)
{
if (a=123) {a=234;break}
}
} Ayala 发表于 2018-12-5 09:15
遍历 或者 memmov 伪代码
void pass()
{
版主,这个通不过编译吧?
通过设置a为野指针,搜索123换成234。但是野指针没有指定开始位置呢。 这样可以编译吧 上面那个是伪代码
void pass()
{
int a;
int i ;
for ( i =0;i < 256;i++)
{
if ( a==123)
{
a=234;
}
}
} tomwillow 发表于 2018-12-5 17:53
我做了你的题。
我用OD查看pass()函数,pass里面先是
啊,小弟我一开始是想着找ebp的,帖子里我想了两个办法,第一个是一路pop,最后ebp给pop出来用,结果发现效果不理想,第二种方法是用esp来算偏移量,结果发现我寻址寻不对,老哥你第一个push ebp是我所没有想到的,所以我才想了其他的笨方法,学习了! 本帖最后由 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
请按任意键继续. . .
请问一下有什么问题么 256没啥特殊含义 遍历指针需要从栈上开始 代码本质是遍历栈
页:
[1]