背景
glibc里面有很多未导出的符号,比如一些数据变量、内部函数等等。漏洞利用时有时需要对这些符号进行定位,由于这些符号与libc偏移固定,一般的方式是通过gdb算出这些偏移。但这种方式的弊端在于不能可靠地解析不同glibc下的符号偏移,每次切换不同的libc都需要重新定位。
本文以exit hook中的__libc_atexit
为例介绍一种基于汇编代码特征的自动化利用方法,本质是对手动分析的脚本复现。
对exit hook不熟悉的可以看我这篇文章:exit hook分析
方法
__libc_atexit
调用流程:
exit->__run_exit_handlers->__libc_atexit
这里__run_exit_handlers
也是未导出符号,所以要对该函数先进行定位,我们反汇编exit
,看到这里直接call了一个立即数完成调用,显然这个立即数就是__run_exit_handlers
的地址。
Dump of assembler code for function __GI_exit:
=> 0x00007ffff7e0fa40 <+0>: endbr64
0x00007ffff7e0fa44 <+4>: push rax
0x00007ffff7e0fa45 <+5>: pop rax
0x00007ffff7e0fa46 <+6>: mov ecx,0x1
0x00007ffff7e0fa4b <+11>: mov edx,0x1
0x00007ffff7e0fa50 <+16>: lea rsi,[rip+0x1a5cc1] # 0x7ffff7fb5718 <__exit_funcs>
0x00007ffff7e0fa57 <+23>: sub rsp,0x8
0x00007ffff7e0fa5b <+27>: call 0x7ffff7e0f7b0 <__run_exit_handlers>
End of assembler dump.
因为exit
是导出符号,我们可以直接使用 Pwntools.elf.ELF 接口定位到exit
。
然后可以使用 capstone 反汇编库,实现各种对反汇编指令的操作。这里可以向下搜索call
,取出操作数,得到__run_exit_handlers
的偏移。
这里是获取__run_exit_handlers
偏移的代码:
from pwn import *
from capstone import *
from capstone import x86
libc = ELF(path/to/libc.so)
# get __run_exit_handlers addr based on offset of exit
def get_run_exit_handlers(libc):
code = libc.data
off = libc.sym['exit']
md = Cs(CS_ARCH_X86, CS_MODE_64)
md.detail = True
# look for ptr offset
ptr_run_exit_handlers = None
for insn in md.disasm(code[off:], off):
if insn.mnemonic == 'call':
for operand in insn.operands:
if operand.type == x86.X86_OP_IMM:
ptr_run_exit_handlers = operand.value.imm
break
if ptr_run_exit_handlers:
break
if ptr_run_exit_handlers is None:
return None
return ptr_run_exit_handlers
得到该函数之后,可以进行下一步,反汇编__run_exit_handlers
,找到__libc_atexit
的调用,我们知道实际__libc_atexit
是调用的__elf_set___libc_atexit_element__IO_cleanup__
指向的地址,我们需要找到的也是该地址:
Dump of assembler code for function __run_exit_handlers:
0x00007ffff7e0f7b0 <+0>: endbr64
0x00007ffff7e0f7b4 <+4>: push r15
0x00007ffff7e0f7b6 <+6>: push r14
0x00007ffff7e0f7b8 <+8>: push r13
0x00007ffff7e0f7ba <+10>: push r12
0x00007ffff7e0f7bc <+12>: push rbp
0x00007ffff7e0f7bd <+13>: mov ebp,edi
0x00007ffff7e0f7bf <+15>: push rbx
0x00007ffff7e0f7c0 <+16>: mov rbx,rsi
0x00007ffff7e0f7c3 <+19>: sub rsp,0x18
0x00007ffff7e0f7c7 <+23>: mov DWORD PTR [rsp+0xc],edx
0x00007ffff7e0f7cb <+27>: test cl,cl
0x00007ffff7e0f7cd <+29>: jne 0x7ffff7e0fa08 <__run_exit_handlers+600>
0x00007ffff7e0f7d3 <+35>: lea r14,[rip+0x1aab0e] # 0x7ffff7fba2e8 <__exit_funcs_lock>
0x00007ffff7e0f7da <+42>: xor r12d,r12d
0x00007ffff7e0f7dd <+45>: nop DWORD PTR [rax]
0x00007ffff7e0f7e0 <+48>: mov eax,DWORD PTR fs:0x18
0x00007ffff7e0f7e8 <+56>: mov edx,0x1
0x00007ffff7e0f7ed <+61>: test eax,eax
0x00007ffff7e0f7ef <+63>: jne 0x7ffff7e0f9c2 <__run_exit_handlers+530>
0x00007ffff7e0f7f5 <+69>: cmpxchg DWORD PTR [r14],edx
0x00007ffff7e0f7f9 <+73>: mov r15,QWORD PTR [rbx]
0x00007ffff7e0f7fc <+76>: test r15,r15
0x00007ffff7e0f7ff <+79>: jne 0x7ffff7e0f83c <__run_exit_handlers+140>
0x00007ffff7e0f801 <+81>: jmp 0x7ffff7e0f968 <__run_exit_handlers+440>
0x00007ffff7e0f806 <+86>: nop WORD PTR cs:[rax+rax*1+0x0]
0x00007ffff7e0f810 <+96>: cmp rdx,0x2
0x00007ffff7e0f814 <+100>: je 0x7ffff7e0f8d0 <__run_exit_handlers+288>
0x00007ffff7e0f81a <+106>: mov eax,DWORD PTR fs:0x18
0x00007ffff7e0f822 <+114>: mov edx,0x1
0x00007ffff7e0f827 <+119>: test eax,eax
0x00007ffff7e0f829 <+121>: jne 0x7ffff7e0f8f0 <__run_exit_handlers+320>
0x00007ffff7e0f82f <+127>: cmpxchg DWORD PTR [r14],edx
0x00007ffff7e0f833 <+131>: cmp QWORD PTR [rip+0x1aaaa6],r13 # 0x7ffff7fba2e0 <__new_exitfn_called>
0x00007ffff7e0f83a <+138>: jne 0x7ffff7e0f7f9 <__run_exit_handlers+73>
0x00007ffff7e0f83c <+140>: mov rax,QWORD PTR [r15+0x8]
0x00007ffff7e0f840 <+144>: test rax,rax
0x00007ffff7e0f843 <+147>: je 0x7ffff7e0f940 <__run_exit_handlers+400>
0x00007ffff7e0f849 <+153>: lea r9,[rax-0x1]
0x00007ffff7e0f84d <+157>: mov r13,QWORD PTR [rip+0x1aaa8c] # 0x7ffff7fba2e0 <__new_exitfn_called>
0x00007ffff7e0f854 <+164>: mov QWORD PTR [r15+0x8],r9
0x00007ffff7e0f858 <+168>: mov eax,DWORD PTR fs:0x18
0x00007ffff7e0f860 <+176>: test eax,eax
0x00007ffff7e0f862 <+178>: jne 0x7ffff7e0f910 <__run_exit_handlers+352>
0x00007ffff7e0f868 <+184>: sub DWORD PTR [r14],0x1
0x00007ffff7e0f86c <+188>: mov rax,r9
0x00007ffff7e0f86f <+191>: shl rax,0x5
0x00007ffff7e0f873 <+195>: add rax,r15
0x00007ffff7e0f876 <+198>: mov rdx,QWORD PTR [rax+0x10]
0x00007ffff7e0f87a <+202>: cmp rdx,0x3
0x00007ffff7e0f87e <+206>: je 0x7ffff7e0f8b0 <__run_exit_handlers+256>
0x00007ffff7e0f880 <+208>: cmp rdx,0x4
0x00007ffff7e0f884 <+212>: jne 0x7ffff7e0f810 <__run_exit_handlers+96>
0x00007ffff7e0f886 <+214>: mov rdx,QWORD PTR [rax+0x18]
0x00007ffff7e0f88a <+218>: mov rdi,QWORD PTR [rax+0x20]
0x00007ffff7e0f88e <+222>: mov QWORD PTR [rax+0x10],0x0
0x00007ffff7e0f896 <+230>: mov esi,ebp
0x00007ffff7e0f898 <+232>: ror rdx,0x11
0x00007ffff7e0f89c <+236>: xor rdx,QWORD PTR fs:0x30
0x00007ffff7e0f8a5 <+245>: call rdx
0x00007ffff7e0f8a7 <+247>: jmp 0x7ffff7e0f81a <__run_exit_handlers+106>
0x00007ffff7e0f8ac <+252>: nop DWORD PTR [rax+0x0]
0x00007ffff7e0f8b0 <+256>: mov rax,QWORD PTR [rax+0x18]
0x00007ffff7e0f8b4 <+260>: ror rax,0x11
0x00007ffff7e0f8b8 <+264>: xor rax,QWORD PTR fs:0x30
0x00007ffff7e0f8c1 <+273>: call rax
0x00007ffff7e0f8c3 <+275>: jmp 0x7ffff7e0f81a <__run_exit_handlers+106>
0x00007ffff7e0f8c8 <+280>: nop DWORD PTR [rax+rax*1+0x0]
0x00007ffff7e0f8d0 <+288>: mov rdx,QWORD PTR [rax+0x18]
0x00007ffff7e0f8d4 <+292>: mov rsi,QWORD PTR [rax+0x20]
0x00007ffff7e0f8d8 <+296>: mov edi,ebp
0x00007ffff7e0f8da <+298>: ror rdx,0x11
0x00007ffff7e0f8de <+302>: xor rdx,QWORD PTR fs:0x30
0x00007ffff7e0f8e7 <+311>: call rdx
0x00007ffff7e0f8e9 <+313>: jmp 0x7ffff7e0f81a <__run_exit_handlers+106>
0x00007ffff7e0f8ee <+318>: xchg ax,ax
0x00007ffff7e0f8f0 <+320>: mov eax,r12d
0x00007ffff7e0f8f3 <+323>: lock cmpxchg DWORD PTR [r14],edx
0x00007ffff7e0f8f8 <+328>: je 0x7ffff7e0f833 <__run_exit_handlers+131>
0x00007ffff7e0f8fe <+334>: mov rdi,r14
0x00007ffff7e0f901 <+337>: call 0x7ffff7e5d5a0 <__lll_lock_wait_private>
0x00007ffff7e0f906 <+342>: jmp 0x7ffff7e0f833 <__run_exit_handlers+131>
0x00007ffff7e0f90b <+347>: nop DWORD PTR [rax+rax*1+0x0]
0x00007ffff7e0f910 <+352>: mov eax,r12d
0x00007ffff7e0f913 <+355>: xchg DWORD PTR [r14],eax
0x00007ffff7e0f916 <+358>: cmp eax,0x1
0x00007ffff7e0f919 <+361>: jle 0x7ffff7e0f86c <__run_exit_handlers+188>
0x00007ffff7e0f91f <+367>: xor r10d,r10d
0x00007ffff7e0f922 <+370>: mov edx,0x1
0x00007ffff7e0f927 <+375>: mov esi,0x81
0x00007ffff7e0f92c <+380>: mov rdi,r14
0x00007ffff7e0f92f <+383>: mov eax,0xca
0x00007ffff7e0f934 <+388>: syscall
0x00007ffff7e0f936 <+390>: jmp 0x7ffff7e0f86c <__run_exit_handlers+188>
0x00007ffff7e0f93b <+395>: nop DWORD PTR [rax+rax*1+0x0]
0x00007ffff7e0f940 <+400>: mov rax,QWORD PTR [r15]
0x00007ffff7e0f943 <+403>: mov QWORD PTR [rbx],rax
0x00007ffff7e0f946 <+406>: test rax,rax
0x00007ffff7e0f949 <+409>: je 0x7ffff7e0f953 <__run_exit_handlers+419>
0x00007ffff7e0f94b <+411>: mov rdi,r15
0x00007ffff7e0f94e <+414>: call 0x7ffff7deb330 <free@plt>
0x00007ffff7e0f953 <+419>: mov eax,DWORD PTR fs:0x18
0x00007ffff7e0f95b <+427>: test eax,eax
0x00007ffff7e0f95d <+429>: jne 0x7ffff7e0f9dd <__run_exit_handlers+557>
0x00007ffff7e0f95f <+431>: sub DWORD PTR [r14],0x1
0x00007ffff7e0f963 <+435>: jmp 0x7ffff7e0f7e0 <__run_exit_handlers+48>
0x00007ffff7e0f968 <+440>: mov BYTE PTR [rip+0x1a731d],0x1 # 0x7ffff7fb6c8c <__exit_funcs_done>
0x00007ffff7e0f96f <+447>: mov eax,DWORD PTR fs:0x18
0x00007ffff7e0f977 <+455>: test eax,eax
0x00007ffff7e0f979 <+457>: jne 0x7ffff7e0fa12 <__run_exit_handlers+610>
0x00007ffff7e0f97f <+463>: sub DWORD PTR [r14],0x1
0x00007ffff7e0f983 <+467>: cmp BYTE PTR [rsp+0xc],0x0
0x00007ffff7e0f988 <+472>: je 0x7ffff7e0f9bb <__run_exit_handlers+523>
0x00007ffff7e0f98a <+474>: lea rbx,[rip+0x1a1f07] # 0x7ffff7fb1898 <__elf_set___libc_atexit_element__IO_cleanup__>
0x00007ffff7e0f991 <+481>: lea rax,[rip+0x1a1f08] # 0x7ffff7fb18a0 <_IO_helper_jumps>
0x00007ffff7e0f998 <+488>: cmp rbx,rax
0x00007ffff7e0f99b <+491>: jae 0x7ffff7e0f9bb <__run_exit_handlers+523>
0x00007ffff7e0f99d <+493>: sub rax,0x1
0x00007ffff7e0f9a1 <+497>: sub rax,rbx
0x00007ffff7e0f9a4 <+500>: shr rax,0x3
0x00007ffff7e0f9a8 <+504>: lea r12,[rbx+rax*8+0x8]
0x00007ffff7e0f9ad <+509>: nop DWORD PTR [rax]
0x00007ffff7e0f9b0 <+512>: call QWORD PTR [rbx]
0x00007ffff7e0f9b2 <+514>: add rbx,0x8
0x00007ffff7e0f9b6 <+518>: cmp r12,rbx
0x00007ffff7e0f9b9 <+521>: jne 0x7ffff7e0f9b0 <__run_exit_handlers+512>
0x00007ffff7e0f9bb <+523>: mov edi,ebp
0x00007ffff7e0f9bd <+525>: call 0x7ffff7eac110 <__GI__exit>
0x00007ffff7e0f9c2 <+530>: mov eax,r12d
0x00007ffff7e0f9c5 <+533>: lock cmpxchg DWORD PTR [r14],edx
0x00007ffff7e0f9ca <+538>: je 0x7ffff7e0f7f9 <__run_exit_handlers+73>
0x00007ffff7e0f9d0 <+544>: mov rdi,r14
0x00007ffff7e0f9d3 <+547>: call 0x7ffff7e5d5a0 <__lll_lock_wait_private>
0x00007ffff7e0f9d8 <+552>: jmp 0x7ffff7e0f7f9 <__run_exit_handlers+73>
0x00007ffff7e0f9dd <+557>: mov eax,r12d
0x00007ffff7e0f9e0 <+560>: xchg DWORD PTR [r14],eax
0x00007ffff7e0f9e3 <+563>: sub eax,0x1
0x00007ffff7e0f9e6 <+566>: jle 0x7ffff7e0f7e0 <__run_exit_handlers+48>
0x00007ffff7e0f9ec <+572>: xor r10d,r10d
0x00007ffff7e0f9ef <+575>: mov edx,0x1
0x00007ffff7e0f9f4 <+580>: mov esi,0x81
0x00007ffff7e0f9f9 <+585>: mov rdi,r14
0x00007ffff7e0f9fc <+588>: mov eax,0xca
0x00007ffff7e0fa01 <+593>: syscall
0x00007ffff7e0fa03 <+595>: jmp 0x7ffff7e0f7e0 <__run_exit_handlers+48>
0x00007ffff7e0fa08 <+600>: call 0x7ffff7e10280 <__GI___call_tls_dtors>
0x00007ffff7e0fa0d <+605>: jmp 0x7ffff7e0f7d3 <__run_exit_handlers+35>
0x00007ffff7e0fa12 <+610>: xor eax,eax
0x00007ffff7e0fa14 <+612>: xchg DWORD PTR [r14],eax
0x00007ffff7e0fa17 <+615>: sub eax,0x1
0x00007ffff7e0fa1a <+618>: jle 0x7ffff7e0f983 <__run_exit_handlers+467>
0x00007ffff7e0fa20 <+624>: xor r10d,r10d
0x00007ffff7e0fa23 <+627>: mov edx,0x1
0x00007ffff7e0fa28 <+632>: mov esi,0x81
0x00007ffff7e0fa2d <+637>: mov rdi,r14
0x00007ffff7e0fa30 <+640>: mov eax,0xca
0x00007ffff7e0fa35 <+645>: syscall
0x00007ffff7e0fa37 <+647>: jmp 0x7ffff7e0f983 <__run_exit_handlers+467>
End of assembler dump.
注意0x00007ffff7e0f98a <+474>
的位置,这里gdb已经告诉我们就是__elf_set___libc_atexit_element__IO_cleanup__
的位置了。同时,可以看到上下文中没有相同特征的代码,我们可以直接查找指令为lea
,操作数为rbx
和一个以rip
为基准的取地址代码:
# get __libc_atexit addr based on offset of __run_exit_handlers
def get_libc_atexit_addr(libc):
code = libc.data
off = get_run_exit_handlers(libc)
md = Cs(CS_ARCH_X86, CS_MODE_64)
md.detail = True
# look for ptr offset
ptr_libc_atexit = None
for insn in md.disasm(code[off:], off):
# print("0x%x+%u:\t%s\t%s" % (off, inst.address-off, inst.mnemonic, inst.op_str))
if insn.mnemonic == 'lea' and len(insn.operands) == 2:
dest, src = insn.operands
if dest.type == x86.X86_OP_REG and insn.reg_name(dest.value.reg) == 'rbx':
if src.type == x86.X86_OP_MEM and insn.reg_name(src.value.mem.base) == 'rip':
ptr_libc_atexit = insn.address + insn.size + src.value.mem.disp
if ptr_libc_atexit:
break
if ptr_libc_atexit is None:
return None
return ptr_libc_atexit
至此,得到exit hook地址偏移,其他地址也是类似的原理,但是可以看出来这也有一定局限性:
- 我们分析的汇编代码是特定glibc的,可能别的版本或系统的libc会有细微区别。
- 我们对汇编代码的分析都是比较简单的,可以直接或者通过简单运算取到偏移,对于更复杂的取地址还需要别的方法。
- 未导出符号的获取依赖于已知符号和已知汇编代码,不够灵活。
- 查找方法通过简单遍历搜索,效率不高。
总结
前提
- 用于调用的符号偏移已知
- 目标符号能通过立即数或基于
rip
的偏移计算得到
方法
- gdb分析反汇编代码
- pwntools打开ELF文件并导出符号
- capstone根据分析获取未导出符号
局限
- 我们分析的汇编代码是特定glibc的,可能别的版本或系统的libc会有细微区别。
- 我们对汇编代码的分析都是比较简单的,可以直接或者通过简单运算取到偏移,对于更复杂的取地址还需要别的方法。
- 未导出符号的获取依赖于已知符号和已知汇编代码,不够灵活。
- 查找方法通过简单遍历搜索,效率不高。
参考
[1] Notes on abusing exit handlers, bypassing pointer mangling and glibc ptmalloc hooks, https://binholic.blogspot.com/2017/05/notes-on-abusing-exit-handlers.html