1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
| """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) RTC_MIDCHARVAR_IAT = 0x401040
def load(): 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: 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 = [] 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))
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:])))
|