2024 KCTF 大赛 | 第十题《试探》设计思路及解析

WriteUp 1周前 admin
48 0 0
2024 KCTF 大赛 | 第十题《试探》设计思路及解析
2024 KCTF 大赛于8月15日正式开赛!比赛设置了多维度的评分体系,包括难度值、火力值和精致度积分,旨在引导竞赛的难度和趣味度,使其更具挑战性和吸引力。同时,也为参赛选手提供了更加公平、有趣的竞赛平台。

今天中午12点,第十题《试探》已截止答题,本题共有10支战队成功破解,【hzqmwne】战队用时1小时48分18秒抢先拿下此题,第二名来自【Nepnep】战队、第三名来自【COMPASS】战队。
*注意:签到题《逐光启航》持续开放,整个比赛期间均可提交答案获得积分

一起来看看本题设计思路和解析吧!

出题战队:天外星系


战队成员ID:geekfire

2024 KCTF 大赛 | 第十题《试探》设计思路及解析


设计思路


题目名称:hidesc
运行环境:win10 win11
输出提示:key正确则输出提示ok!

题目设计思路:

算法采用一个简单的拼图游戏

初始状态为:
{0, 1, 3},
{5, 2, 6},
{4, 7, 8}

目标状态为:
{1, 2, 3},
{4, 5, 6},
{7, 8, 0}

通过移动元素0来到达目标状态。移动过程中0元素的坐标即为注册码。

整个算法隐藏在一段shellcode中,并且shellcode加入了大量的花指令干扰分析。

对shellcode的加载函进行了字符串隐藏 并通过系统调用隐藏API的方式干扰分析。

最终注册码为:
011110202122

赛题解析


以下解析由看雪专家【wx_孤城】给出,来自【中午吃什么】战队。

2024 KCTF 大赛 | 第十题《试探》设计思路及解析


丢进IDA查看main函数,有一些简单的字符串加密。

逐步断点,调用了以下函数:
ntdll.dll
NtAddBootEntry
TpAllocWait
TpSetWait

猜测为shellcode注入,简单分析下main函数逻辑。




2024 KCTF 大赛 | 第十题《试探》设计思路及解析

18DB处的逻辑,将kctf + input + 6050处的一块shellcode拷贝到75C0
之后创建2个线程,线程A执行shellcode, 线程B等待答案并输出结果ok!或no!



下面重点分析这串shellcode。
2024 KCTF 大赛 | 第十题《试探》设计思路及解析

选中140006050,使用IDA–>Edit–>Code转换为代码。
这时候我们是不能F5的,因为作者做了混淆。

混淆分析


混淆分为2部分

第一部分将部分字节0x00换成了0x2A,导致IDA静态分析挂掉所以不能F5。


2024 KCTF 大赛 | 第十题《试探》设计思路及解析

第二部分增加了一些无关的跳转和无效指令。


2024 KCTF 大赛 | 第十题《试探》设计思路及解析

经过分析发现无效指令特征码只有这三种:
?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? FD EB 1F 3E 1C EB EB
?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? FF EB 15 3E 1D EB FB
?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? FE EB 18 3E 1C EB EB


去混淆脚本


直接丢给GPT写个去混淆的python脚本。



1.py,将无关跳转和无效指令替换为nop。

# -*- coding: gbk -*-

def replace_bytes_and_preceding(file_path, search_bytes, replace_byte, preceding_length):
# 读取二进制文件内容
with open(file_path, 'rb') as file:
data = file.read()

# 将要查找的字节和替换的字节转换为字节类型
search_bytes = bytes.fromhex(search_bytes)
replace_byte = bytes.fromhex(replace_byte)
replace_length = len(search_bytes) + preceding_length # 替换的总长度

# 创建一个可变字节数组来进行操作
modified_data = bytearray(data)

# 初始化搜索开始位置
start = 0

while start < len(modified_data):
# 查找字节序列的位置
index = modified_data.find(search_bytes, start)
if index == -1:
break

# 计算需要替换的起始位置
replace_start = max(0, index - preceding_length)

# 将替换的范围全部设置为 `replace_byte`
modified_data[replace_start:index + len(search_bytes)] = replace_byte * replace_length

# 更新搜索开始位置,跳过当前替换的位置
start = index + len(search_bytes)

# 将修改后的数据写回到文件中
with open(file_path, 'wb') as file:
file.write(modified_data)

print(f"Replaced all occurrences of {search_bytes.hex()} and preceding {preceding_length} bytes with {replace_byte.hex()} in {file_path}.")

preceding_length = 11 # 替换之前的字节数

replace_bytes_and_preceding('test3.exe', 'FD EB 1F 3E 1C EB EB', '90', preceding_length)
replace_bytes_and_preceding('test3.exe', 'FF EB 15 3E 1D EB FB', '90', preceding_length)
replace_bytes_and_preceding('test3.exe', 'FE EB 18 3E 1C EB EB', '90', preceding_length)


2.py,将0x2A还原成0x00。

# -*- coding: gbk -*-

def replace_specific_byte_in_range(file_path, search_bytes, search_byte, replace_byte, offset_start, offset_end):
# 读取二进制文件内容
with open(file_path, 'rb') as file:
data = file.read()

# 将要查找的字节和替换的字节转换为字节类型
search_bytes = bytes.fromhex(search_bytes)
search_byte = bytes.fromhex(search_byte)
replace_byte = bytes.fromhex(replace_byte)

# 创建一个可变字节数组来进行操作
modified_data = bytearray(data)

# 初始化搜索开始位置
start = 0

while start < len(modified_data):
# 查找字节序列的位置
index = modified_data.find(search_bytes, start)
if index == -1:
break

# 计算替换范围的起始和结束位置
range_start = index + offset_start
range_end = min(index + offset_end, len(modified_data))

# 替换范围内的所有指定字节
for i in range(range_start, range_end):
if modified_data[i] == search_byte[0]:
modified_data[i] = replace_byte[0]

# 更新搜索开始位置,跳过当前查找的位置
start = index + len(search_bytes)

# 将修改后的数据写回到文件中
with open(file_path, 'wb') as file:
file.write(modified_data)

print(f"Replaced all occurrences of {search_byte.hex()} with {replace_byte.hex()} in range [{offset_start:#X}, {offset_end:#X}] after each occurrence of {search_bytes.hex()} in {file_path}.")

replace_specific_byte_in_range('test3.exe', '57 50 51 56 E8 FF FF FF FF C0', '2A', '00', 0x2C, 0xEA7 + 0x2C)


用脚本去除混淆后的代码,可以发现无效指令都被换成nop了。


2024 KCTF 大赛 | 第十题《试探》设计思路及解析


然后就可以愉快的F5了。


2024 KCTF 大赛 | 第十题《试探》设计思路及解析
2024 KCTF 大赛 | 第十题《试探》设计思路及解析


然后分析主校验函数:


2024 KCTF 大赛 | 第十题《试探》设计思路及解析
2024 KCTF 大赛 | 第十题《试探》设计思路及解析
2024 KCTF 大赛 | 第十题《试探》设计思路及解析


分析后看起来是一个8-puzzle问题,要求从起始棋盘状态走到最终状态的最少步数。


原始棋盘状态
0 1 3
5 2 6
4 7 8

终点棋盘状态
1 2 3
4 5 6
7 8 0


这个棋盘挺简单的,可以手动解出。


解棋盘步骤


原始
0 1 3
5 2 6
4 7 8

第一步往右(索引:1),0和1交换,变化为
1 0 3
5 2 6
4 7 8

第二步往下(索引:4),0和2交换,变化为
1 2 3
5 0 6
4 7 8

第三步往左(索引:3),0和5交换,变化为
1 2 3
0 5 6
4 7 8

第四步往下(索引:6),0和4交换,变化为
1 2 3
4 5 6
0 7 8

第五步往右(索引:7),0和7交换,变化为
1 2 3
4 5 6
7 0 8

第六步往右(索引:8),0和8交换,变化为
1 2 3
4 5 6
7 8 0


得到走法的路径为1,4,3,6,7,8

换成三进制01 11 10 20 21 22

得到flag 011110202122

2024 KCTF 大赛 | 第十题《试探》设计思路及解析


2024 KCTF 大赛 | 第十题《试探》设计思路及解析
2024 KCTF 大赛 | 第十题《试探》设计思路及解析

球分享

2024 KCTF 大赛 | 第十题《试探》设计思路及解析

球点赞

2024 KCTF 大赛 | 第十题《试探》设计思路及解析

球在看


2024 KCTF 大赛 | 第十题《试探》设计思路及解析

点击阅读原文查看更多

原文始发于微信公众号(看雪学苑):2024 KCTF 大赛 | 第十题《试探》设计思路及解析

版权声明:admin 发表于 2024年9月6日 下午6:00。
转载请注明:2024 KCTF 大赛 | 第十题《试探》设计思路及解析 | CTF导航

相关文章