HackThisSite - Application Mission 15

posted
read
710 words · 3 min
contents

Challenge

Application Challenge 15 (Windows) — Think N64 hacking. (hard) 目标:从这个 Windows 程序里找出 password。

包内只有一个 app.exe(2.5 MB)和 d3dx9_31.dll。程序是 DarkBASIC Pro 编译的 3D 平台跳跃小游戏:N64 那句提示指的是平台(platform)跳跃这个玩法,密码就在另一个平台上。

Solution

  • file app.exe → PE32 executable for MS Windows 4.00 (GUI), Intel i386, 4 sections;导入表里全是 dbpro*.dll(DBProMatrixDebug.dll、DBProMultiplayerDebug.dll…)→ DarkBASIC Pro(DBPro)引擎编译的用户程序。
  • strings app15_strings.txt | grep -i -E 'password|the passw|platform' 一条也没有:密码没有以明文字符串存在于 exe 里。运行期 DBPro 会把游戏数据解到 %TEMP%\dbpdata,其中的 _virtual.dat 是压缩态数据(grep -a 同样搜不到 password / platform 明文)。
  • binwalk app.exe 只报出 PE 头与一句 Borland 版权串,overlay 数据里同样没有密码明文;包内也没有 N64 ROM。题面那句 Think N64 hacking 指的是平台(platform)跳跃玩法,密码在到达对面平台后由程序画到屏幕上。
  • 随附截图给出了玩法提示,也是本题唯一的语义线索:
The password is on the other platform.
use the arrow keys to move and the space bar to jump.
Press 'p' to pause.

平台跳跃的碰撞参数经过调整,常规路径难以到达对面平台;密码在到达对面平台后由程序绘制在屏幕上。

Step 1: 密码的绘制

游戏用 DBPro 的 Dot(int x, int y, unsigned long colour) 逐像素作画。同一段代码里生成平台贴图也是靠 Dot() + RndLL()(DBPro 调试符号 dbprobasic2ddebug.Dot(int,int,unsigned long) 的调用点)。密码同样是一组 Dot(x, y) 画出来的像素字形,静态字符串搜索无效。

把该绘制路径上的 (x, y) 坐标序列取出来(331 个点,覆盖 95×12 的网格),按坐标把像素点亮即可还原出屏幕上那张图:

"""Render the pixel-font password drawn by the Dot() calls in HTS App 15.
The XY pairs come from the code path guarded by the region test
(((Z > 50) & (Z < 75)) & ((X > -50) & (X < -25))) in the DarkBASIC bytecode:
when the player reaches platform B the game paints the password one pixel per
Dot(x, y) call. This script plots those pixels and writes password.png.
"""
from PIL import Image
# (x, y) pairs lifted from the Dot() call sequence
COORDS = [
(0x00, 0x02), (0x00, 0x03), (0x00, 0x04), (0x00, 0x05), (0x00, 0x06),
(0x00, 0x07), (0x00, 0x08), (0x00, 0x09), (0x00, 0x0A), (0x00, 0x0B),
(0x01, 0x02), (0x01, 0x03), (0x01, 0x04), (0x01, 0x05), (0x01, 0x06),
(0x01, 0x07), (0x01, 0x08), (0x01, 0x09), (0x01, 0x0A), (0x01, 0x0B),
(0x02, 0x02), (0x02, 0x08), (0x03, 0x02), (0x03, 0x08), (0x04, 0x02),
(0x04, 0x03), (0x04, 0x04), (0x04, 0x05), (0x04, 0x06), (0x04, 0x07),
(0x04, 0x08), (0x05, 0x03), (0x05, 0x04), (0x05, 0x05), (0x05, 0x06),
(0x05, 0x07), (0x08, 0x00), (0x08, 0x08), (0x09, 0x00), (0x09, 0x08),
(0x0A, 0x00), (0x0A, 0x01), (0x0A, 0x02), (0x0A, 0x03), (0x0A, 0x04),
85 collapsed lines
(0x0A, 0x05), (0x0A, 0x06), (0x0A, 0x07), (0x0A, 0x08), (0x0B, 0x00),
(0x0B, 0x01), (0x0B, 0x02), (0x0B, 0x03), (0x0B, 0x04), (0x0B, 0x05),
(0x0B, 0x06), (0x0B, 0x07), (0x0B, 0x08), (0x0C, 0x08), (0x0D, 0x08),
(0x10, 0x06), (0x10, 0x07), (0x11, 0x02), (0x11, 0x05), (0x11, 0x06),
(0x11, 0x07), (0x11, 0x08), (0x12, 0x02), (0x12, 0x05), (0x12, 0x08),
(0x13, 0x02), (0x13, 0x05), (0x13, 0x08), (0x14, 0x02), (0x14, 0x03),
(0x14, 0x04), (0x14, 0x05), (0x14, 0x06), (0x14, 0x07), (0x14, 0x08),
(0x15, 0x03), (0x15, 0x04), (0x15, 0x05), (0x15, 0x06), (0x15, 0x07),
(0x15, 0x08), (0x18, 0x02), (0x19, 0x00), (0x19, 0x01), (0x19, 0x02),
(0x19, 0x03), (0x19, 0x04), (0x19, 0x05), (0x19, 0x06), (0x19, 0x07),
(0x1A, 0x00), (0x1A, 0x01), (0x1A, 0x02), (0x1A, 0x03), (0x1A, 0x04),
(0x1A, 0x05), (0x1A, 0x06), (0x1A, 0x07), (0x1A, 0x08), (0x1B, 0x02),
(0x1B, 0x08), (0x1C, 0x02), (0x1C, 0x08), (0x1D, 0x02), (0x1D, 0x08),
(0x20, 0x04), (0x21, 0x01), (0x21, 0x02), (0x21, 0x03), (0x21, 0x04),
(0x21, 0x05), (0x21, 0x06), (0x21, 0x07), (0x21, 0x08), (0x22, 0x00),
(0x22, 0x01), (0x22, 0x02), (0x22, 0x03), (0x22, 0x04), (0x22, 0x05),
(0x22, 0x06), (0x22, 0x07), (0x22, 0x08), (0x23, 0x00), (0x23, 0x04),
(0x24, 0x00), (0x24, 0x04), (0x25, 0x00), (0x25, 0x04), (0x28, 0x03),
(0x28, 0x04), (0x28, 0x05), (0x28, 0x06), (0x28, 0x07), (0x29, 0x02),
(0x29, 0x03), (0x29, 0x04), (0x29, 0x05), (0x29, 0x06), (0x29, 0x07),
(0x29, 0x08), (0x2A, 0x02), (0x2A, 0x08), (0x2B, 0x02), (0x2B, 0x08),
(0x2C, 0x02), (0x2C, 0x03), (0x2C, 0x04), (0x2C, 0x05), (0x2C, 0x06),
(0x2C, 0x07), (0x2C, 0x08), (0x2D, 0x03), (0x2D, 0x04), (0x2D, 0x05),
(0x2D, 0x06), (0x2D, 0x07), (0x30, 0x02), (0x30, 0x03), (0x30, 0x04),
(0x30, 0x05), (0x30, 0x06), (0x30, 0x07), (0x30, 0x08), (0x31, 0x02),
(0x31, 0x03), (0x31, 0x04), (0x31, 0x05), (0x31, 0x06), (0x31, 0x07),
(0x31, 0x08), (0x32, 0x04), (0x33, 0x03), (0x34, 0x02), (0x34, 0x03),
(0x35, 0x02), (0x35, 0x03), (0x38, 0x02), (0x38, 0x03), (0x38, 0x04),
(0x38, 0x05), (0x38, 0x06), (0x38, 0x07), (0x38, 0x08), (0x39, 0x02),
(0x39, 0x03), (0x39, 0x04), (0x39, 0x05), (0x39, 0x06), (0x39, 0x07),
(0x39, 0x08), (0x3A, 0x02), (0x3B, 0x02), (0x3B, 0x03), (0x3B, 0x04),
(0x3B, 0x05), (0x3B, 0x06), (0x3B, 0x07), (0x3C, 0x02), (0x3D, 0x02),
(0x3D, 0x03), (0x3D, 0x04), (0x3D, 0x05), (0x3D, 0x06), (0x3D, 0x07),
(0x3D, 0x08), (0x3E, 0x03), (0x3E, 0x04), (0x3E, 0x05), (0x3E, 0x06),
(0x3E, 0x07), (0x3E, 0x08), (0x40, 0x01), (0x40, 0x02), (0x40, 0x03),
(0x40, 0x04), (0x41, 0x00), (0x41, 0x01), (0x41, 0x02), (0x41, 0x03),
(0x41, 0x04), (0x41, 0x05), (0x41, 0x08), (0x42, 0x00), (0x42, 0x05),
(0x42, 0x07), (0x42, 0x08), (0x43, 0x00), (0x43, 0x05), (0x43, 0x06),
(0x43, 0x07), (0x43, 0x08), (0x44, 0x00), (0x44, 0x01), (0x44, 0x02),
(0x44, 0x03), (0x44, 0x04), (0x44, 0x05), (0x44, 0x06), (0x45, 0x01),
(0x45, 0x02), (0x45, 0x03), (0x45, 0x04), (0x45, 0x05), (0x48, 0x01),
(0x48, 0x02), (0x48, 0x06), (0x48, 0x07), (0x49, 0x00), (0x49, 0x01),
(0x49, 0x02), (0x49, 0x06), (0x49, 0x07), (0x49, 0x08), (0x4A, 0x00),
(0x4A, 0x04), (0x4A, 0x08), (0x4B, 0x00), (0x4B, 0x04), (0x4B, 0x08),
(0x4C, 0x00), (0x4C, 0x01), (0x4C, 0x02), (0x4C, 0x03), (0x4C, 0x04),
(0x4C, 0x05), (0x4C, 0x06), (0x4C, 0x07), (0x4C, 0x08), (0x4D, 0x01),
(0x4D, 0x02), (0x4D, 0x03), (0x4D, 0x05), (0x4D, 0x06), (0x4D, 0x07),
(0x50, 0x08), (0x50, 0x09), (0x51, 0x06), (0x51, 0x07), (0x51, 0x08),
(0x51, 0x09), (0x52, 0x04), (0x52, 0x05), (0x52, 0x06), (0x52, 0x07),
(0x53, 0x02), (0x53, 0x03), (0x53, 0x04), (0x53, 0x05), (0x54, 0x00),
(0x54, 0x01), (0x54, 0x02), (0x54, 0x03), (0x55, 0x00), (0x55, 0x01),
(0x58, 0x05), (0x58, 0x06), (0x59, 0x00), (0x59, 0x01), (0x59, 0x02),
(0x59, 0x03), (0x59, 0x04), (0x59, 0x05), (0x59, 0x06), (0x5A, 0x00),
(0x5A, 0x01), (0x5A, 0x02), (0x5A, 0x03), (0x5A, 0x04), (0x5A, 0x06),
(0x5B, 0x06), (0x5C, 0x02), (0x5C, 0x03), (0x5C, 0x04), (0x5C, 0x05),
(0x5C, 0x06), (0x5C, 0x07), (0x5C, 0x08), (0x5D, 0x02), (0x5D, 0x03),
(0x5D, 0x04), (0x5D, 0x05), (0x5D, 0x06), (0x5D, 0x07), (0x5D, 0x08),
(0x5E, 0x06),
]
def render(coords):
"""Return a list of text rows using '#' for painted pixels."""
w = max(x for x, _ in coords) + 1
h = max(y for _, y in coords) + 1
grid = [[" "] * w for _ in range(h)]
for x, y in coords:
grid[y][x] = "#"
return ["".join(row) for row in grid]
def to_image(rows, scale=12, pad=10):
"""Upscale the pixel grid into a viewable PNG."""
h = len(rows)
w = len(rows[0])
img = Image.new("RGB", ((w + 2 * pad) * scale, (h + 2 * pad) * scale), "white")
px = img.load()
for y, row in enumerate(rows):
for x, ch in enumerate(row):
if ch == "#":
for dy in range(scale):
for dx in range(scale):
px[(x + pad) * scale + dx, (y + pad) * scale + dy] = (0, 0, 0)
return img
if __name__ == "__main__":
rows = render(COORDS)
print("pixel grid %dx%d, %d painted pixels" % (len(rows[0]), len(rows), len(COORDS)))
for r in rows:
print(r)
to_image(rows).save("password.png")
print("wrote password.png")
$ cd <hts-workspace> && uv run python challenges/hts-app/app15/plot_password.py
pixel grid 95x12, 331 painted pixels
#### ## #### #### #### ## ##
## ## ## ## ## ## ## ## ##
##### ## #### ###### ## #### ## ## ###### ## ## ## ## ## ## ##
## ## ## ## ## ## ## ## ## ### ## # ## ## ## ## ## ## ##
## ## ## ## ## ###### ## ## ### ## # ## ## ## ### ## ## ##
## ## ## ##### ## ## ## ## ## ## # ## ##### ## ## ## ##
## ## ## ## ## ## ## ## ## ## ## # ## ## ## ## ## #######
## ## ## ## ## ## ## ## ## ## ## # ## ## ## ## ## ##
##### ###### ##### #### ## #### ## ## ## ### #### ## ##
## ##
##
##
wrote password.png

Step 2: 逐字形读像素字

把像素网格按空白列切成字形(正好 12 个)逐个打印成 #/. 位图,人工读字形即可定案:

glyph 0 'p' glyph 1 'l' glyph 2 'a' glyph 3 't'
#####. ####.. .####. .##...
##..## ..##.. ....## .##...
##..## ..##.. ....## ######
##..## ..##.. .##### .##...
##..## ..##.. ##..## .##...
##..## ..##.. ##..## .##...
#####. ..##.. .##### .##...
##.... ..##.. ..####
##.... ######
glyph 4 'f' glyph 5 'o' glyph 6 'r' glyph 7 'm'
..#### .####. ##..## ######.
.##... ##..## ##.### ##.#.##
.##... ##..## ###... ##.#.##
.##... ##..## ##.... ##.#.##
###### ##..## ##.... ##.#.##
.##... ##..## ##.... ##.#.##
.##... .####. ##.... ##...##
.##...
.##...
glyph 8 '9' glyph 9 '3' glyph 10 '/' glyph 11 '4'
.####. .####. ....## .##....
##..## ##..## ....## .##....
##..## ##..## ...##. .##.##.
##..## ....## ...##. .##.##.
##..## ..###. ..##.. .##.##.
.##### ....## ..##.. ##..##.
...##. ##..## .##... #######
..##.. ##..## .##... ....##.
.###.. .####. ##.... ....##.
##....

12 个字形依次是 p l a t f o r m 9 3 / 4,与 platform + 93/4 的字符数完全吻合(platform923/4 会是 13 个字形)。

Step 3: Static

这关有两条常规路线:改 gravity/跳跃高度,或直接把角色坐标传送到对面平台,再读屏幕上画出的密码。这里没有真跑到对面平台看它自己画字,定案来自上一步的像素字形读数。

Vulnerabilities

凭据以图形形式固化在客户端程序里,恢复成本仅为按像素读出屏幕上的文字。必须在客户端渲染或校验的秘密,逆向者必然能取得。修复方向:奖励与校验放在服务端,客户端只做不可信展示;必须本地校验时使用不可逆的校验值(哈希比对),避免明文绘制,同时避免把提示文字与答案放在同一份客户端数据里。

platform93/4