Logo WP

myBase

文件

Haskell 新语言,两条路

  • 学习 Haskell 语言特性、编译实现 —— 学到东西,慢
  • 打内存断点,追踪内存、寄存器的变化,边逆边猜,硬逆 —— 没啥用,能做出题

这里先选后者,

两个IO点, main(..) -> hs_main(..) -> rts_evalLazyIO(..) -> scheduleWaitThread(..) 下的:

  • awaitEvent(n_run_queue == 0); 一般获取用户输入卡在这
  • StgRun(stg_returnToStackTop, &pcapa[0]->r); 逻辑处理都在这,包括IO输出

搜索字符串,下硬件断点,运行,断在 ghczmprim_GHCziCString_unpackCStringzh_info 函数,在这个函数下断点继续调试,各种字符串都在这里出现,猜测所有 .rodata 的字符串都在这里获取,简单搓个 frida 脚本,看看能否验证猜测

# 文件 spawn.py
import subprocess
import frida

script = open("./scripts/hook_unpack.js", "r").read()
def go(str):
    p = subprocess.Popen(["./chall"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) # 不能 shell = True
    session = frida.attach(p.pid)
    s = session.create_script(script)
    s.on("message", lambda msg, data: print(f"{str}: {msg}"))
    s.load()
    stdout, stderr = p.communicate(str.encode())
    print(stdout.decode())
    p.kill()

go("aaa")
// 文件 ./scripts/hook_unpack.js
var unpack_str_addr = 0x48354C;
var times = 0;
Interceptor.attach(ptr(unpack_str_addr), {
    onEnter: function(args) {
        let out = ptr(this.context.r14).readUtf8String()
        send(out)
    }
})

正确的。类似的,最后调到函数 ghczmprim_GHCziClasses_eqChar_info 找到关键 cmp 指令,比较 加密后的输入 == 密文 ,继续 frida hook这里找出加密的规律,但是没找到,那就继续调试。

小技巧,调试到 cmp 的时候,为了得到下一个加密,可以修改寄存器的值以符合cmp的要求

调到函数 ghczmbignum_GHCziNumziInteger_integerQuotRemzh_info ,其中有一个 idiv 除法,是把输入转换成long(bytes_to_long),然后除以 key 的长度(31),然后把余数作为 加密后的输入 ,把商作为下一次的被除数,frida测试,验证成功

那加密过程就是一个 16 进制转 31 进制,逆向运算就是反过来,exp如下

# pip install pycryptodome
from Crypto.Util.number import bytes_to_long, long_to_bytes

key = "$#654321bvcxzkjhgfdsapoiuytrewq"
enc = "r6x1rotr6e6qixye5dwv1#1wpc6$oyygspczbc2z2zk4s#k1cwat6$y4vsdo3jx"

tmp = 0
for i in range(len(enc) - 1, -1, -1):
    xishu = key.find(enc[i])
    tmp += xishu * 31 ** i

print(long_to_bytes(tmp))

# 加密测试
div_times = 0
div = 0
r = ""
while tmp > 0:
    div_times += 1
    div = tmp // 31
    r += key[tmp % 31]
    tmp = div

print(r)