持续学习 感谢分享
本文没有归到反-反汇编
系列中,因为主要是对抗IDA-F5
,让函数的参数显示的很难看(没从网上看到过类似的思路,可能是我书读的比较少...分享一下心得)
本文介绍x64
架构下的一个技巧,之后可能会研究一下x86
架构下的对抗
不可否认的是,这些技巧都会在动态调试时露出真面目,但是配合反调试技巧,分析难度会加倍
失败尝试
代码如下:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main(int argc, char** argv) {
puts("nop me");
puts("nop me");
return 0;
}
//visual studio 2019 x64 release
打开IDA,再熟悉不过的流程...
因为是x64-fastcall
,第一个参数被保存在了rcx
中,既然rcx
都是Str:"nop me"
,那么是否可以把第二次的lea rcx,Str
直接nop
掉呢?
patch
后汇编如图:
F5
后如图:
可以看到第二个puts(v3)
,只静态分析就不会得知输出什么了
运行崩溃
在call puts("nop me")
时的调试状况
rcx
就是"nop me"
的地址
再运行一下,rcx
直接变成了0xFFFFFFFF
这样,运行到下一个puts(v3)
时,程序就会因为非法的内存读而崩溃
原因
其实问题的根源很简单,rcx
在函数内部被改掉了
跟进puts
函数内部,前几条指令就已经破坏了原先的rcx
最后一行test rcx,rcx
直接把它清零了...
一些思考:
这里应该是因为
rcx
默认用来放函数的第一个参数,调用子函数时会破坏原先的rcx
也属正常但是,在父函数没有改变rcx时,是否就可以达到我们欺骗IDA的目的呢?
改进
源码
示例程序源码:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
const char* s1 = "%s";
char buf[16];
int func() {
//这里用返回类型为int,返回一个无用的0
//主要是void时汇编层面一开始没调对,这样更轻松一点
puts("nop me");
puts("nop me");
puts("nop me");
puts("nop me");
puts("nop me");
puts("nop me");
puts("nop me");
puts("nop me");
puts("nop me");
puts("nop me");
return 0;
}
int main(int argc, char** argv) {
scanf(s1, buf);
puts(buf);
func();
func();
func();
func();
func();
scanf(s1, buf);
puts(buf);
return 0;
}
//visual studio 2019 x64 release
程序中
func()
函数内写了这么多是不想让函数编译后被"内联"
func()
调用这么多次也是出于这个原因
patch
main
函数的逻辑如下:
我们的目的是隐藏第二个scanf("%s",buf)
的参数,最好让F5
的结果变成scanf(v3)
这样
第一步
我们把func()
的最后两个puts("nop me")
直接nop
掉,如图
这并不会破坏func()
的堆栈之类,程序可以正确运行
第二步
main
函数patch
也不复杂,把第二个scanf
的lea rcx,Format
&lea rdx,buf
都nop
掉即可
这样会改变程序流程,像刚才的失败一样,直接非法访问内存崩溃
第三步
我们在刚刚的func()
函数中的一堆nop
里做一些手脚,如下:
结果
F5后的结果如下,第二个scanf(v3)
已经面目全非了,运行也不会报错
的确是两个
scanf
的效果,只是第二次scanf
把上一次的\n
吃掉了,看起来不对劲实际上只要在第一个
scanf
后加上一个getchar()
即可,放对位置也不会影响我们func()
中藏的rcx&rdx
补充
复现可以直接拿源码编译,示例程序没有放在附件中...因为有时上传不成功比较尴尬
大佬轻喷,欢迎交流和指出错误和改进