技术宅的结界

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

QQ登录

只需一步,快速开始

搜索
热搜: 下载 VB C 实现 编写
查看: 155|回复: 10
收起左侧

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

[复制链接]

12

主题

111

帖子

805

积分

用户组: 大·技术宅

UID
3808
精华
1
威望
16 点
宅币
611 个
贡献
46 次
宅之契约
0 份
在线时间
110 小时
注册时间
2018-5-6
发表于 2018-12-4 20:18:45 | 显示全部楼层 |阅读模式

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

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

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

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

题目大概是这样的:如何填写pass函数中的内容,使main函数中的第二个printf打印234,代码如下:
[C] 纯文本查看 复制代码
#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中的反汇编:
[Asm] 纯文本查看 复制代码
_main           proc near               ; CODE XREF: _main_0↑j
.text:004113C0
.text:004113C0 var_CC          = byte ptr -0CCh
[b][u].text:004113C0 a               = dword ptr -8[/u][/b]
.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, [ebp+var_CC]
.text:004113D2                 mov     ecx, 33h
.text:004113D7                 mov     eax, 0CCCCCCCCh
.text:004113DC                 rep stosd
[u][b].text:004113DE                 mov     [ebp+a], 7Bh[/b][/u]
.text:004113E5                 mov     esi, esp
.text:004113E7                 mov     eax, [ebp+a]
.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, [ebp+a]
.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来寻找他们的关系,我就把上面的程序稍微修改了一下:
[C] 纯文本查看 复制代码
#include <stdio.h>

void pass()
{
	int i = 1;		//设置变量,用来存储pass中的ebp的值
	_asm
	{
		lea eax,[ebp]
		mov [i],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,[ebp]
		mov [b],eax
	}

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


运行结果如下:
[AppleScript] 纯文本查看 复制代码
第一次运行结果:
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的值。

最终程序如下:
[C] 纯文本查看 复制代码
#include <stdio.h>

void pass()
{
	int i = 1;		
	_asm
	{
		mov dword ptr [ebp+228],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;
}

运行结果为:
[AppleScript] 纯文本查看 复制代码
123
234
请按任意键继续. . .


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

小弟知识浅薄,望各位大佬多多指正,不必留情。
菜鸟一枚,直接指正,不必留情

1

主题

9

帖子

234

积分

用户组: 中·技术宅

UID
4533
精华
0
威望
22 点
宅币
131 个
贡献
50 次
宅之契约
0 份
在线时间
3 小时
注册时间
2018-12-5
发表于 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
完工。

[C++] 纯文本查看 复制代码
#include <stdio.h>

void pass()
{
	_asm
	{
		push ebp;
		mov ebp, dword ptr[esp + 0xD0];
		mov dword ptr[ebp - 0x8], 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,不好改了。

1010

主题

2246

帖子

5万

积分

用户组: 管理员

一只技术宅

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

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

12

主题

111

帖子

805

积分

用户组: 大·技术宅

UID
3808
精华
1
威望
16 点
宅币
611 个
贡献
46 次
宅之契约
0 份
在线时间
110 小时
注册时间
2018-5-6
 楼主| 发表于 2018-12-5 00:36:53 | 显示全部楼层
0xAA55 发表于 2018-12-5 00:00
内联汇编后,你这就不能算是正儿八经的C语言了——你这代码在我的单片机编译器上,pass会被直接内联,然后 ...

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

12

主题

111

帖子

805

积分

用户组: 大·技术宅

UID
3808
精华
1
威望
16 点
宅币
611 个
贡献
46 次
宅之契约
0 份
在线时间
110 小时
注册时间
2018-5-6
 楼主| 发表于 2018-12-5 00:51:39 | 显示全部楼层
好的,这篇帖子就此到一段落,刚刚小弟又减少变量重新找了一下pass中ebp和main中ebp的关系
最后程序如下:
[C] 纯文本查看 复制代码
#include <stdio.h>


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

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


运行结果:
[AppleScript] 纯文本查看 复制代码
123
234
请按任意键继续. . .


至于为什么ebp+216,那是试验出来的,不是小弟算出来的。
菜鸟一枚,直接指正,不必留情

25

主题

91

帖子

1207

积分

用户组: 版主

UID
1821
精华
6
威望
57 点
宅币
936 个
贡献
36 次
宅之契约
0 份
在线时间
210 小时
注册时间
2016-7-12
发表于 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}
      }
     
}

1

主题

9

帖子

234

积分

用户组: 中·技术宅

UID
4533
精华
0
威望
22 点
宅币
131 个
贡献
50 次
宅之契约
0 份
在线时间
3 小时
注册时间
2018-12-5
发表于 2018-12-5 18:03:24 | 显示全部楼层
Ayala 发表于 2018-12-5 09:15
遍历 或者 memmov 伪代码
void pass()
{

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

25

主题

91

帖子

1207

积分

用户组: 版主

UID
1821
精华
6
威望
57 点
宅币
936 个
贡献
36 次
宅之契约
0 份
在线时间
210 小时
注册时间
2016-7-12
发表于 2018-12-5 20:20:34 | 显示全部楼层
这样可以编译吧 上面那个是伪代码
[C] 纯文本查看 复制代码
void pass()
{
	int a[1];
	int i ;
	for ( i =0;i < 256;i++)
	{
		if ( a[i]==123) 
		{
			a[i]=234;
		}
	}
}

12

主题

111

帖子

805

积分

用户组: 大·技术宅

UID
3808
精华
1
威望
16 点
宅币
611 个
贡献
46 次
宅之契约
0 份
在线时间
110 小时
注册时间
2018-5-6
 楼主| 发表于 2018-12-5 20:29:09 | 显示全部楼层
tomwillow 发表于 2018-12-5 17:53
我做了你的题。

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

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

12

主题

111

帖子

805

积分

用户组: 大·技术宅

UID
3808
精华
1
威望
16 点
宅币
611 个
贡献
46 次
宅之契约
0 份
在线时间
110 小时
注册时间
2018-5-6
 楼主| 发表于 2018-12-5 20:36:27 | 显示全部楼层
本帖最后由 watermelon 于 2018-12-5 20:40 编辑
Ayala 发表于 2018-12-5 20:20
这样可以编译吧 上面那个是伪代码
[C] 纯文本查看 复制代码
void pass()
{
[/quote]

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

void pass()
{

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

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

运行结果:
123
123
请按任意键继续. . .
请问一下有什么问题么
菜鸟一枚,直接指正,不必留情

25

主题

91

帖子

1207

积分

用户组: 版主

UID
1821
精华
6
威望
57 点
宅币
936 个
贡献
36 次
宅之契约
0 份
在线时间
210 小时
注册时间
2016-7-12
发表于 2018-12-6 07:27:31 | 显示全部楼层
256没啥特殊含义 遍历指针需要从栈上开始 代码本质是遍历栈

本版积分规则

QQ|申请友链|Archiver|手机版|小黑屋|技术宅的结界 ( 滇ICP备16008837号|网站地图

GMT+8, 2018-12-15 10:52 , Processed in 0.112590 second(s), 33 queries , Gzip On.

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

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