hutrace的功能已经实现的比较全面了,dynamroio与pin虽然在对程序的分析处理上各有一定的优势,但是实际中测试下来dynamorio在Linux下的兼容性要比pin稍微差一点,存在一些无法用hutrace记录但pin可以追踪的情况,而且pin提供的接口功能也更丰富,故基于pin完成了大部分hutrace的功能,也即是下面要介绍的hzytrace工具。hzytrace目前仅支持linux平台,windows平台上基于dynamrio的hutrace在效率、兼容性上均优于pin,暂时就还没完善window下的hzytrace。
简介 hzytrace本身基于pin+codapintracer开发,原codapintracer仅支持Windows平台的API记录(类似drltrace),代码基于Window做了特别多的处理(所以hzytrace移植到win上比较简单…),功能也很多,导致移植到linux多了很多坑,而且在linux下pin的对程序的处理接口也存在一些问题,最终魔改完的代码快没法看了。。不过整体来说hzytrace的功能性还可以,可作为hutrace在处理一些Linux程序上的补充,本篇先介绍下hzytrace的基本功能,最后将结合一些实例进行演示。
基本功能 hzytrace支持的基本功能参数如下:
1 2 3 4 5 6 7 8 -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调用以及所有的基本块转移记录,下面结合一些实例的记录信息进行介绍。
基本块汇编指令打印 在hzytrace目录中执行下述指令:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 # 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日志内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 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:
1 $ ./pin -t hzytrace.so -bbllog 0 -- ls -al
trace结果:
1 2 3 4 5 6 7 8 9 10 11 12 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日志中不会再出现该情况。
syscall记录 功能及使用方式与hutrace类似,默认情况下只打印执行的syscall的名称,在hzytrace_linux.config文件中设置参数信息后才会根据参数类型打印对应的参数:
1 2 3 int|syscall_read|LONG|__out hex^ARG2|int int|syscall_write|LONG|char * int|syscall_open|char *|LONG
trace记录样例(运行过程中所有syscall):
1 $ ./pin -t hzytrace.so -syscall -- ls -al
分析trace的结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 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即可:
1 $ ./pin -t hzytrace.so -syscall 0 -- ls -al
指令运行状态记录 功能类似hutrace,开启指令状态记录时则不需要再记录bbllog功能中的指令反汇编结果:
1 $ ./pin -t hzytrace.so -inslog -- ls -al
trace的部分日志结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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
指定trace指令起始、结束地址 指定需要trace的指令范围,需要设置为基本块的开始或结束地址(为了便于展示,可以取消全局的syscall调用打印):
1 $ ./pin -t hzytrace.so -inslog -logstart 0x402a00 -logend 0x402c7c -syscall 0 -- ls -al
trace的结果信息中只会打印到0x402c7c的前一个基本块信息结束,并不会打印0x402c7c所在基本块的指令信息等。
指定需要trace的模块 利用target参数指定需要trace的模块:
1 $ ./pin -t hzytrace.so -inslog -target mytestso.so -syscall 0 -- ./test.out
trace得到的部分结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 ~~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
其它功能 任意地址hook插件 这里继续使用hutrace的linux应用文章中的例子进行演示,将原本程序中打印函数的参数从1-8修改为11-18,首先在hzytrace_linux.config设置需要hook的地址:
在hzytrace中简化了插件的加载和调用方式,只需要设置需要hook的的地址即可,而且不支持hutrace提供的函数开始、结束同时hook,如果有类似需求,可以在返回地址处添加新的返回地址hook条目,hzytrace运行到0x400706地址处时会加载mypinplugin.so并调用导出函数名为f_hookpre_0x400706的导出函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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编译时可以使用以下编译选项:
1 2 3 4 //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则不受此限制。
内存dump功能 根据hzytrace在运行时输出的调试信息中测试程序soTest加载后的代码范围0x400000-400b43,来演示下hzytrace的内存dump功能,在hzytrace_linux.config设置需要dump的时机以及需要dump的内存地址以及内存大小。
1 dump|0x400706|0x400000|0xb43
同样设置代码运行到0x400706时dump地址从0x400000开始大小为0xb43字节的内存数据,运行测试程序后,在Result目录中对应的日志记录文件夹中生成内存dump文件。
实例演示
hide 来看一个几年前qwb里ling博狗的例子“hide”,用了upx壳并且修改了一些upx标识,无法自动脱壳,程序本身使用了ptrace的PTRACE_TRACEME反调试(使用strace跟踪会退出),而且本身程序使用syscall完成读写、还使用了一处虚假的flag校验函数进行混淆,常规方式可以使用“catch syscall ptrace”逐步定位关键代码处,下面我们尝试使用hzytrace处理类似程序(hutrace虽然可以正常处理upx和PTRACE_TRACEME,但是这个程序无法正常trace,原因没有深入研究)。
1 2 3 4 5 6 7 8 9 10 # 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固定内存地址):
1 dump|0x4c8eeb|0x400000|0xca000
再次运行在对应的result日志目录生成内存dump文件,拖进ida,查看上述记录的日志中对应代码:
后面分析算法既可以ida静态,也可使用hzytrace的inslog参数追踪代码执行过程的寄存器及内存信息辅助分析算法,亦可使用gdb根据日志中的ptrace调用信息对其反调试进行绕过并继续调试,不再赘述。
bytepacker 这个程序来自某次分享的ctf赛题,使用双进程方式进行反调试,但是没有使用特别复杂的debugblock方式,所以依旧可以使用hzytrace进行指令流的trace(bbllog功能),但是无法使用inslog功能,全指令插桩会影响其执行流程:
1 2 3 4 5 6 7 8 9 10 11 12 $ ./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的代码位置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 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代码所在内存范围,确保内存地址固定):
1 dump|0x7fffe42f6360|0x7fffe41f7000|0x1c0000
后续可以据此静态分析,同样也可以向上述hide一样对照trace日志中父进程对子进程的操作进行针对性的处理,只是不能支持inslog功能打印寄存器及引用的内存状态了,父进程的处理代码地址可简单定位如下:
实际上像一些更复杂的debugblock代码等使用hzytrace处理子进程时也会崩溃,有的情况下可以通过pin提供的接口对其进行绕过,但是代价略大点,同样后续有好的方案了会进行更新。
总结 前面的文章中提到hzytrace一个主要目的是为了应对linux平台下一些程序无法使用hutrace处理的问题,当然不止是上面提到的一些小众的ctf赛题,pin的接口相对更为丰富,hzytrace也只是相对hutrace稍微简化了下,功能整体来说保持一致。
github地址:https://github.com/huhu0706/hzytrace