(黑科技)怎么在这种情况下改变输出顺序?
来源:互联网 发布:全民奇迹数据库修改 编辑:程序博客网 时间: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);退出程序,又不能调用其它函数,只能弄个指针错误,搞个大新闻,暴力退出.
这个绝对是黑科技,嗯,专门黑人的科技...
- (黑科技)怎么在这种情况下改变输出顺序?
- 在这种情况下
- 料理机器人!有哪些黑科技改变了家庭生活?
- 料理机器人!有哪些黑科技改变了家庭生活?
- 黑科技
- 黑科技
- 黑科技
- 《黑科技》
- 读入输出优化 黑科技 快过fread&&fwite
- 黑科技/隐性科技展望
- gson改变输出字段的顺序
- 比AlphaGo更可怕!10大黑科技或将改变人类未来
- "黑科技"改变生活 业内称人工智能将迎第三次爆发
- debug 黑科技
- CSS 的黑科技
- 汽车座椅中的黑科技
- ACM竞赛黑科技
- 离散化黑科技
- oracle中修改已存在数据的列
- linux下利用C或C++ 语言调用需要root权限
- iOS 在UILabel显示不同的字体和颜色(转)
- JPA和Hibernate的区别
- [leetcode] Reorder List
- (黑科技)怎么在这种情况下改变输出顺序?
- linux 安装jdk
- JavaScript闭包
- 一道序列算法题
- 刑警的射击成绩
- 在iOS上使用ffmpeg播放视频
- intellij idea 14 插件IdentifierHighlighter 报错 Could not save application settings
- 租用游艇问题
- NS2中,编译文件