HackThisSite - Application Mission 8

posted
read
641 words · 2 min
contents

Challenge

Application Challenge 8 — Find the 6 digit code. (medium)

找出 6 位数字:程序是个 VB6 写的数字键盘小程序,输入正确的 6 位码后会回显站点密码。

Solution

  • app8win.exe 是 VB6 原生编译程序:导入表只有 MSVBVM60.DLL,且绝大多数条目是 ordinal(ordinal 632 = rtcMidCharVar,即 VB6 的 Mid$())。
  • 密码/数字都不在字符串里:ASCII 与 UTF-16 搜 password、185862 等全部 0 命中;有效信息全在 .text 里的 UTF-16 BSTR 字面量和一串 push imm8 立即数里。VB6 的字符串是 UTF-16LE,必须 strings -el 或直接按 BSTR 结构读。
  • 先做纯静态:解析 PE → 读 BSTR 字面量 → 把校验函数里所有 rtcMidCharVar 调用点的立即数抽出来。静态结果先给出候选,最终结论通过运行程序读取消息框确认。

VB6 的 BSTR 字面量结构是 长度 dword + UTF-16 数据,校验值是用 Mid$(源串, n, 1) 逐个字符拼出来的,所以只要拿到源串和那串下标就能还原所有字符串:

#!/usr/bin/env python3
"""Static recon script for app8win.exe (HTS Application Mission 8).
The sample is a Visual Basic 6 program compiled to native x86 code. This
script parses the PE, lists MSVBVM60.DLL imports (name + ordinal), locates the
UTF-16 BSTR string literals VB6 stores in .text, and recovers the immediates
handed to the rtcMidCharVar calls (MSVBVM60.DLL ordinal 632, the runtime
implementation of VB6's Mid$()) inside the digit-check routine.
Run from the directory that holds app8/win/app8win.exe.
"""
import re
import struct
PATH = "app8/win/app8win.exe"
IMAGE_BASE = 0x400000
CHECK_FN = (0x406D40, 0x407740) # the button/verification routine
RTC_MIDCHARVAR_IAT = 0x401040 # IAT slot, called as "call ebx"
def load():
114 collapsed lines
data = open(PATH, "rb").read()
pe = struct.unpack_from("<I", data, 0x3C)[0]
nsec = struct.unpack_from("<H", data, pe + 6)[0]
opt_size = struct.unpack_from("<H", data, pe + 20)[0]
secs = []
for i in range(nsec):
off = pe + 24 + opt_size + i * 40
name = data[off:off + 8].rstrip(b"\0").decode()
vsize, vaddr, rawsize, rawptr = struct.unpack_from("<IIII", data, off + 8)
secs.append((name, vaddr, vsize, rawptr, rawsize))
return data, pe, secs
def rva_of(secs, raw):
for name, vaddr, vsize, rawptr, rawsize in secs:
if rawptr <= raw < rawptr + rawsize:
return vaddr + (raw - rawptr)
raise ValueError(hex(raw))
def raw_of(secs, rva):
for name, vaddr, vsize, rawptr, rawsize in secs:
if vaddr <= rva < vaddr + max(vsize, rawsize):
return rawptr + (rva - vaddr)
raise ValueError(hex(rva))
def va_to_raw(secs, va):
return raw_of(secs, va - IMAGE_BASE)
def imports(data, pe, opt_size):
"""Yield (dll, member, iat_va) for every import."""
d = pe + 24
imp_rva, imp_size = struct.unpack_from("<II", data, d + 96 + 8)
i = 0
while True:
ent = raw_of(SECS, imp_rva) + i * 20
oft, _ts, _fc, name_rva, first = struct.unpack_from("<IIIII", data, ent)
if name_rva == 0:
break
noff = raw_of(SECS, name_rva)
dll = data[noff:data.index(b"\0", noff)].decode()
thunk_rva = oft or first
j = 0
while True:
t = struct.unpack_from("<I", data, raw_of(SECS, thunk_rva + j * 4))[0]
if t == 0:
break
if t & 0x80000000: # by ordinal
member = "ordinal %d" % (t & 0xFFFF)
else:
hint_rva = t & 0x7FFFFFFF
hoff = raw_of(SECS, hint_rva)
member = data[hoff + 2:data.index(b"\0", hoff + 2)].decode()
yield dll, member, IMAGE_BASE + first + j * 4
j += 1
i += 1
def bstr(data, secs, va):
"""Read a VB6 string literal: dword length at va-4, UTF-16 data at va."""
ln = struct.unpack_from("<I", data, va_to_raw(secs, va) - 4)[0]
raw = va_to_raw(secs, va)
return data[raw:raw + ln].decode("utf-16-le")
def mid_picks(data, secs):
"""Immediates of the 'push imm8 ; ... ; call ebx' sites where ebx holds the
rtcMidCharVar (ordinal 632) import. Returns [(call_va, imm, dest_ebp_offset)]
in program order, together with the chain boundary (the VarCat concat block).
"""
lo = va_to_raw(secs, CHECK_FN[0])
hi = va_to_raw(secs, CHECK_FN[1])
body = data[lo:hi]
out = []
# The compiler emits "lea eax,[ebp-..] ; push eax ; push <index>" at the head
# of every Mid$() argument block, and the block ends in "call ebx" where ebx
# was loaded from the rtcMidCharVar IAT slot.
for m in re.finditer(b"\x50\x6a(.)", body):
nxt = body.find(b"\xff\xd3", m.end())
if nxt == -1 or nxt - m.end() > 0x60:
continue
if body.find(b"\x50\x6a", m.end()) not in (-1,) and body.find(b"\x50\x6a", m.end()) < nxt:
continue
out.append((CHECK_FN[0] + m.start(), m.group(1)[0]))
return out
data, pe, SECS = load()
print("file %s: %d bytes, %d sections" % (PATH, len(data), len(SECS)))
for name, vaddr, vsize, rawptr, rawsize in SECS:
print(" %-8s VA %#x vsize %#x raw %#x" % (name, vaddr, vsize, rawptr))
print("\nimports:")
for dll, member, iat in imports(data, pe, pe and 224):
print(" %#x %-14s %s" % (iat, dll, member))
LITERALS = {
"keypad legend": 0x4055E0,
"digit 1": 0x405608, "digit 2": 0x405610, "digit 3": 0x405618,
"digit 4": 0x405620, "digit 5": 0x405628, "digit 6": 0x405630,
"digit 7": 0x405638, "digit 8": 0x405640, "digit 9": 0x405648,
"ok message": 0x405650, "ok title": 0x40569C, "separator": 0x405694,
"label caption": 0x40557C,
}
print("\nBSTR literals:")
for what, va in LITERALS.items():
print(" %#08x %-14s %r" % (va, what, bstr(data, SECS, va)))
print("\nrtcMidCharVar (ordinal 632) call sites in the check routine:")
picks = mid_picks(data, SECS)
for va, idx in picks:
print(" %#08x push %d" % (va, idx))
# The routine builds two concatenations out of Mid$() picks: the first one is
# the value the user input is compared against, the second one feeds the
# "Correct! The Magic Number is: " message. Split at the VarCat block.
CONCAT_START = 0x407088
chain1 = [i for va, i in picks if va < CONCAT_START]
chain2 = [i for va, i in picks if va > CONCAT_START]
print("\nMid$() picks compared against the input : %s" % chain1)
print("Mid$() picks used in the success message: %s" % chain2)
for src in ("123456789", "987654321"):
def pick(seq):
return "".join(src[n - 1] for n in seq)
print("\nsource string %r" % src)
print(" check value %s" % pick(chain1))
print(" success message %s-%s" % (pick(chain2[:6]), pick(chain2[6:])))
$ cd <hts-workspace>/challenges/hts-app && uv run python app8_static_dump.py
file app8/win/app8win.exe: 40960 bytes, 4 sections
.text VA 0x1000 vsize 0x8000 raw 0x400
.rdata VA 0x9000 vsize 0x1000 raw 0x8400
.data VA 0xa000 vsize 0x1000 raw 0x8600
BSTR literals:
0x004055e0 keypad legend '123456789' # 运行时先被 rtcStrReverse 翻成 '987654321'
0x00405608 digit 1 '1'
0x00405610 digit 2 '2'
0x00405618 digit 3 '3'
0x00405620 digit 4 '4'
0x00405628 digit 5 '5'
0x00405630 digit 6 '6'
0x00405638 digit 7 '7'
0x00405640 digit 8 '8'
0x00405648 digit 9 '9'
0x00405650 ok message 'Correct! The Magic Number is: '
0x0040569c ok title 'Correct!'
0x00405694 separator '-'
24 collapsed lines
0x0040577c label caption 'HTS Application Challenge Programmed by Magic.'
rtcMidCharVar (ordinal 632) call sites in the check routine:
0x406f28 push 1
0x406f70 push 8
0x406f9d push 5
0x406fd0 push 8
0x407009 push 6
0x407042 push 2
0x407172 push 8
0x4071a3 push 4
0x4071d7 push 6
0x407214 push 6
0x407251 push 9
0x40728e push 4
0x4072cb push 6
0x407308 push 3
0x407345 push 7
0x407382 push 6
0x4073bf push 8
0x4073fc push 3
Mid$() picks compared against the input : [1, 8, 5, 8, 6, 2]
Mid$() picks used in the success message: [8, 4, 6, 6, 9, 4, 6, 3, 7, 6, 8, 3]
source string '123456789' (the raw legend literal as stored)
check value 185862
success message 846694-637683
source string '987654321' (the legend after rtcStrReverse -- the one actually used)
check value 925248
success message 2644-164-73427 # digits 264416473427, '-' inserted after the 4th and 7th digit

校验函数里对 Mid$() 的调用分成两段,中间夹着 __vbaVarCat 拼接块,两段的产物是:

  • 6 位码:由下标 [1, 8, 5, 8, 6, 2] 从键盘图例串按 1-based 下标取出
  • 魔数:由下标 [8, 4, 6, 6, 9, 4, 6, 3, 7, 6, 8, 3] 取出,插入字面量 '-',整段接在 BSTR 模板 'Correct! The Magic Number is: ' 之后

图例字面量在 .data 里存的是 '123456789',运行时会先经 rtcStrReverse(导入表里同时有 rtcStrReverse 和 rtcMidCharVar)翻成 '987654321' 再交给 Mid$(),所以 1→9、8→2、5→5、8→2、6→4、2→8。

也就是说:输入 6 位码后,程序弹出的消息框带出这段魔数。

此处存在一个纯静态无法推出的易错点:如果按下标顺序 = 显示顺序、连字符插在第 6 位之后去推,会得到 264416-473427(12 位数字无误,但连字符位置错误)。__vbaVarCat 的拼接顺序与 Mid$() 调用顺序并不一致,连字符实际落在第 4 位和第 7 位之后。连字符的实际位置只有运行程序并读出消息框才能确认。

提交到站点校验端点被接受:

POST /missions/application/applevelup.php level=8 password=2644-164-73427
-> Congratulations, you have successfully completed application 8!

Vulnerabilities

校验值是运行时用 Mid$() 从图例字符串拼出来的,读出 push 的立即数即可还原。在本地判定的校验无法保守秘密,等同于把答案交给逆向者。修复方向:校验放服务端,客户端只提交不可逆的校验结果;必须本地校验时,不让答案以可还原的形式出现在代码里。

2644-164-73427