(黑科技)怎么在这种情况下改变输出顺序?

#include <stdio.h> 
void a(); void b(); void c();     //函数原型
int main() { a(); printf("\n"); return 0; }    //main
void a() { b(); printf("one "); } 
void b() { c(); printf("two "); } 
void c() {
	int x;
	// 在此处加代码

}

要求在不改变其他代码的情况下,只在//在此处加代码    加上一段代码(多长都行,但是不能用printf之类的)让整段代码输出one two 而不是two one。

小伙伴们有没有什么好办法呢?




想不出,没关系,我们慢慢来,这里给出了两种可行的做法


首先要关闭内联函数编译选项,一旦a,b,c被内联展开后我们除了修改源码别无它法了.

这里需要一些黑科技,我们知道在一个函数调用结束后会执行ret指令,改指令相当于取栈顶的地址并跳转

等价于

pop ecx
jmp ecx

那么我们要更改a(),b()的执行顺序那么我们只需要将a,b栈帧中的返回地址对调一下就好.

但是stack frame的安排会随着编译参数的不同而改变,若是想要swap call stack里的两个返回地址的话代码根本没有移植性.另外.text段是不可写的,直接修改代码什么的是不可能的了,Windows下可以通过VirtualProtectEx改,但是要求中明确表明不能用其他函数什么的...汗...
我们还是老老实实swap call stack里的两个返回地址吧, 不用汇编的方法也是有的,不过这个高度依赖编译参数
拿VS2013来说吧,编译选项:Release|Win32 优化:已禁用 (/Od)

#include <stdio.h> 
void a(); void b(); void c();     //函数原型
int main() { a(); printf("\n"); return 0; }    //main
void a() { b(); printf("one "); }
void b() { c(); printf("two "); }
void c() {
	int x;
	// 在此处加代码
	int *rpa, *rpb;
	rpa = &x+3;       //指向c()的返回地址 
	rpb = rpa + 2;    //指向b()的返回地址
	x = *rpa;
	*rpa = *rpb,*rpb = x;    //Swap!!!
}  //return WTF?
好的,让我们来看看发生了什么
观察反汇编
这是函数b的反汇编:

void b()
{
 push        ebp  
 mov         ebp,esp  
	c();
 call        c (0AA1040h)  
	printf("two ");
 push        0AA2108h  
	printf("two ");
 call        dword ptr ds:[0AA2090h]  
 add         esp,4  
}
 pop         ebp  
 ret  
在b()调用c()之前先保存了ebp的值,然后再call c (0AA1040h) ,也就是说栈里面返回到a()和b()中间插了个ebp.现在来看函数c的反汇编:

void c()
{
 push        ebp  
 mov         ebp,esp  
 sub         esp,10h  
 mov         eax,dword ptr ds:[00DC3000h]  
 xor         eax,ebp  
 mov         dword ptr [ebp-4],eax  
	int x;

	int *rpa,*rpb;
	rpa = &x+3;
 lea         eax,[ebp+4]  
 mov         dword ptr [rpa],eax  
	rpb = rpa + 2;
 mov         ecx,dword ptr [rpa]  
 add         ecx,8  
 mov         dword ptr [rpb],ecx  
	x = *rpa;
 mov         edx,dword ptr [rpa]  
 mov         eax,dword ptr [edx]  
 mov         dword ptr [x],eax  
	*rpa = *rpb;
 mov         ecx,dword ptr [rpa]  
 mov         edx,dword ptr [rpb]  
 mov         eax,dword ptr [edx]  
 mov         dword ptr [ecx],eax  
	*rpb = x;
 mov         ecx,dword ptr [rpb]  
 mov         edx,dword ptr [x]  
 mov         dword ptr [ecx],edx  
}
 mov         ecx,dword ptr [ebp-4]  
 xor         ecx,ebp  
 call        __security_check_cookie (0DC10AAh)  
 mov         esp,ebp  
 pop         ebp  
 ret  
观察上面的

	rpa = &x+3;
lea         eax,[ebp+4]  
mov         dword ptr [rpa],eax  
栈帧示意图:

我们会发现x的地址实际上就是ebp-0x08,其中ebp-0x04是开启了编译选项安全检查:(/GS)之后用于检查ebp是否发生改变防止溢出的,和最后的__security_check_cookie有关.这里可以不管它.rpa = &x+3;就是指向ebp+4为返回b()的地址,根据前面的分析返回b()的地址和返回a()的地址中间相隔了4byte,那么rpb = rpa + 2;就是指向返回a()的地址.最后交换一下返回地址那么c()执行完了之后就会跳转到a()执行printf("one ");然后跳转到b()执行printf("two ");最后跳回到main退出.

特别指出一点:因为a()和b()的栈帧简单结构相同所以才可以直接swap返回地址,要不还要整个栈帧交换...多写个循环...
如果改了编译参数或平台的话地址要自己重新掰手指算咯,所以嘛根本没有移植性,这就是典型的黑科技.

另外调试的时候发现了编译器好奇葩的一点.明明c()函数是有副作用的,在开启了O1或O2下他喵的居然就直接给cut掉了...什么都没了...关闭了内联函数就只剩下个ret(这算是bug嘛?不过嘛,黑科技都用上了就不要对编译器强求太多啦)...情何以堪啊...还是写内联汇编安全...


还有另外一种呢,那就是把函数a,b给拷贝出来,放到c的栈帧里面去,栈的内存是可执行的,不过需要关闭链接时的数据执行保护(DEP)选项(/NXCOMPAT:NO)

为了防止出现递归现象,我们需要把a,b函数里面对其他函数的调用给去掉

在a,b中各有两次函数的调用,一次是调用b和c,第二次是调用printf.

在调用b,c时的调用是短跳转,通过相对位移来进行的跳转,对应的机械码为

0xE8 0x00 0x00 0x00 0x00

后面4字节是位移大小,那么我们只需要将这5字节填充为空指令nop(0x90)即可,然后执行

代码如下:

编译选项:Release|Win32 内联函数拓展:已禁用 (/Ob0)或只适用于 __inline (/Ob1)

数据执行保护(DEP):否 (/NXCOMPAT:NO)

#include <stdio.h>

void a(); void b(); void c();     //函数原型
int main() { a(); printf("\n"); return 0; }    //main
void a() { b(); printf("one "); }
void b() { c(); printf("two "); }
void c() {
	int x;
	// 在此处加代码
	typedef void(*func)(void);
	typedef unsigned char byte;
	byte buffer_a[1 << 7];
	byte buffer_b[1 << 7];
	int index = 0;
	byte *pa_code = (byte*)a;
	byte *pb_code = (byte*)b;
	func fa = (func)&buffer_a, fb = (func)&buffer_b;
	bool find = false;

	while (*pa_code != 0xc3)
	{
		if (*pa_code == 0xe8){
			if (!find){
				for (int i = 0; i < 5; i++)
				{
					buffer_a[index++] = 0x90;
					pa_code++;
				}  //用nop替换call b
				find = true;
			}
			else
			{
				buffer_a[index] = 0xe8;
				int addr = *(int*)(pa_code + 1);
				int *newAddr = (int*)(&buffer_a[index + 1]);
				*newAddr = addr - (int)(&buffer_a[index] - pa_code);
				index += 5, pa_code += 5;
			}  //重新计算call跳转位移
		}
		buffer_a[index++] = *(pa_code++);
	}
	buffer_a[index] = 0xc3;
	index = 0;
	find = false;
	while (*pb_code != 0xc3)
	{
		if (*pb_code == 0xe8){
			if (!find){
				for (int i = 0; i < 5; i++)
				{
					buffer_b[index++] = 0x90;
					pb_code++;
				}  //用nop替换call b
				find = true;
			}
			else
			{
				buffer_b[index] = 0xe8;
				int addr = *(int*)(pb_code + 1);
				int *newAddr = (int*)(&buffer_b[index + 1]);
				*newAddr = addr - (int)(&buffer_b[index] - pb_code);
				index += 5, pb_code += 5;
			}  //重新计算call跳转位移
		}
		buffer_b[index++] = *(pb_code++);
	}
	buffer_b[index] = 0xc3;
	fa();
	fb();
	volatile int i = *(int *)0;  //exit(0);  暴力退出,搞个大新闻
}


因为没有#include <stdlib.h>所以没办法调用exit(0);退出程序,又不能调用其它函数,只能弄个指针错误,搞个大新闻,暴力退出.


这个绝对是黑科技,嗯,专门黑人的科技...



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值