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)