实战:定位目标函数
实战:定位目标函数
之前一直在纸上谈兵,完全没有在实际的可执行文件中去看这个函数的调用,现在时机成熟,让我们来尝试第一次逆向
我们先编译以下测试代码,得到可执行文件:
c
复制代码
#include <stdio.h>
int add(int a, int b)
{
return a + b;
}
int main()
{
int x = 10;
int y = 20;
int z = add(x, y);
printf("%d\n", z);
return 0;
}
我们这次的目标是找到我们的add函数,然后看他的Shadow Space是如何运作的
本次实战使用以下动态调试工具:
https://sourceforge.net/projects/x64dbg/files/snapshots/
寻找目标函数
将可执行文件用dbg打开,然后发现天塌了,啥也看不懂,最开始是程序启动,都是Windows 和 C 运行库在干活,我们直接运行到用户代码
“运行到用户代码” = Run until user code,意思是:
让程序继续运行,直到 CPU 执行到你这个 EXE 自己的代码,而不是 Windows 系统 DLL、运行库之类的代码
快捷键是Alt+F9
这个时候才找到我们自己的模块

喏,test_main就是我的EXE的名字,确实进到这个模块了,但是为什么这么多个地址,难道我要一个个看吗?
让我们使用伟大的搜索功能

我们选择在当前模块搜索字符串,因为我们之前的c代码里面留了一个
c
复制代码
printf("%d\n", z);
其中的字符串会作为数据存在内存,于是我们在搜索中见到一个熟悉的面孔:

这不就是我在main里要打印的字符串吗?右键跳转到反汇编界面

我们的call add大概就藏在上面某个test_main,这里的命名是模块名.地址,所以没有add这个名字出现,但是我们可以推测一下:我们的add函数是紧跟着printf的,点开最近的main试一下
打个断点,一路F9让程序运行到断点

我们发现这个好像是处理eax和edx之间的加法,让我们看看寄存器窗口

RAX和RDX转十进制:
复制代码
Ah->10
14h->20
这和我们给add传入的参数也吻合
至此,我们找到了我们的add函数
为什么能根据字符串定位
首先字符串不会被汇编成指令,而是作为数据被编译进EXE的数据区域,所以搜索当前模块(test_main)的字符串,其实就是在EXE的数据里面找一串被作为字符串存储的字节

示例图片右边,记录的是数据区域记录的字符串和存储这个字符串的地址
左边记录的是这个地址被引用的位置,我们根据这个引用位置跳转过去,就可以找到对应执行这条指令的反汇编位置

然后我们就发现,这里将这个字符串的地址放进寄存器
asm
复制代码
lea rcx,qword ptr ds:[404000]
接着下面就call了printf函数
而我们的add函数和printf是紧跟着的,前一个调用的函数有个:
asm
复制代码
add eax, edx
ret
并且在调用前有准备两个参数
复制代码
RCX = 10
RDX = 20
所以前一个调用的函数我们就推测为add,事实也就是如此
理解函数调用
别忘了找到这个函数的目的:看下函数在实际情况下如何被调用
让我们分析一整个函数调用过程:
asm
复制代码
mov dword ptr ss:[rbp-4],A |
mov dword ptr ss:[rbp-8],14 |
mov edx,dword ptr ss:[rbp-8] |
mov eax,dword ptr ss:[rbp-4] |
mov ecx,eax |
call test_main.401550 |
这是在进入add函数前的操作,可以看到,最后整个指令运行完的结果是:
复制代码
ecx = 10
edx = 20
我们继续F7步进,看到这个RIP指针跳转了,推测整个程序的范围:

我们可以看到前面有一个很经典的栈帧创建和销毁
asm
复制代码
push rbp
mov rbp, rsp
...
pop rbp
ret
中间执行的应该就是我们的a+b,不过这个位置貌似没有用到Shadow Space
不过没关系,我们继续往下执行,这个ret从栈中取出返回地址,并跳回调用者,这就是简单的函数调用时,内部的变化
至于shadow space?我们发现指令中有类似东西:

我们之前提到过,Shadow Space 是 caller 为 callee 准备的;由 caller 保证存在,由 callee 按需使用,并且 caller 可以在不同调用之间复用
后面的函数都是复用这片空间的,于是我们推测这个位置是main函数的开头,在这个位置设置个断点,看看能不能跑到

我的推测是对的,指令在这个位置停下来了
asm
复制代码
push rbp
mov rbp,rsp
sub rsp,30
让我们分析影子空间的指令,发现预留了48个字节,这是为什么🧐?哦,这个函数的栈空间分配未必全都是影子空间,其中可能包含:
- Shadow Space / Home Space
- 局部变量
- 对齐所需空间
- 其他临时使用空间
具体是啥,还要看实际怎么使用
不管了,总之影子空间就在这里,后面printf运行完之后,就收回这片空间了
asm
复制代码
mov eax, 0
add rsp, 30
pop rbp
ret
如果我们继续设置断点步进,一直执行到ret结束,然后会发现这个函数又出去了

在这里我们能看到整个main函数的全貌,我们之前的猜测没有问题

至于这个函数前面为什么没看到影子空间,Shadow Space 是针对“一次调用”准备的,而且由caller提前分配而不是callee
main在自己的栈空间中准备好一个可以随时调用的空间,caller可以在自己的栈帧/栈空间中复用这块调用区域给callee,前一次调用结束后,下一次调用继续使用
评论