Python解析libc未导出符号

背景

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地址偏移,其他地址也是类似的原理,但是可以看出来这也有一定局限性:

  1. 我们分析的汇编代码是特定glibc的,可能别的版本或系统的libc会有细微区别。
  2. 我们对汇编代码的分析都是比较简单的,可以直接或者通过简单运算取到偏移,对于更复杂的取地址还需要别的方法。
  3. 未导出符号的获取依赖于已知符号和已知汇编代码,不够灵活。
  4. 查找方法通过简单遍历搜索,效率不高。

总结

前提

  1. 用于调用的符号偏移已知
  2. 目标符号能通过立即数或基于rip的偏移计算得到

方法

  1. gdb分析反汇编代码
  2. pwntools打开ELF文件并导出符号
  3. capstone根据分析获取未导出符号

局限

  1. 我们分析的汇编代码是特定glibc的,可能别的版本或系统的libc会有细微区别。
  2. 我们对汇编代码的分析都是比较简单的,可以直接或者通过简单运算取到偏移,对于更复杂的取地址还需要别的方法。
  3. 未导出符号的获取依赖于已知符号和已知汇编代码,不够灵活。
  4. 查找方法通过简单遍历搜索,效率不高。

参考

[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

暂无评论

发送评论 编辑评论

|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇