首页
社区
课程
招聘
[原创]攻防世界 GFSJ0508「黑客精神」- 逆向分析
发表于: 2026-10-17 12:26 809

[原创]攻防世界 GFSJ0508「黑客精神」- 逆向分析

2026-10-17 12:26
809

分析机为 macOS(自带 objdump 对 ARM Thumb-2 支持不完善,后面会踩坑),因此准备了一套 Python 逆向环境:

需要用到的核心 Python 库:

结论:

关键线索已经浮现:

这些就是原生函数的真实入口地址,后面反汇编就按这些地址切分函数。

图片描述

包名 com.gdufs.xman,三个关键类:

MyApp(Application 子类)

MainActivity

RegActivity

push {r0, r1, ...} 在 ARM 指令集里根本不合法 → 说明这些函数是用 Thumb-2 编译的,但 objdump 当成 ARM 解码,整段错位。

由于 macOS 的 objdump 不支持 force-thumb,写 Python 脚本用 Capstone 解码:

反汇编结果片段(n2 开头):

至此确认 n2 就是一个 逐字节 XOR 加密函数:

静态区 .data 中保存了 JNINativeMethod 结构体数组(三要素:{方法名指针, 签名指针, 函数指针})。该数组在文件偏移 0x4004 处,用 Python 解析:

解析结果:

函数指针 (0x13b1/0x11f9/0x14cd) 末尾的 +1 是 ARM/Thumb 调用约定(bit0=1 表示 Thumb 代码)。

逻辑梳理(结合反汇编 + 字面池):

反汇编尾部(从 strcmp 开始,地址 0x1410 起):

字符串指针解析(PC 相对 + 字面池):

核心结论:initSN 把 reg.dat 读出来后,只要 strcmp(content, "EoPAoY62@ElRD") == 0,就把状态置为 m=1(注册成功)。也就是说,写进 reg.dat 的内容必须等于密文 EoPAoY62@ElRD。

work 仅根据 m 显示提示文本,不会直接把 flag 字符串回显——flag 就是能让你通过 initSN 校验的那个输入。

把第 2~6 步得到的关键常量汇总:

关系推导:

按 n2 的索引算法,对密文 EoPAoY62@ElRD(下标 i = 0..12)逐字节逆 XOR:

拼接明文:2 0 1 6 0 8 A m ! 2 3 3 3 → 201608Am!2333

注意:密钥字节 struct[idx] 在 i%3==0/1/2 时分别对应 w/_/a,因此密文每 3 字节循环出现 E o P/A o Y/6 2 @/E l R/D,与 EoPAoY62@ElRDEoP 的周期性完全吻合,进一步佐证算法正确。

用同样的算法正向加密,应当还原出密文:

加密 201608Am!2333 得到密文 EoPAoY62@ElRD,与 initSN 内置目标完全一致 → 注册码正确无误。

说明:原生层 strcmp 比较的是 13 字节密文,因此严格输入 201608Am!2333 即可完成注册;xman{} 是题目在 rodata 提示里指定的 flag 包装格式(输入即是flag,格式为xman{……}!)。

最终 Flag:xman{201608Am!2333}

RegActivity 输入 → saveSN(n2) 用密钥 W3_arE_whO_we_ARE\0 + 计数器 0x7e0 逐字节 XOR 加密 → 写入 /sdcard/reg.dat → 下次启动 initSN(n1) 读回并 strcmp 比对内置目标 EoPAoY62@ElRD → 相等则 m=1 → work(n3) 提示成功。反向 XOR_decrypt("EoPAoY62@ElRD") 即得能注册通过的内容 201608Am!2333,套用 xman{} 即最终 flag。

# 1. 建立隔离 venv(managed python)
VENV=~/.workbuddy/binaries/python/envs/default
python3 -m venv "$VENV"
"$VENV/bin/pip" install capstone pyelftools

# 2. 关键命令
jadx xman.apk            # Java 层反编译
file / strings / nm / objdump   # 原生库静态信息
python (capstone + pyelftools)  # 精确的 Thumb-2 反汇编
$ file libmyjni.so xman.apk
libmyjni.so: ELF 32-bit LSB shared object, ARM, EABI5 version 1 (SYSV), dynamically linked, stripped
xman.apk:     Zip archive data (Android package)
$ strings -n 4 libmyjni.so
...
JNI_OnLoad
g_env
native_class
getValue / setValue / callWork
fopen / fread / fputs / strcmp / strlen
/sdcard/reg.dat
W3_arE_whO_we_ARE
EoPAoY62@ElRD
com.gdufs.xman
输入即是flag,格式为xman{……}!
打开文件失败
$ nm -D libmyjni.so
00001320 T getValue
00001364 T setValue
000013b0 T n1
000011f8 T n2
00001444 T callWork
000014cc T n3
00001508 T JNI_OnLoad
$ jadx -d jadx_out -j 4 xman.apk
public class MyApp extends Application {
    public static int m;                  // 0=未注册 1=已注册 其他=状态异常
    public static native void initSN();
    public static native void saveSN(String sn);
    public static native void work();

    @Override
    public void onCreate() {
        super.onCreate();
        initSN();                         // 启动时读取并校验注册状态
    }
}
// 按钮点击逻辑(整理后)
if (MyApp.m == 0) {
    // 未注册 → 跳转到输入注册码界面
    startActivity(new Intent(this, RegActivity.class));
} else {
    work();   // 已注册 → 调用原生 work(),结果通过 MainActivity.work(String) 回调显示
}
// 原生 work() 内部会回调 MainActivity.work(String result)
// 用户在编辑框输入 sn,点确定:
MyApp.saveSN(et.getText().toString());   // 交给原生 saveSN 加密保存
// 然后杀掉进程(下次启动由 initSN 重新判定)
$ objdump -d libmyjni.so | head
000011f8 <n2>:
    e92d 6803   push    {r0, r1, r11, lr}   # 这条在 ARM 模式下是错的
from elftools.elf.elffile import ELFFile
from capstone import Cs, CS_ARCH_ARM, CS_MODE_THUMB

elf = ELFFile(open("libmyjni.so", "rb"))
text = elf.get_section_by_name(".text")
code = text.data()
base = text["sh_addr"]
md = Cs(CS_ARCH_ARM, CS_MODE_THUMB)

# 按符号表给出的函数边界逐函数反汇编(避免整段错位)
for name, addr, end in [("n2",0x11f8,0x13b0), ("n1",0x13b0,0x1444),
                         ("n3",0x14cc,0x1508), ("JNI_OnLoad",0x1508,0x1600)]:
    print(f"===== {name} =====")
    for insn in md.disasm(code[addr-base:end-base], addr):
        print(f"{insn.address:#08x}: {insn.mnemonic} {insn.op_str}")
000011f8: push.w  {r4, r5, r6, r7, r8, sb, sl, lr}
000011fc: mov     r6, r0
000011fe: ldr     r4, [pc, #0x108]
00001200: sub     sp, #0x18
00001202: ldr     r0, [pc, #0x108]
...
0000125e: mov.w   r4, #0x7e0          # ← 关键:运行计数器 r4 初值 0x7e0
...
0000127e: movs    r1, #3
00001280: blx     #0x16c8            # 调用一个取模运算 (i % 3)
00001284: cmp     r1, #1
00001286: bne     #0x12a8
...                              # i%3 == 1 分支
0000128a: adds    r4, #5
0000128c: ands    r3, r4
...
000012a8: cmp     r1, #2
000012aa: bne     #0x12c0
...                              # i%3 == 2 分支
000012ac: adds    r0, r4, #7
...
000012c0: adds    r0, r4, #3          # i%3 == 0 分支
...
000012d2: ldrb    r3, [r5]           # 取出明文第 i 字节
000012d6: adds    r5, #1
000012d8: eors    r3, r2             # ← 与密钥字节 XOR
000012da: strb    r3, [r5, #-0x1]    # 存回(密文)
from elftools.elf.elffile import ELFFile
elf = ELFFile(open("libmyjni.so", "rb"))
data = open("libmyjni.so", "rb").read()

# JNINativeMethod 数组(3 项,每项 12 字节)
base = 0x4004
arr = data[base:base+3*12]
for k in range(3):
    e = arr[k*12:k*12+12]
    name_p = int.from_bytes(e[0:4], "little")   # 方法名 vaddr
    sig_p  = int.from_bytes(e[4:8], "little")   # 签名 vaddr
    fn     = int.from_bytes(e[8:12], "little")  # 原生函数 vaddr (+1 表示 Thumb)
    # 通过 PT_LOAD 把 vaddr 换算成文件偏移,读出字符串
    ...
    print(name, fn)
00001410: ldr   r1, [pc, #0x2c]   ; 字面池 → 目标字符串指针
00001412: strb.w r8, [r5, r7]     ; 在缓冲区末尾补 '\0'
00001416: mov   r0, r5
00001418: add   r1, pc
0000141a: blx   #0x1168           ; ← 调用 strcmp(content, TARGET)
0000141e: cbnz  r0, #0x1426       ; 不相等 → 跳 1426
00001420: mov   r0, r6
00001422: movs  r1, #1            ; 相等 → setValue(obj, 1)  ← 注册成功
00001424: b     #0x142a
00001426: mov   r0, r6
00001428: mov   r1, r8            ; 不相等 → setValue(obj, 0)
0000142a: bl    #0x1364           ; setValue
000014cc: bl #0x13b0        ; 先调 initSN 刷新 m
000014d4: bl #0x1320        ; getValue(obj) 读取 m
000014da: cbnz r0, #0x14e4  ; m != 0
000014dc: ... string '还不行呢!'        (m==0:未注册)
000014e4: cmp r0, #1
000014ea: ... string '输入即是flag,格式为xman{……}!'  (m==1:提示 flag 格式)
000014f0: ... string '状态不太对。。'     (其他:异常)
struct = b"W3_arE_whO_we_ARE\x00"
def key_index(i, r4):
    rem = i % 3
    if   rem == 1: r4 = (r4 + 5) & 0xF; idx = r4 + 1
    elif rem == 2: r4 = (r4 + 7) % 0xF; idx = r4 + 2
    else:          r4 = (r4 + 3) % 0xD; idx = r4 + 3
    return idx, r4

def xor(data):
    out = bytearray(len(data)); r4 = 0x7e0
    for i in range(len(data)):
        idx, r4 = key_index(i, r4)
        out[i] = data[i] ^ struct[idx]
    return bytes(out)

serial = b"201608Am!2333"
print(xor(serial))            # -> b'EoPAoY62@ElRD'
print(xor(serial) == b"EoPAoY62@ElRD")   # True

传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!!

上传的附件:
收藏
点赞 ・0
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回