[安全] RCTF 2022 HuoWang 题解
RCTF 2022 HuoWang WP
以下展示的所有反编译代码均以经过重命名恢复符号
明显注意到这里有两个判断,是需要两个条件都为 0 才能通过题目
通过 交叉引用 跟踪修改其的函数
if (FLAG1 != 0 && FLAG2 != 0)
puts("GrandFather Dao, I've made it!(ohhhhh. The flag is RCTF{(md5(your input))}.")
else if (FLAG1 != 0 || FLAG2 != 0)
puts("Mom, I really can't tell the difference!")
else
puts("CTF is fun! Jie Jie Jie")第一段验证
veri1(maze: "* *********************", &input, len_input)FLAG1 由 veri1 函数修改
差不多就是 wasd 然后碰到墙会死,需要由 (Y, X) = (0, 1) 移动到 (Y, X) = (0x16, 0x15)
地图的 maze 是这样的
018d7180 char data_26046848[0x18] = "* *********************", 0
018d7198 char data_26046872[0x18] = "* * * * *", 0
018d71b0 char data_26046896[0x18] = "* * * * *** * *** * * *", 0
018d71c8 char data_26046920[0x18] = "* * * * * * * *", 0
018d71e0 char data_26046944[0x18] = "* * * ********* * *** *", 0
018d71f8 char data_26046968[0x18] = "* * * * * * *", 0
018d7210 char data_26046992[0x18] = "* * ***** * * ***** * *", 0
018d7228 char data_26047016[0x18] = "* * * * * *", 0
018d7240 char data_26047040[0x18] = "*** * * *** * *** *** *", 0
018d7258 char data_26047064[0x18] = "* * * * * * * *", 0
018d7270 char data_26047088[0x18] = "* *** *** * * * *** *", 0
018d7288 char data_26047112[0x18] = "* * * * * * * *", 0
018d72a0 char data_26047136[0x18] = "* * * * *** * ***** * *", 0
018d72b8 char data_26047160[0x18] = "* * * * * *", 0
018d72d0 char data_26047184[0x18] = "* *********** *** * * *", 0
018d72e8 char data_26047208[0x18] = "* * * * * *", 0
018d7300 char data_26047232[0x18] = "* ********* ********* *", 0
018d7318 char data_26047256[0x18] = "* ** *** * * *", 0
018d7330 char data_26047280[0x18] = "* * **** ** ***** * *", 0
018d7348 char data_26047304[0x18] = "* * * * * * *", 0
018d7360 char data_26047328[0x18] = "* * * * * ***** ***** *", 0
018d7378 char data_26047352[0x18] = "* * * *", 0
018d7390 char data_26047376[0x18] = "********************* *", 0
使用 bn 查看可能会没这么整齐,可以使用以下脚本修复
import binaryninja as bn
def change_var_type_of_maze_1(bv: bn.binaryview.BinaryView):
beg_addr = 0x018d7180
data_len = 0x18
end_addr = 0x018d73a8
data_typ = bv.parse_type_string(f"char[{data_len}]")[0]
cur_addr = beg_addr
while cur_addr < end_addr:
raw_name = f"data_{cur_addr}"
bv.define_user_data_var(cur_addr, data_typ, raw_name)
cur_addr += data_len这样我们就能构造路径通过 veri1 的判断了
一般的简单迷宫题,基本上到这里就是直接用 bfs 求出最短路,但是别忘了我们还有 veri2
第二段验证
for (int32_t i = 0; i s<= 6; i += 1)
(&handler)[sx.q(i)] = &(&data_18d73e0)[sx.q(i)]
int64_t* mu
// uc_open(UC_ARCH_ARM64, UC_MODE_ARM, &uc)
int32_t var_14c = uc_open(4, 8, &mu)
int32_t var_14c_1 = uc_mem_map(mu, &__elf_header, 0x200000, 7)
int32_t var_14c_2 = uc_mem_map(mu, 0, 0x100000, 7)
int64_t var_138
__builtin_memset(dest: &var_138, ch: 0, count: 0x14)
int32_t var_14c_3 =
uc_mem_write(mu, &__elf_program_headers[1].offset, &shellcode, 0x7aa8)
int32_t var_14c_4 = uc_mem_write(mu, 0x407b34, &input, sx.q(len_input))
// uc_reg_write(mu, UC_ARM64_REG_X30, &stack_val)
int32_t var_14c_5 = uc_reg_write(mu, 30, &var_158)
int64_t var_170 = 0x2bb
int64_t* var_140
int32_t var_14c_6 = uc_hook_add(mu, &var_140, 2, veri2, 0, 1, 0, 0)
int32_t var_14c_7 = uc_reg_write_wrapper(mu, &__elf_program_headers[3].align:1,
&data_400168:2, 0, 0)
int32_t var_14c_8 = uc_reg_write_wrapper(mu, &data_400168:2,
&__elf_program_headers[2].memory_size, 0, 0)
uc_emu_start(mu)似乎是 unicorn 的模拟执行,模拟了一段 shellcode 的执行,挂了个钩子 veri2 对某个数值进行判断,并以此修改 FLAG2
观察到传入了相同的输入,应该是需要使用一段输入通过两个验证,由于其中一个输入被用于走迷宫,我们可以合理猜测另一个验证程序也是一种走迷宫
好了接下来我不会了,我不会分析这个模拟执行
爆破
由于第一个图的起点和终点分别是图的左上角和右下角,如果要使用相同的路径一起走两个迷宫(碰到墙就会死的情况下)
那么两个图起点到终点的向量会相等,都等于这一条路径的向量
即使第二张图很大,我们也只需要关注用长方形框住起点和终点相对位置的部分,因为如果走出这个部分了,第一个图必然走出地图,会死
我们已经有第一张图了,如果我们的路径不能碰到墙,那么实际上我们要走的图是第一张图墙的集合和第二张图的墙的集合的并集(其实我在说废话)
好的,无论如何,既然已经有了大部分的墙,那可以考虑爆破了
import collections
MAZE = [
"* *********************",
"* * * * *",
"* * * * *** * *** * * *",
"* * * * * * * *",
"* * * ********* * *** *",
"* * * * * * *",
"* * ***** * * ***** * *",
"* * * * * *",
"*** * * *** * *** *** *",
"* * * * * * * *",
"* *** *** * * * *** *",
"* * * * * * * *",
"* * * * *** * ***** * *",
"* * * * * *",
"* *********** *** * * *",
"* * * * * *",
"* ********* ********* *",
"* ** *** * * *",
"* * **** ** ***** * *",
"* * * * * * *",
"* * * * * ***** ***** *",
"* * * *",
"********************* *",
]
START = (0, 1)
END = (22, 21)
HEIGHT = len(MAZE)
WIDTH = len(MAZE[0])
MOVES = {
'w': (-1, 0),
's': (1, 0),
'a': (0, -1),
'd': (0, 1)
}
def solve():
queue = collections.deque([(START[0], START[1], "", {START})])
while queue:
y, x, path, visited = queue.popleft()
if (y, x) == END:
if check(path):
print(f"找到路径: {path}")
return path
else:
continue
for char, (dy, dx) in MOVES.items():
ny, nx = y + dy, x + dx
if 0 <= ny < HEIGHT and 0 <= nx < WIDTH:
if MAZE[ny][nx] == ' ' and (ny, nx) not in visited:
new_visited = visited | {(ny, nx)}
queue.append((ny, nx, path + char, new_visited))
return None
import subprocess
def check(road: str) -> bool:
result = subprocess.run(["./HuoWang"], input=road, capture_output=True, text=True)
output = result.stdout
# print(output)
return output[0] == 'G'
solve()这实际上就是正常的 BFS,不过加了一个终点判断罢了,终点判断运行程序来检验(((
这样既能排除掉第一张图到达终点但是第二张图死掉的路线,也确保了能找到符合条件的最短路径
路径是
sssddwwddssssddddssaassddssaassddddwwwwwwwwddddwwddwwddddssssaassaassaassddddssaassaaaaaassassdddwwddddddssaaaassdddddds
flag 是 RCTF{e1f9e3d166dcec5ecff3a2c5fbdeab3b}
我也不知道 flag 是不是这个,我没看别的 wp,不过路径确实能通过检验,md5 的大小写自己试了
附带的
veri1 反编译结果
uint32_t veri1(uint8_t* maze, char* input, int32_t len_input)
int32_t Y = 0
int32_t X = 1
uint32_t index = 0
uint32_t STEP
while (true)
STEP = index
if (STEP s>= len_input)
if (Y == 0x16 && X == 0x15)
FLAG1 = 1
break
STEP = zx.d(input[sx.q(index)])
if (STEP == 'w') // UP
if (Y == 0)
break
STEP = zx.d(*(sx.q(Y) * 0x18 - 0x18 + maze + sx.q(X)))
if (STEP.b != 0x20)
break
Y -= 1
else
if (STEP s> 'w') // invalid input
sub_13af360(0xffffffff)
noreturn
if (STEP == 's') // DOWN
STEP = zx.d(maze[(sx.q(Y) + 1) * 0x18 + sx.q(X)])
if (STEP.b != ' ') // hit a wall == failure
break
Y += 1
else
if (STEP s> 's') // invalid input
sub_13af360(0xffffffff)
noreturn
if (STEP == 'a') // LEFT
if (X == 0)
break
STEP = zx.d(maze[sx.q(Y) * 0x18 + sx.q(X - 1)])
if (STEP.b != ' ') // hit the wall == failure
break
X -= 1
else
if (STEP != 'd') // invalid input
sub_13af360(0xffffffff)
noreturn
// RIGHT
STEP = zx.d(maze[sx.q(Y) * 0x18 + sx.q(X + 1)])
if (STEP.b != 0x20) // hit the wall
break
X += 1
index += 1
return STEP