前言
MoeCTF 2023 re 第二篇
GUI
这道题是一道32位windows程序的题目
只需要找到加密函数和密文即可解密

在 Windows 编程里,窗口过程(Window Procedure)属于回调函数,它的作用是处理发送给窗口的各类消息。当用户与窗口交互(像点击按钮、关闭窗口等)或者系统有事件发生时,操作系统会把相应的消息发送给窗口,而窗口过程函数则会对这些消息进行处理。
WndClass.lpfnWndProc = sub_450CDF; 这行代码把 WndClass 结构体的 lpfnWndProc 成员设置为 sub_450CDF。这里的 sub_450CDF 应该是一个函数指针,它指向的函数就会作为窗口过程函数来处理窗口消息。
我们跟进去
__CheckForDebuggerJustMyCode(&unk_528026);
if ( Msg > 0xF )//如果msg的值大于15
{
if ( Msg == 16 )//并且msg的值等于16
{
DestroyWindow(hWndParent);//调用 DestroyWindow 函数销毁父窗口 hWndParent
}
else
{
if ( Msg != 273 )//如果msg不等于273,则调用 DefWindowProcW 函数,将该消息交给默认的窗口过程处理
return DefWindowProcW(hWndParent, Msg, wParam, lParam);
if ( (unsigned __int16)wParam == 1 )//msg不等于16且等于273
{
DlgItem = GetDlgItem(hWndParent, 2);
GetWindowTextW(DlgItem, String, 1024);
sub_450C94(String);
v13 = 0;
sub_450A0A(v7, v8);//加密函数
LOBYTE(v13) = 1;
sub_450C94(a91);//a91是密文
if ( (unsigned __int8)sub_4531AB(v7, v6) )
MessageBoxW(hWndParent, Text, L"hint", 0);
else
MessageBoxW(hWndParent, L"Sorry, flag error.", L"hint", 0);//这里消息弹窗flag错误,说明加密函数在上方
sub_4529B8(v6);
LOBYTE(v13) = 0;
sub_4529B8(v7);
v13 = -1;
sub_4529B8(v8);
}
}
}
else
{
switch ( Msg )
{
case 0xFu:
hDC = BeginPaint(hWndParent, &Paint);
hbr = CreateSolidBrush(0xFFFFFFu);
FillRect(hDC, &Paint.rcPaint, hbr);
DeleteObject(hbr);
EndPaint(hWndParent, &Paint);
break;
case 1u:
CreateWindowExW(0, L"STATIC", L"Please input flag:", 0x50000000u, 10, 10, 120, 20, hWndParent, 0, 0, 0);
CreateWindowExW(0, L"EDIT", &word_4FE15C, 0x50800000u, 10, 40, 250, 30, hWndParent, (HMENU)2, 0, 0);
CreateWindowExW(0, L"BUTTON", L"submit", 0x50000000u, 10, 80, 80, 30, hWndParent, (HMENU)1, 0, 0);
break;
case 2u:
PostQuitMessage(0);
break;
default:
return DefWindowProcW(hWndParent, Msg, wParam, lParam);
}
}
return 0;
}
这段代码是一个 Windows 典型的窗口消息处理函数(回调函数)
sub_450A0A(v7, v8)是加密函数
sub_450C94(a91)中的a91是密文

跟进去这两个看看,加密函数是-5之后异或,那解密函数先异或再加5

再看a91

exp:
str1 = [0x0F, 0x3E, 0x30, 0x27, 0x13,
0x01, 0x7D, 0x70, 0x70, 0x03,
0x7D, 0x38, 0x0E, 0x7A, 0x23,
0x7C, 0x0B, 0x1A, 0x3C, 0x7D,
0x39, 0x7F, 0x3C, 0x4D, 0x4D,
0x4D, 0x29]
for i in str1:
tmp = (i ^ 0x51) + 5
print(chr(tmp), end='')
UPX!

64位加壳程序

砸壳

函数逻辑很简单,str[j]异或0x67 如果等于byte_140196000[j],那就是对的,所以跟进去byte_140196000[j]

这里把8,W,‘8’,‘5’这些转换为对应的ascii码值再换成16进制即可,注意8和‘8’是不一样的,8是0x08,‘8’是8转换为ascii码值之后对应的16进制
3 dup(‘F’) 这个的意思是有三个‘F’,那就是三个0x46
exp:
str1 = [ 0x0A, 0x08, 0x02, 0x04, 0x13, 0x01, 0x1C, 0x57, 0x0F, 0x38,
0x1E, 0x57, 0x12, 0x38, 0x2C, 0x09, 0x57, 0x10, 0x38, 0x2F,
0x57, 0x10, 0x38, 0x13, 0x08, 0x38, 0x35, 0x02, 0x11, 0x54,
0x15, 0x14, 0x02, 0x38, 0x32, 0x37, 0x3F, 0x46, 0x46, 0x46,
0x1A]
for i in str1:
tmp = i ^ 0x67
print(chr(tmp), end='')
ezandroid
apk逆向,而且还是个典型的迷宫游戏
找到MainActivity函数进去
在 Android 逆向分析中,理解 JNI 也是非常关键的,因为很多 Android 应用会使用 JNI 来隐藏关键逻辑和数据


flag长度是23
把apk文件变成zip文件进去找到so文件里面的JNI_Onload,用ida打开



有迷宫但是不知道迷宫怎么构建

找到sub_E80这个函数,看到了wasd,且sw上下是15,就是说上下移动下标会加减15(到下一行的同一位置),那就是列数是15,行数是120/15 = 9
迷宫:
@********
.********
…#…*
.********.***
.…*
.***.********
…*******
这里我直接用python脚本跑出结果

@是起点,下标=行号15+列号=15+3=18
#是终点=行号15+列号=3*15+8=53
exp:
from collections import deque
# 定义迷宫
maze = [
"***************",
"***@***********",
"***.***********",
"*...****#..****",
"*.********.****",
"*.****.....****",
"*.****.********",
"*......********",
"***************"
]
# 定义方向和对应的字符
directions = [(-1, 0, 'w'), (1, 0, 's'), (0, -1, 'a'), (0, 1, 'd')]
# 获取迷宫的行数和列数
rows = len(maze)
cols = len(maze[0])
# 找到起始位置 @ 和目标位置 #
start_row, start_col = None, None
target_row, target_col = None, None
for i in range(rows):
for j in range(cols):
if maze[i][j] == '@':
start_row = i
start_col = j
elif maze[i][j] == '#':
target_row = i
target_col = j
# 计算 # 的下标
target_index = target_row * cols + target_col
print(f"# 的下标是: {target_index}")
# 初始化队列和访问标记
queue = deque([(start_row, start_col, "", [start_row * cols + start_col])])
visited = [[False] * cols for _ in range(rows)]
visited[start_row][start_col] = True
while queue:
current_row, current_col, path, index_list = queue.popleft()
# 如果到达目标位置,输出路径和下标变化
if current_row == target_row and current_col == target_col:
print("找到路径:", path)
print("下标变化:", index_list)
break
# 尝试四个方向的移动
for dr, dc, dir_char in directions:
new_row = current_row + dr
new_col = current_col + dc
new_index = new_row * cols + new_col
# 检查新位置是否合法且未访问过
if 0 <= new_row < rows and 0 <= new_col < cols and maze[new_row][new_col] != '*' and not visited[new_row][new_col]:
visited[new_row][new_col] = True
new_path = path + dir_char
new_index_list = index_list + [new_index]
queue.append((new_row, new_col, new_path, new_index_list))
else:
print("未找到路径。")
SMC
SMC 是一种代码混淆技术,代码在运行时会修改自身以达到隐藏真实逻辑的目的


for 循环从 0 到 121,共循环 122 次。
*((_BYTE *)&loc_4014D0 + i) ^= 0x66u;:对从 loc_4014D0 地址开始的每个字节进行异或操作,异或密钥为 0x66。(_BYTE *)&loc_4014D0 + i 表示当前要处理的字节的地址,通过解引用操作符 * 对该地址的字节进行异或运算。
写个idapython patch一下
addr = 0x4014D0
for i in range(122):
b = get_bytes(addr + i, 1)
idc.patch_byte(addr + i, ord(b) ^ 0x66)
print('ok')
这是解密之前的样子

执行命令

回到解密之后

选中这些标红的区域,先按c转化为代码,然后再摁按P生成函数,最后再摁F5反编译

找到密文

异或即可出答案
junk_code
这里考察了花指令(就是多余指令)
进入main函数,发现有两个判断函数

点进去查看发现反编译失败

点击0x4605D0回到汇编,发现有多余指令

选中(点击一下即可)00460612摁U再选中0E8h点击NOP进行填充

再回到0x4605D0,选中(点击一下即可),摁p生成函数

摁f5反编译即可进去看到正确函数,大致内容就是flag值ascii码减去5等于aHj表里面的值


点进去查看即可知道aHj表

另一半flag也是一样的,摁p生成函数f5反编译

这里是个异或


两段合在一起即可得出flag


693

被折叠的 条评论
为什么被折叠?



