HackThisSite - Application Mission 10
Challenge
Application Challenge 10 (Windows) — Find the Password. (medium) 目标:从这个 Windows 程序里找出 password。
包内只有一个 app10win.exe(32 KB,PE32 GUI)。它是个 VB6
程序(工程名 ch16Project1,路径
C:\Program Files\Microsoft Visual Studio\VB98\Projects\Challenge\ch16Project1.vbp,strings -el
里还留着原作者署名
HTS Application Challenge Programmed by Magic.),界面上有个
Proceed 按钮,按下去只会弹一个 Error-266
警告框(Error: 404 object(pwd); not found!)。真正的答案在一个没有任何控件会触发的
event handler 里被逐字符拼出来,再用一个消息框显示。
Solution
file app10win.exe→PE32 executable for MS Windows 4.00 (GUI), Intel i386, 3 sections;导入表只有MSVBVM60.DLL,而且大部分是按 ordinal 导入(0000104c 608 <none>、00001018 595 <none>)→ VB6 native-code 编译产物。查 msvbvm60 的 ordinal 表(本机在/usr/share/retdec/support/ordinals/x86/msvbvm60.ord,PE 库的ordlookup也带同一份):100 = ThunRTMain、595 = rtcMsgBox、608 = rtcVarBstrFromAnsi、0x401054 = __vbaVarCat。- 解析 PE
导入表逐槽核对过:
0x401018 -> ord 595、0x40104c -> ord 608、0x401054 -> __vbaVarCat(按名字导入)、0x401008 -> __vbaFreeVarList、0x401074 -> __vbaVarDup,与后面反汇编里各调用点的角色严格对应。 strings -el(VB6 的字符串资源是 UTF-16LE,普通strings看不到)只有ch16Project1、HTS Application Challenge Programmed by Magic.、Error: 404 object(pwd); not found!、Error-266、Untitled-1,没有明文密码。binwalk在 exe 里报出一个内嵌 JPEG(file offset0x1272)。carve 出来(0x1272–0x36aa)是 233×33 的装饰图,里面没有密码文本。- 字符串和图片都没收获,于是转去看 VB6 的 event 分派表。
Step 1: event handler 分派表
VB6 native code 把每个 form / control 的事件编译成表里的一条
entry,形状统一是 sub dword ptr [esp+4], <event id>
紧跟
jmp <handler>(运行时把事件号压栈,分派器减掉基址后跳转)。app10
的表在 0x404904,总共三条:
1 | $ objdump -d -M intel app10win.exe | sed -n '/404904:/,/404928:/p' |
(objdump 不打印注释;上面这六行的 sub/jmp
是原始输出,下面这张对照表里的事件含义来自各 handler 的代码。)
1 | event 0x3b -> sub 0x3b / jmp 0x4049e0 ; "mystery event",form 上没有控件触发它 |
三条 entry 的 event id 与 jmp 目标都在本地 objdump
里逐字节核对过(例如 404926 处的
e9 d5 0b 00 00 就是 jmp 0x405500)。
Step 2: Proceed
按钮无效
0x405500 整个函数只做一件事:把两个 UTF-16 常量装进
VARIANT,然后弹消息框(; 后的注释为本文所加,objdump
本身不打印注释):
1 | 405542: mov edi,DWORD PTR ds:0x401074 ; __vbaVarDup |
以上是常量装载(第一条把 __vbaVarDup 装进
edi,后两条把两个宽字符常量的地址写进 VARIANT
槽位);接下来是 rtcMsgBox 的参数与调用:
1 | 4055aa: push 0x30 ; 48 = vbExclamation |
0x4045a8 和 0x40455c 指向 .text
里的宽字符常量,直接把文件解出来看就是这两个字符串:
1 | $ xxd -s 0x45a8 -l 20 app10win.exe |
也就是
rtcMsgBox("Error: 404 object(pwd); not found!", vbExclamation, "Error-266")。48
是警告图标,Error-266 是
caption。函数体里再没有别的分支,调用后直接
ret。错误文本就是提示:404 object(pwd); not found,即正常路径没有接到密码对象;密码在另一个
handler 中。
Step 3: mystery event 拼串
0x4049E0(event 0x3B,form
上没有任何控件会触发它)是个很长的函数,核心是一个重复 38
次的模式(同样,; 后的注释为本文所加):
1 | 404b9e: mov edi,DWORD PTR ds:0x40104c ; ord 608 = rtcVarBstrFromAnsi |
余下 35 个字符是完全相同的 push <imm8> + 目标槽位
+ call edi 套路,最后一个字符紧接在
__vbaVarCat 的 IAT 装载之前:
1 | 404d71: push 0x21 ; '!' |
__vbaVarCat 的参数装配(若干 lea /
push 的槽位地址)不再逐条贴出,拼接结果直接交给
rtcMsgBox:
1 | 40500d: push eax ; 拼接结果 |
每个字面字符都是一个 push <imm8>
立即数,紧跟一个栈上 VARIANT 槽位的地址,再
call edi(rtcVarBstrFromAnsi)把它变成一个单字符
BSTR variant;38 个 variant 最后由 __vbaVarCat
串成一整串,交给 rtcMsgBox 显示。所以拼出来的 38
个字符就散落在 mov edi, [0x40104c] 和
mov edi, [0x401054] 这两条 IAT 装载之间的所有
push <imm8> 里。
1 | """Recover the password embedded in HackThisSite App 10 (app10win.exe). |
1 | $ cd <hts-workspace>/challenges/hts-app/app10 && python3 extract_password.py |
Step 4: 38 个立即数 = 正文 30 + 标题 8
把 38 个字符直接连起来是一句完整的英文
The Password Is: HiddenSecretsCorrect!,容易把
The Password Is:
之后的整截(HiddenSecretsCorrect!)当作密码。正确的切法来自消息框的两个字段:
0x4049E0这个 handler 里一个字符串字面量都没有。全函数唯一的 4 字节立即数地址是 SEH 帧指针push 0x4010c6(0x4049e6)和push 0x405443(异常恢复块),rtcMsgBox的其余参数槽是VT_ERROR / 0x80020004的缺省参数变体。也就是说正文和标题都是逐字符拼出来的,38 个立即数必须被切成两段。- 正文(前 30
个立即数):
The Password Is: HiddenSecrets - 标题(后 8 个立即数):
Correct! - 分界在第一处
__vbaVarCat(0x404dc1):它的参数是'C'([ebp-0x3d4])和'o'([ebp-0x3e4]),即标题的前两个字符;消息框的按钮位是push 0x40(0x404e2d,64 =vbInformation)。
所以消息框等价于
rtcMsgBox("The Password Is: HiddenSecrets", vbInformation, "Correct!"),密码就是正文里
The Password Is: 之后的 13 个字符(见文末 spoiler)。
Step 5: 打补丁动态复现
这关的常规玩法是:把 0x404926 那条 jmp
的目标从 0x405500 改成 0x4049E0,运行后点
Proceed,mystery event
就被接上了,消息框会把正文和标题打出来。补丁就是一个 5 字节的 rel32
改动(目标地址 0x4049E0 相对下一条指令
0x40492B 的偏移是 0xB5;文件偏移 = VA −
0x400000):
1 | file offset 0x4926: e9 d5 0b 00 00 -> e9 b5 00 00 00 |
两个状态下消息框的每个字段都由反汇编给出(没有运行打补丁后的程序):
1 | 未打补丁(0x405500):标题栏 Error-266 / 正文 Error: 404 object(pwd); not found! / 图标 vbExclamation |
前者与 Step 2 里 0x405500
的静态分析完全一致;后者既印证了 Step 3 脚本抽出的字符,也把正文 /
标题两个字段切得清清楚楚,这是本题确定答案的依据。
Step 6: 站点校验端点
把候选提交到站点校验端点(applevelup.php,字段
level + password):
1 | $ curl -s -b "HackThisSite=<mission-cookie>" \ |
对照记录:HiddenSecretsCorrect!(正文+标题连写)回
invalid password;HiddenSecrets(正文冒号后内容)被接受。答案以站点实际接受值为准。
Vulnerabilities
密码作为一个编译期字面量的拼接结果被留在客户端可执行文件里,恢复成本只是把分派表读出来、把立即数字节连起来。客户端程序无法保存秘密:只要校验或展示发生在客户端,逆向者就能拿到。密码应该放在服务端校验,客户端只做不可信输入;如果必须本地比对,也要把密码哈希化(不可逆),而不是让字符串的每个字节都能在代码里被逐个读出来。这里的另一个反面教材是把真正的处理逻辑挂在了一个永远不会被触发的事件上,却把提示信息(404 object(pwd); not found)留在了错误分支里,等于给逆向者指路。