2023香山杯nesting详解

发布时间 2023-10-17 13:04:38作者: 阿威在潜水

nesting

通过函数分析 ,有一个VM的指令解析器,也看不懂,VM的题看起来特别费劲

在sub_16BC里面找cmp的flag比对指令,0x1E21和0x1EC9。最终发现输入正确的字符和错误的字符,0x1E21处的指令执行次数不一样,可以通过输入fo,fl,fi,其中fl是正确的字符,发现正确的字符在0x1E21处执行的次数更多,因此可以使用这个方法来进行逐字节爆破,但是需要一款工具来统计执行次数,看了别人的WP好像是通过直接patch的方式,让返回值返回执行次数,但没详细写,也看不懂,所以自己找了下,发现一款动态插桩工具可以实现指令执行次数统计,叫pin

安装教程

https://www.intel.com/content/www/us/en/developer/articles/tool/pin-a-dynamic-binary-instrumentation-tool.html
下载完之后解压,然后进入source/tools/ManualExamples,
make obj-intel64/inscount0.so TARGET=intel64,这是计数器的so,
此时在 obj-intel64 下编译生成了 inscount0.so,这个 so 即为一种 pintool,功能为记录程序执行的指令的条数;

判断 pintool 的功能可以阅读 pintool 源代码或者使用下条指令

$ pin -t your_pintool -h -- your_application

类似的,要编译 32 位的 pintool 可以使用

make obj-ia32/inscount0.so
编译 ManualExamples 中的所有 pintool 可以使用

make all TAEGET=intel64 make all TAEGET=ia32

编译完的使用格式是
../../../pin -t obj-intel64/inscount00.so -o inscount0.log -- you_binary

实战

原example是统计的所有指令的执行次数,但是,这个程序的执行次数是变化的,可能来自库函数的指令差异,我们只想统计指定行的执行次数,需要对inscount.cc进行更改,具体如下:

差异如下:

VOID docount() { icount++; }

已更改为:

VOID docount(void *ip) 
{
	if ((long long int)ip == 0x0000555555555E21)
	 icount++; 
}

和:INS_InsertCall(ins, IPOINT_BEFORE, (AFUNPTR)docount, IARG_END);

已更改为:

INS_InsertCall(ins, IPOINT_BEFORE, (AFUNPTR)docount, IARG_INST_PTR, IARG_END);

这里的IARG_INST_PTR的解释,表示传入eip
image

这还有一个小坑,我使用的ubuntu开了ALSR,所以基址一直在变,追踪不到,需要关闭ALSR

点击查看代码
sudo su
echo 0 > /proc/sys/kernel/randomize_va_space

然后就可以编写脚本爆破了,附上我的脚本

from pwn import *
import subprocess
def run(msg):
    cmd = [
    "../../../pin",
    "-t", "obj-intel64/inscount00.so",
    "-o", "inscount0.log",
    "--",
    "/home/xiaowei/Desktop/CTF/nesting"
]
    # 启动进程,并设置 stdin, stdout, stderr 为 PIPE,以便与进程交互
    p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

    # 发送数据到进程
    p.stdin.write(msg.encode())
    p.stdin.flush()
    # # 从进程接收数据
    output = p.stdout.readline()
    # print(output)
    # 结束进程
    p.terminate()
    return int(read("inscount0.log").split(" ")[1])


def read(fname):
    with open(fname) as f:
        return f.read()
# print(run("fl"))


charset = string.printable

l = []
flag = ""
counter = 0

while(True):
    max_chr = 0
    first_iteration = True  # 初始化标志为 True
    for chr in charset:
        tmp = run(flag + chr)
        if first_iteration:  # 只在第一次迭代时执行
            max_value = tmp
            first_iteration = False  # 设置标志为 False
        if tmp > max_value:
            max_chr = chr
            max_value = tmp
            break
    print(max_chr)
    flag += str(max_chr)
    print(flag)

大概半小时左右就能出来了
image

参考文章

https://eternal.red/2017/dont_panic-writeup/
https://firmianay.gitbooks.io/ctf-all-in-one/content/doc/5.6_llvm.htmlhttps://firmianay.gitbooks.io/ctf-all-in-one/content/doc/5.6_llvm.html