实战:定位目标函数

汇编yulabu2026-09-08 62 次阅读

实战:定位目标函数

之前一直在纸上谈兵,完全没有在实际的可执行文件中去看这个函数的调用,现在时机成熟,让我们来尝试第一次逆向

我们先编译以下测试代码,得到可执行文件:

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

这个时候才找到我们自己的模块

image-20260908113129525

喏,test_main就是我的EXE的名字,确实进到这个模块了,但是为什么这么多个地址,难道我要一个个看吗?

让我们使用伟大的搜索功能

image-20260908113511216

我们选择在当前模块搜索字符串,因为我们之前的c代码里面留了一个

c 复制代码
printf("%d\n", z);

其中的字符串会作为数据存在内存,于是我们在搜索中见到一个熟悉的面孔:

image-20260908114307624

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

image-20260908114443366

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

打个断点,一路F9让程序运行到断点

image-20260908115135171

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

image-20260908115307111

RAX和RDX转十进制:

复制代码
Ah->10
14h->20

这和我们给add传入的参数也吻合

至此,我们找到了我们的add函数

为什么能根据字符串定位

首先字符串不会被汇编成指令,而是作为数据被编译进EXE的数据区域,所以搜索当前模块(test_main)的字符串,其实就是在EXE的数据里面找一串被作为字符串存储的字节

image-20260908114307624

示例图片右边,记录的是数据区域记录的字符串和存储这个字符串的地址

左边记录的是这个地址被引用的位置,我们根据这个引用位置跳转过去,就可以找到对应执行这条指令的反汇编位置

image-20260908115135171

然后我们就发现,这里将这个字符串的地址放进寄存器

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指针跳转了,推测整个程序的范围:

image-20260908171446004

我们可以看到前面有一个很经典的栈帧创建和销毁

asm 复制代码
push rbp
mov rbp, rsp
...
pop rbp
ret

中间执行的应该就是我们的a+b,不过这个位置貌似没有用到Shadow Space

不过没关系,我们继续往下执行,这个ret从栈中取出返回地址,并跳回调用者,这就是简单的函数调用时,内部的变化

至于shadow space?我们发现指令中有类似东西:

image-20260908172952910

我们之前提到过,Shadow Space 是 caller 为 callee 准备的;由 caller 保证存在,由 callee 按需使用,并且 caller 可以在不同调用之间复用

后面的函数都是复用这片空间的,于是我们推测这个位置是main函数的开头,在这个位置设置个断点,看看能不能跑到

image-20260908173224267

我的推测是对的,指令在这个位置停下来了

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结束,然后会发现这个函数又出去了

image-20260908174104802

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

image-20260908174241182

至于这个函数前面为什么没看到影子空间,Shadow Space 是针对“一次调用”准备的,而且由caller提前分配而不是callee

main在自己的栈空间中准备好一个可以随时调用的空间,caller可以在自己的栈帧/栈空间中复用这块调用区域给callee,前一次调用结束后,下一次调用继续使用

评论

正在加载评论区…