hutrace的功能已经实现的比较全面了,dynamroio与pin虽然在对程序的分析处理上各有一定的优势,但是实际中测试下来dynamorio在Linux下的兼容性要比pin稍微差一点,存在一些无法用hutrace记录但pin可以追踪的情况,而且pin提供的接口功能也更丰富,故基于pin完成了大部分hutrace的功能,也即是下面要介绍的hzytrace工具。hzytrace目前仅支持linux平台,windows平台上基于dynamrio的hutrace在效率、兼容性上均优于pin,暂时就还没完善window下的hzytrace。
1. 简介
hzytrace本身基于pin+codapintracer开发,原codapintracer仅支持Windows平台的API记录(类似drltrace),代码基于Window做了特别多的处理(所以hzytrace移植到win上比较简单...),功能也很多,导致移植到linux多了很多坑,而且在linux下pin的对程序的处理接口也存在一些问题,最终魔改完的代码快没法看了。。不过整体来说hzytrace的功能性还可以,可作为hutrace在处理一些Linux程序上的补充,本篇先介绍下hzytrace的基本功能,最后将结合一些实例进行演示。
2. 基本功能
hzytrace支持的基本功能参数如下:
-bbllog [default true] 打印所有执行基本块的汇编指令
-forklog [default true] 默认追踪fork的子进程
-syscall [default true] 默认追踪进程的syscall调用
-inslog [default false] 打印所有执行指令的运行状态信息
-logstart [default false] 指定记录的指令开始地址
-logend [default false] 指定记录的指令结束地址
-target [default -] 指定需要记录的模块名
-allimage [default false] 记录所有模块的运行
默认情况下,hzytrace会记录程序的API调用、Syscall调用以及所有的基本块转移记录,下面结合一些实例的记录信息进行介绍。
2.1 基本块汇编指令打印
在hzytrace目录中执行下述指令:
# huhu @ huhu in ~/Desktop/pin-3.5 [3:12:07]
$ ./pin -t hzytrace.so -bbllog -- ls -al
[INFO] Configuring Pintool
[INFO] Starting instrumented program
Load Image:/bin/ls Base:400000-41da63
setMainIMGAddress:400000 41da63
Load Image:/lib64/ld-linux-x86-64.so.2 Base:7f8438bd4000-7f8438bf93af
Load Image:[vdso] Base:7ffdde88b000-7ffdde88c02a
[INFO] Opening file
./Results/2022_05_21_03_07_08//TRACER/hzytrace.ls.11596.0.1653120428.out
Load Image:/lib/x86_64-linux-gnu/libselinux.so.1 Base:7f84252b9000-7f84254da6df
Load Image:/lib/x86_64-linux-gnu/libc.so.6 Base:7f8424ec2000-7f842528b99f
Load Image:/lib/x86_64-linux-gnu/libpcre.so.3 Base:7f8424c4b000-7f8424eba107
Load Image:/lib/x86_64-linux-gnu/libdl.so.2 Base:7f8424a2f000-7f8424c320ef
Load Image:/lib/x86_64-linux-gnu/libpthread.so.0 Base:7f8424811000-7f8424a2d427
Load Image:/lib/x86_64-linux-gnu/libnss_compat.so.2 Base:7f8423474000-7f842367c45f
Load Image:/lib/x86_64-linux-gnu/libnsl.so.1 Base:7f8423256000-7f842346ea57
Load Image:/lib/x86_64-linux-gnu/libnss_nis.so.2 Base:7f8422f82000-7f842318d587
Load Image:/lib/x86_64-linux-gnu/libnss_files.so.2 Base:7f8422d6b000-7f8422f7c717
//以上为hzytrace打印的调试信息
total 6228
drwxr-x--- 4 huhu huhu 4096 May 21 03:07 .
drwxr-x--- 48 huhu huhu 4096 May 21 02:50 ..
//省略部分ls结果显示
查看Results目录下记录的trace日志内容:
C0x4049c4|0x402640
//C0x4049c4对应下面ida中ls程序反汇编结果
//.text:00000000004049AF mov r8, offset fini ; fini
//.text:00000000004049B6 mov rcx, offset init ; init
//.text:00000000004049BD mov rdi, offset main ; main
//.text:00000000004049C4 call ___libc_start_main
B0x402640|0x402640
D0x402640|jmp qword ptr [rip+0x21bb7a]
B0x402646|0x40264b
D0x402646|push 0x35
D0x40264b|jmp 0x4022e0
B0x4022e0|0x4022e6
D0x4022e0|push qword ptr [rip+0x21bd22]
D0x4022e6|jmp qword ptr [rip+0x21bd24]
J0x4022e6|0x7f15489d0f10
//hzytrace_linux.config未配置的函数默认打印四个参数
~~16451~~ libc.so.6!__libc_start_main N:0x1
arg 0: 0x402a00
arg 1: 0x2
arg 2: 0x7ffda586d668
arg 3: 0x413bb0
//ls的init
B0x413bb0|0x413bdc
D0x413bb0|push r15
D0x413bb2|push r14
D0x413bb4|mov r15d, edi
D0x413bb7|push r13
D0x413bb9|push r12
D0x413bbb|lea r12, ptr [rip+0x20a23e]
D0x413bc2|push rbp
D0x413bc3|lea rbp, ptr [rip+0x20a23e]
D0x413bca|push rbx
D0x413bcb|mov r14, rsi
D0x413bce|mov r13, rdx
D0x413bd1|sub rbp, r12
D0x413bd4|sub rsp, 0x8
D0x413bd8|sar rbp, 0x3
D0x413bdc|call 0x4022b8
C0x413bdc|0x4022b8
......
//hzytrace_linux.config配置的函数根据设置的参数类型的打印参数信息
~~16451~~ libc.so.6!__lxstat N:0x25b
arg 0: 0x1
arg 1: 0x7ffda586ce00
arg 2: 0x246d3e0
arg 3: 0x2
~~16451~~ libc.so.6!__lxstat64 N:0x25c
arg 0: 0x1 (type=int, size=0x4)
arg 1: pintool.log (type=char*, size=0x0)
arg 2: (type=char*, size=0x0)
S|lstat:6
executed libc.so.6!__lxstat returnIp:0x4080c9 =>
retval: 0x0
executed libc.so.6!__lxstat64 returnIp:0x4080c9 =>
retval: 0x0 (type=int, size=0x4)
......
显示功能基本与hutrace保持一致,同时支持hex以及struct打印,除后面介绍的hook、dump功能上(不支持hutrace的简易patch、hide功能),hzytrace_linux.config与hutrace.config在API参数打印上的设置基本一致。
如果不需要打印基本块对应的汇编指令,可以设置bbllog参数的值为false:
$ ./pin -t hzytrace.so -bbllog 0 -- ls -al
trace结果:
C0x4049c4|0x402640
J0x4022e6|0x7f93f9a77f10
~~16821~~ libc.so.6!__libc_start_main N:0x1
arg 0: 0x402a00
arg 1: 0x2
arg 2: 0x7ffda07a5b88
arg 3: 0x413bb0
C0x413bdc|0x4022b8
R0x4022d1|0x413be1
C0x413bf9|0x404a70
R0x404a49|0x413bfd
R0x413c14|0x7f93e5d6e7cf
注意开启bbllog的trace日志中会打印出所有ls主程序中执行的代码,而在上述不开启bbllog的trace日志结果中,并不会记录到跳转到main函数0x402A00地址的信息,因为并非是从ls主程序中执行的指令跳转进入的main函数。
- 一个小bug:在ls等程序运行加载libc.so.6进行初始化时,pin无法获取到初始化代码的模块名称,而hzytrace记录时会默认记录不在任意模块内的代码,导致trace日志开头会存在一小部分libc.so代码的冗余,后面的trace日志中不会再出现该情况。
2.2 syscall记录
功能及使用方式与hutrace类似,默认情况下只打印执行的syscall的名称,在hzytrace_linux.config文件中设置参数信息后才会根据参数类型打印对应的参数:
int|syscall_read|LONG|__out hex^ARG2|int
int|syscall_write|LONG|char *
int|syscall_open|char *|LONG
trace记录样例(运行过程中所有syscall):
$ ./pin -t hzytrace.so -syscall -- ls -al
分析trace的结果:
S|brk:12
S|access:21
S|access:21
S|open:2
SysEnter|syscall_open|0x7f5ec45d41d1|0x80000|
arg 0: /etc/ld.so.cache (type=char*, size=0x0)
arg 1: 0x80000 (type=long, size=0x8)
SysExit|open
S|fstat:5
S|mmap:9
S|close:3
S|access:21
S|open:2
SysEnter|syscall_open|0x7f5ec47dbd60|0x80000|
arg 0: /lib/x86_64-linux-gnu/libselinux.so.1 (type=char*, size=0x0)
arg 1: 0x80000 (type=long, size=0x8)
SysExit|open
S|read:0
SysEnter|syscall_read|0x3|0x7ffda586ce38|0x340|
arg 0: 0x3 (type=long, size=0x8)
arg 2: 0x340 (type=int, size=0x4)
SysExit|read
arg 1: 0x7ffda586ce38
000000: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 .ELF............
000010: 03 00 3e 00 01 00 00 00 b0 5a 00 00 00 00 00 00 ..>......Z......
000020: 40 00 00 00 00 00 00 00 30 f5 01 00 00 00 00 00 @.......0.......
000030: 00 00 00 00 40 00 38 00 08 00 40 00 1e 00 1d 00 ....@.8...@.....
000040: 01 00 00 00 05 00 00 00 00 00 00 00 00 00 00 00 ................
......
如果不需要记录syscall调用,同样设置syscall参数为0即可:
$ ./pin -t hzytrace.so -syscall 0 -- ls -al
2.3 指令运行状态记录
功能类似hutrace,开启指令状态记录时则不需要再记录bbllog功能中的指令反汇编结果:
$ ./pin -t hzytrace.so -inslog -- ls -al
trace的部分日志结果:
B0x402a00|0x402a2c
I0x402a00|push r15 | M:0x7fffffffd930 | D:0x0 | r15:0x0
I0x402a02|push r14 | M:0x7fffffffd928 | D:0x0 | r14:0x0
I0x402a04|push r13 | M:0x7fffffffd920 | D:0x7fffffffda10 | r13:0x7fffffffda10
I0x402a06|push r12 | M:0x7fffffffd918 | D:0x4049a0 | r12:0x4049a0
I0x402a08|push rbp | M:0x7fffffffd910 | D:0x413bb0 | rbp:0x413bb0
I0x402a09|push rbx | M:0x7fffffffd908 | D:0x0 | rbx:0x0
I0x402a0a|mov ebx, edi | rdi:0x2 | rbx:0x0
I0x402a0c|mov rbp, rsi | rsi:0x7fffffffda18 | rbp:0x413bb0
I0x402a0f|sub rsp, 0x388 | rsp:0x7fffffffd908
I0x402a16|mov rdi, qword ptr [rsi] | M:0x7fffffffda18 | D:0x7fffffffddeb | rdi:0x2
I0x402a19|mov rax, qword ptr fs:[0x28] | M:0x7fffe39a3828 | D:0x8cde58e761386e00 | rax:0x402a00
I0x402a22|mov qword ptr [rsp+0x378], rax | M:0x7fffffffd8f8 | D:0x413bfd | rax:0x8cde58e761386e00
I0x402a2a|xor eax, eax | rax:0x8cde58e761386e00
C0x402a2c|0x40db00
I0x402a2c|call 0x40db00 | M:0x7fffffffd578 | D:0x7fffe46f84e8
2.4 指定trace指令起始、结束地址
指定需要trace的指令范围,需要设置为基本块的开始或结束地址(为了便于展示,可以取消全局的syscall调用打印):
$ ./pin -t hzytrace.so -inslog -logstart 0x402a00 -logend 0x402c7c -syscall 0 -- ls -al
trace的结果信息中只会打印到0x402c7c的前一个基本块信息结束,并不会打印0x402c7c所在基本块的指令信息等。
2.5 指定需要trace的模块
利用target参数指定需要trace的模块:
$ ./pin -t hzytrace.so -inslog -target mytestso.so -syscall 0 -- ./test.out
trace得到的部分结果:
~~25933~~ libdl.so.2!dlsym N:0x7
arg 0: 0x555555756030 (type=long, size=0x8)
arg 1: my_main (type=char*, size=0x0)
executed libdl.so.2!dlsym returnIp:0x5555555548cb =>
retval: 0x7fffe36f460a (type=int, size=0x4)
//得到mytestso.so!my_main的函数地址
B0x5555555548cb|0x5555555548e2
I0x5555555548cb|mov qword ptr [rbp-0x10], rax | M:0x7fffffffd920 | D:0x0 | rax:0x7fffe36f460a
I0x5555555548cf|mov rax, qword ptr [rbp-0x10] | M:0x7fffffffd920 | D:0x7fffe36f460a | rax:0x7fffe36f460a
I0x5555555548d3|mov edx, 0x3 | rdx:0x1
I0x5555555548d8|mov esi, 0x2 | rsi:0x7fffe471a0d8
I0x5555555548dd|mov edi, 0x1 | rdi:0x7ffff7ffd948
C0x5555555548e2|0x7fffe36f460a
I0x5555555548e2|call rax | M:0x7fffffffd918 | D:0x5555555548cb | rax:0x7fffe36f460a
~~25933~~ mytestso.so!my_main N:0x8
arg 0: 0x1
arg 1: 0x2
arg 2: 0x3
arg 3: 0x0
B0x7fffe36f460a|0x7fffe36f4622
I0x7fffe36f460a|push rbp | M:0x7fffffffd910 | D:0x7fffffffd930 | rbp:0x7fffffffd930
I0x7fffe36f460b|mov rbp, rsp | rsp:0x7fffffffd910 | rbp:0x7fffffffd930
I0x7fffe36f460e|sub rsp, 0x10 | rsp:0x7fffffffd910
I0x7fffe36f4612|mov dword ptr [rbp-0x4], edi | M:0x7fffffffd90c | D:0xffffd93000000000 | rdi:0x1
I0x7fffe36f4615|mov dword ptr [rbp-0x8], esi | M:0x7fffffffd908 | D:0x100000000 | rsi:0x2
I0x7fffe36f4618|mov dword ptr [rbp-0xc], edx | M:0x7fffffffd904 | D:0x200005555 | rdx:0x3
I0x7fffe36f461b|lea rdi, ptr [rip+0x1f] | rip:0x7fffe36f461b | rdi:0x1
3. 其它功能
3.1 任意地址hook插件
这里继续使用hutrace的linux应用文章中的例子进行演示,将原本程序中打印函数的参数从1-8修改为11-18,首先在hzytrace_linux.config设置需要hook的地址:
hook|0x400706
在hzytrace中简化了插件的加载和调用方式,只需要设置需要hook的的地址即可,而且不支持hutrace提供的函数开始、结束同时hook,如果有类似需求,可以在返回地址处添加新的返回地址hook条目,hzytrace运行到0x400706地址处时会加载mypinplugin.so并调用导出函数名为f_hookpre_0x400706的导出函数。
int f_hookpre_0x400706(bluepill_tls *tdata,ADDRINT *r_RDI,ADDRINT *r_RSI,ADDRINT *r_RDX,ADDRINT *r_RCX,ADDRINT *r_R8,ADDRINT *r_R9,ADDRINT *r_RAX,ADDRINT *r_RBX,ADDRINT *r_RBP,ADDRINT *r_RSP,ADDRINT *r_R10,ADDRINT *r_R11,ADDRINT *r_R12,ADDRINT *r_R13,ADDRINT *r_R14,ADDRINT *r_R15)
{
*r_RDI = 11;
*r_RSI = 12;
*r_RDX = 13;
*r_RCX = 14;
*r_R8 = 15;
*r_R9 = 16;
*(ADDRINT *)(*r_RSP + (7-6) * 8) = 17;
*(ADDRINT *)(*r_RSP + (8-6) * 8) = 18;
//写入到trace的log日志中
(*tdata->file_write)(tdata->threadid, tdata->buffer, tdata->OutFile, "[hook]|test r_RDI:%p\n",*r_RDI);
return 0;
}
mypinplugin.so编译时可以使用以下编译选项:
//x64
g++ -fPIC -shared -o mypinplugin.so mypinPlugin.cpp -m64 -D X86_64 -Wl,--hash-style=sysv
//x32
g++ -fPIC -shared -o mypinplugin.so mypinPlugin.cpp -m32 -D X86_32 -Wl,--hash-style=sysv
同时注意因为pin的限制,插件中无法引用其它系统的libc.so文件,故不建议在hook函数中引用api函数实现较为复杂的功能,如果确有此类需求,可以尝试编译成静态的so文件或者编译一个main函数为空、仅导出功能函数的pin插件加载,使用hutrace则不受此限制。
3.2 内存dump功能
根据hzytrace在运行时输出的调试信息中测试程序soTest加载后的代码范围0x400000-400b43,来演示下hzytrace的内存dump功能,在hzytrace_linux.config设置需要dump的时机以及需要dump的内存地址以及内存大小。
dump|0x400706|0x400000|0xb43
同样设置代码运行到0x400706时dump地址从0x400000开始大小为0xb43字节的内存数据,运行测试程序后,在Result目录中对应的日志记录文件夹中生成内存dump文件。
4. 实例演示
4.1 hide
来看一个几年前qwb里ling博狗的例子“hide”,用了upx壳并且修改了一些upx标识,无法自动脱壳,程序本身使用了ptrace的PTRACE_TRACEME反调试(使用strace跟踪会退出),而且本身程序使用syscall完成读写、还使用了一处虚假的flag校验函数进行混淆,常规方式可以使用“catch syscall ptrace”逐步定位关键代码处,下面我们尝试使用hzytrace处理类似程序(hutrace虽然可以正常处理upx和PTRACE_TRACEME,但是这个程序无法正常trace,原因没有深入研究)。
# huhu @ huhu in ~/Desktop/pin-3.5 [7:30:39]
$ ./pin -t hzytrace.so -bbllog -- ./hide
[INFO] Configuring Pintool
[INFO] Starting instrumented program
[INFO] Opening file
./Results/2022_05_26_07_31_43//TRACER/hzytrace.hide.37360.0.1653568303.out
Enter the flag:
1234567890
You are wrong
查看日志直接搜索输入的“1234567890”(trace的日志500m文件使用010edit秒搜):
因为hzytrace仅对运行的代码进行记录,下面把这段内存进行dump拖进ida里查看完整的代码逻辑,在hzytrace_linux.config中设置执行到0x4c8eeb代码处时自动dump地址为0x400000代码处的内存(可设置关闭aslr固定内存地址):
dump|0x4c8eeb|0x400000|0xca000
再次运行在对应的result日志目录生成内存dump文件,拖进ida,查看上述记录的日志中对应代码:
后面分析算法既可以ida静态,也可使用hzytrace的inslog参数追踪代码执行过程的寄存器及内存信息辅助分析算法,亦可使用gdb根据日志中的ptrace调用信息对其反调试进行绕过并继续调试,不再赘述。
4.2 bytepacker
这个程序来自某次分享的ctf赛题,使用双进程方式进行反调试,但是没有使用特别复杂的debugblock方式,所以依旧可以使用hzytrace进行指令流的trace(bbllog功能),但是无法使用inslog功能,全指令插桩会影响其执行流程:
$ ./pin -t hzytrace.so -bbllog -- ./bytepacker
[INFO] Configuring Pintool
[INFO] Starting instrumented program
[INFO] Opening file
./Results/2022_05_29_08_46_16//TRACER/hzytrace.bytepacker.47505.0.1653831976.out
[INFO] Fork New Process
[INFO] Opening file
./Results/2022_05_29_08_46_16//TRACER/hzytrace.bytepacker.47509.0.1653831976.out
Show me the flag:
>> 1234567890
No, not this one.%
同样可以在trace的日志中直接搜索到使用syscall方式读取输入的测试flag的代码位置:
B0x7fffe42f6359|0x7fffe42f635e
D0x7fffe42f6359|mov eax, 0x0
D0x7fffe42f635e|syscall
S|read:0
SysEnter|syscall_read|0x0|0x7ffff7fff420|0x400|
arg 0: <null> (type=long, size=0x8)
arg 2: 0x400 (type=int, size=0x4)
SysExit|read
arg 1: 0x7ffff7fff420
000000: 31 32 33 34 35 36 37 38 39 30 30 0a 00 00 00 00 12345678900.....
......//省略部分显示
(type=__out hex^ARG2*, size=0x400)
B0x7fffe42f6360|0x7fffe42f6366
D0x7fffe42f6360|cmp rax, 0xfffffffffffff001
D0x7fffe42f6366|jnb 0x7fffe42f6399
......//省略部分显示
B0x7fffe42f63b0|0x7fffe42f63b7
D0x7fffe42f63b0|cmp dword ptr [rip+0x2d2389], 0x0
D0x7fffe42f63b7|jnz 0x7fffe42f63c9
B0x7fffe42f63b9|0x7fffe42f63be
D0x7fffe42f63b9|mov eax, 0x1
D0x7fffe42f63be|syscall
S|write:1
SysEnter|syscall_write|0x1|0x7ffff7fff010|
arg 0: 0x1 (type=long, size=0x8)
arg 1: No, not this one.5;74mflag[m:
(type=char*, size=0x0)
SysExit|write
在hzytrace_linux.config中设置dump内存(关闭aslr的情况下,查看0x7fffe42f6360代码所在内存范围,确保内存地址固定):
dump|0x7fffe42f6360|0x7fffe41f7000|0x1c0000
后续可以据此静态分析,同样也可以向上述hide一样对照trace日志中父进程对子进程的操作进行针对性的处理,只是不能支持inslog功能打印寄存器及引用的内存状态了,父进程的处理代码地址可简单定位如下:
实际上像一些更复杂的debugblock代码等使用hzytrace处理子进程时也会崩溃,有的情况下可以通过pin提供的接口对其进行绕过,但是代价略大点,同样后续有好的方案了会进行更新。
5. 总结
前面的文章中提到hzytrace一个主要目的是为了应对linux平台下一些程序无法使用hutrace处理的问题,当然不止是上面提到的一些小众的ctf赛题,pin的接口相对更为丰富,hzytrace也只是相对hutrace稍微简化了下,功能整体来说保持一致。
github地址:https://github.com/huhu0706/hzytrace