2025TSCTF-Grilhook
.apk、lua+加密注入.so、hook
题目是.false后缀的文件,放入die后发现它其实就是一个apk文件,放到jadx中打开,

打开后先到res/AndroidManifest.xml下找package=和android:name=,发现是
com.lynnette.girlhook.MainActivity,将.换位/,逻辑代码的路径,进入后就是图中所示
将原题文件复制一个副本,然后将后缀改为zip并解压,拿到其中lib目录下的.so文件再放到IDA中反编译,因为这两个(.dex和.so)文件是.apk执行的文件,所以我都看(其实一开始只看.dex)然后得到了诱饵flag

打开后遵循抓大放小原则,直接看有没有循环、判断、函数调用。发现三个调用了三个函数,其中hook_java_mothe显然是劫持了.dex,再看上面的what_is_this,在ai的帮助下终于看懂了,这是嵌入了一个加密后的lua脚本(在上文中已解释),继续看代码,发现申请了一个1437字节的空间v0,双击secret这个参数,发现刚好有1440字节的数据,事实上就是加密后的lua脚本,观察这几个参数,ai说是chacha20加密算法,因为chacha20需要的其中三个关键参数32 字节密钥、12 字节 nonce、一个计数器(1u)这里刚好对上了,然后用第三方库进行解密
import struct
from Crypto.Cipher import ChaCha20
# ═══════ 第 1 步:从 .so 里把密文抠出来 ═══════
SO = r"E:/下载/TS3 - 副本/lib/arm64-v8a/libgirlhook.so"
raw = open(SO, "rb").read()
VA = 0x18EC40 # IDA 里 secret 的地址
SIZE = 1440 # 你数出来的长度
def va2off(f, va):
"""虚拟地址 → 文件偏移(ELF 通用,以后都能用)"""
e_phoff = struct.unpack_from("<Q", f, 0x20)[0]
e_phentsize = struct.unpack_from("<H", f, 0x36)[0]
e_phnum = struct.unpack_from("<H", f, 0x38)[0]
for i in range(e_phnum):
o = e_phoff + i * e_phentsize
if struct.unpack_from("<I", f, o)[0] != 1: # 只看 PT_LOAD 段
continue
p_offset = struct.unpack_from("<Q", f, o + 0x08)[0]
p_vaddr = struct.unpack_from("<Q", f, o + 0x10)[0]
p_filesz = struct.unpack_from("<Q", f, o + 0x20)[0]
if p_vaddr <= va < p_vaddr + p_filesz:
return p_offset + (va - p_vaddr)
off = va2off(raw, VA)
secret = raw[off : off + SIZE]
print(f"[1] 抠出密文 {len(secret)} 字节(文件偏移 {hex(off)})")
# ═══════ 第 2 步:ChaCha20 解密 ★ 核心就这 4 行 ═══════
key = b"youareclosetoit_youareclosetoit_" # 32 字节
nonce = b"continueabcd" # 12 字节
c = ChaCha20.new(key=key, nonce=nonce)
c.seek(64) # ★ 计数器从 1 开始 = 跳过第 1 个 64 字节块
plain = c.decrypt(secret)
# ═══════ 第 3 步:输出 ═══════
open(r"E:/1/lua_script.txt", "wb").write(plain)
print("[2] 解密完成,前 60 字节:")
print(plain[:60].decode("utf-8", "replace"))
以上代码即为作者写的lua代码,分析后发现它劫持了.dex中的两个函数,第一个check_if_correct(劫持的代码即23行fun1)被劫持后永远返回0,所以!check_if_correct恒为1,所以最后那个if中的三目要区check_again。
再看劫持check_again的代码,他就是fun_enter,被写在lua中,
local function xor_byte(a, b)
local result = 0
for i = 0, 7 do
local x = a % 2
local y = b % 2
result = result + ((x ~ y) << i)
a = math.floor(a / 2)
b = math.floor(b / 2)
end
return result
end
function xor_encrypt(str, key)
local result = {}
for i = 1, #str do
local c = string.byte(str, i)
c = (c + 1) % 255
local k = string.byte(key, (i - 1) % #key + 1)
result[i] = string.char(xor_byte(c, k))
end
return table.concat(result)
end
-- base64 encode
local b='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
function base64_encode(data)
return ((data:gsub('.', function(x)
local r,bits='',string.byte(x)
for i=8,1,-1 do r=r..(bits % 2^i - bits % 2^(i-1) > 0 and '1' or '0') end
return r
end)..'0000'):gsub('%d%d%d?%d?%d?%d?', function(x)
if #x < 6 then return '' end
local c=0
for i=1,6 do c=c+(x:sub(i,i)=='1' and 2^(6-i) or 0) end
return b:sub(c+1,c+1)
end)..({ '', '==', '=' })[#data % 3 + 1])
end
function encrypt(plain, key)
return base64_encode(xor_encrypt(plain, key))
end
function func_enter(args)
local strobj = args[1]
local str = getJavaStringContent(strobj)
local encrypted = encrypt(str, "W0WY0U4R3S0G00D2F1NDTH3K3Y")
local e1 = createJavaString(strobj, encrypted)
args[1] = e1
return true, args, 0
end~
以上即lua脚本,分析可知它将用户的输入异或加密后取代了.dex里那个check_again,然后再与 ("AmQTDHd7fy5CIAQyRUokVD1RJ3VlJFM4WTE+CBcn")
比较是否相同,用这个字符串按照lua再异或一下就能得出flag了

浙公网安备 33010602011771号