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

来源:互联网 发布:全民奇迹数据库修改 编辑:程序博客网 时间:2024/06/12 01:48
#include <stdio.h> void a(); void b(); void c();     //函数原型int main() { a(); printf("\n"); return 0; }    //mainvoid a() { b(); printf("one "); } void b() { c(); printf("two "); } void c() {int x;// 在此处加代码}

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

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




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


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

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

等价于

pop ecxjmp 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; }    //mainvoid 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; }    //mainvoid 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 bfind = 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 bfind = 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