跳转至

逆向基础

约 1078 个字 10 行代码 预计阅读时间 5 分钟

逆向函数时,要提前预测下函数实现机制,以节省时间。要明白哪些部分属于程序特有的实现,哪些部分属于第三方的库,不要随便进到第三方库或者底层API里面分析。

调用约定

cdecl(C默认)由caller负责清理栈上传入参数。

stdcall由callee负责清理栈上传入参数(Win32API),被调函数返回时使用RETN X来退出,相当于RETN、POP X。比如退两个参数,就RETN 8。

fastcall为了提高速度,分别使用ECX、EDX传递前两个参数,更多参数还是使用内存。 传参时都是从右向左以此压入栈中。

一些常见汇编操作符

call 包括保存返回地址、IP跳转

retnpop EIP

test 相当于AND,但是不改变普通寄存器的值,只修改EFLAGS寄存器

NOP指令的用途

NOP指令通常用于控制时序的目的,强制内存对齐,防止流水线灾难,占据分支指令延迟,或是作为占位符以供程序的改善(或替代被移除的指令)。

函数执行栈帧推断

函数内部一般先会执行以下两条指令:

push ebp
mov ebp,esp

可以观察ebp和esp的修改情况推断函数栈帧

名称修饰

名称修饰(name mangling,name decoration),用来解决标志符的唯一命名问题。比如在不同的命名空间实现相同名称的函数,这个函数在怎么表示呢?名称修饰技术用来生成唯一的标志符,保留命名空间、函数名、结构体名、类名以及参数类型等等信息。名称修饰和调用约定、编译器有关,应用最广泛的是C++的代码(尤其是混合C编译时)。比如_ZN9wikipedia7article6formatEv可以用来表示:

namespace wikipedia
{
    class article
    {
        public:
        std::string format();
    }
}

其中_Z是开头(下划线+大写字母在C中是保留的标志符,避免冲突),N表示是嵌套有命名空间和类名,随后的数字+字母中,数字表示长度,字母表示名称,并以E结束。之后的参数表示函数的参数类型,v为void。

PE文件

PE(Portable Execution)文件是Windows系统使用的可执行文件格式。

  • 可执行PE文件扩展名一般为exe,src
  • 库文件扩展名一般为dll,ocx,cpl,drv
  • 驱动程序文件扩展名一般为sys,vxd
  • 对象文件扩展名一般为obj

PE文件的数据节

  • .text 代码节,存放二进制机器码
  • .data 初始化数据节,如宏定义、全局变量、静态变量
  • .idata 可执行文件使用的动态链接库等外部函数与文件,即输入表
  • .rsrc 程序资源节,包括图标、菜单等

#pragma data_seg()可以将代码任意部分编译到PE文件任意节,节名也可以自定义。

静态链接库与动态链接库

静态库的拓展名为.a.lib;动态库的拓展名一般为.so.dll

静态库编译时直接整合到目标程序中,编译成功后的可执行文件可以独立运行;动态库编译时可执行程序无法独立运行

静态库更新后需要更新整个目标程序;动态库更新后只需更换新的动态库即可

静态库编译命令:gcc -c xx.c -o xx.oar crs libxx.a xx.o;动态库编译命令:gcc xx.c -o libxx.so -shared -fPIC其中-fPIC表示使用相对位置

gdb

添加多个符号表add-symbol-file xxx addr其中addr是代码段起始地址,xxx可以为sym文件,或elf文件等。变异时需要加上-g保留符号表(指定具体格式如-g2 -gdwarf-2),可以逐个使用add-symbol-file,都添加进去。

使用ulimit -c unlimited设置不限制coredump文件大小,然后root用户echo "core-%e-%p" > /proc/sys/kernel/core_pattern设置保留程序名、pid,则对于编译时添加了-g选项的程序,其崩溃产生的coredump文件可以使用gdb <程序名> <coredump文件名>来寻找root cause。gdb内用where查看调用栈。

推荐阅读

Linux 静态库 编译和使用 Linux 动态库 编译和使用 Makefile入门 Makefile官方文档 coredump文件基础用法