解析 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,没有明文密码。
"""Recover the password embedded in HackThisSite App 10 (app10win.exe). app10win.exe is a VB6 native-code program. Its event dispatch table (objdump address 0x404904) routes event 0x3B to the handler at 0x4049E0 -- an event no control on the form ever fires ("mystery event"). That handler builds a message box the hard way: every literal character is an immediate `push 0xNN`, immediately converted into a one-character BSTR VARIANT by rtcVarBstrFromAnsi (MSVBVM60.DLL ordinal 608, loaded into edi from the IAT slot ds:0x40104c), and the whole batch is finally concatenated by __vbaVarCat (IAT slot ds:0x401054) and handed to rtcMsgBox (MSVBVM60.DLL ordinal 595, IAT slot ds:0x401018). The run of immediates between the two IAT loads is 38 characters long, but it is NOT a single string. The handler contains no string literal at all -- its only text immediate is the SEH frame pointer 0x4010C6 -- so both the body and the caption of the box are built character by character. The reference run of the patched program shows MsgBox "The Password Is: HiddenSecrets", vbInformation, "Correct!" so the first 30 immediates are the box body and the last 8 ("Correct!") are its caption; only the 13 characters after the "The Password Is: " marker are the password HTS wants. Joining all 38 and stripping the marker yields the tempting but wrong "HiddenSecretsCorrect!" (the site answers "invalid password" for it). Verified against: sha256 cb649beb0fd43fa83c534f58b7444c06b6fbce0db1e7322f2acb4ca9b039f300 """
defdisassemble(path): """Return the objdump disassembly as a list of (address, mnemonic) pairs.""" out = subprocess.run( ["objdump", "-d", "-M", "intel", path], capture_output=True, text=True, check=True, ).stdout insns = [] for line in out.splitlines(): m = re.match(r"\s*([0-9a-f]+):\t[0-9a-f ]+\t(\S.*)", line) if m: insns.append((int(m.group(1), 16), m.group(2).strip())) return insns
deffind_message(insns): """Collect the immediate bytes pushed between the two IAT loads.""" start = end = None for addr, text in insns: ifnot (FUNC_START <= addr <= FUNC_END): continue if start isNoneand re.search(r"mov\s+edi,DWORD PTR ds:0x%x" % RNT_FROM_ANSI, text): start = addr elif start isnotNoneand re.search(r"mov\s+edi,DWORD PTR ds:0x%x" % VAR_CAT, text): end = addr break
if start isNoneor end isNone: raise SystemExit("character-building sequence not found")
chars = [] for addr, text in insns: ifnot (start < addr < end): continue m = re.fullmatch(r"push\s+0x([0-9a-f]+)", text) if m: chars.append(int(m.group(1), 16)) return start, end, bytes(chars).decode("latin1")
defsplit_body_caption(chars): """Split the 38 embedded characters into message-box body and caption. Both halves are built by the same `push imm8` + rtcVarBstrFromAnsi chain; the boundary is the trailing literal caption ("Correct!"), so the body is everything before it. """ ifnot chars.endswith(CAPTION): raise SystemExit("unexpected tail: %r" % chars[-len(CAPTION):]) return chars[: -len(CAPTION)], CAPTION
密码作为一个编译期字面量的拼接结果被留在客户端可执行文件里,恢复成本只是把分派表读出来、把立即数字节连起来。客户端程序无法保存秘密:只要校验或展示发生在客户端,逆向者就能拿到。密码应该放在服务端校验,客户端只做不可信输入;如果必须本地比对,也要把密码哈希化(不可逆),而不是让字符串的每个字节都能在代码里被逐个读出来。这里的另一个反面教材是把真正的处理逻辑挂在了一个永远不会被触发的事件上,却把提示信息(404 object(pwd); not found)留在了错误分支里,等于给逆向者指路。