2024 KCTF 大赛 | 第十题《试探》设计思路及解析
2024-9-6 17:46:57 Author: mp.weixin.qq.com(查看原文) 阅读量:2 收藏

2024 KCTF 大赛于8月15日正式开赛!比赛设置了多维度的评分体系,包括难度值、火力值和精致度积分,旨在引导竞赛的难度和趣味度,使其更具挑战性和吸引力。同时,也为参赛选手提供了更加公平、有趣的竞赛平台。
今天中午12点,第十题《试探》已截止答题,本题共有10支战队成功破解,【hzqmwne】战队用时1小时48分18秒抢先拿下此题,第二名来自【Nepnep】战队、第三名来自【COMPASS】战队。
*注意:签到题《逐光启航》持续开放,整个比赛期间均可提交答案获得积分
一起来看看本题设计思路和解析吧!

出题战队:天外星系

战队成员ID:geekfire

设计思路

题目名称: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_孤城】给出,来自【中午吃什么】战队。

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

逐步断点,调用了以下函数:
ntdll.dll
NtAddBootEntry
TpAllocWait
TpSetWait
猜测为shellcode注入,简单分析下main函数逻辑。

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

下面重点分析这串shellcode。
选中140006050,使用IDA-->Edit-->Code转换为代码。
这时候我们是不能F5的,因为作者做了混淆。
混淆分为2部分

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

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

经过分析发现无效指令特征码只有这三种:
?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? 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了。

然后就可以愉快的F5了。


然后分析主校验函数:



原始棋盘状态
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


球分享

球点赞

球在看

点击阅读原文查看更多


文章来源: https://mp.weixin.qq.com/s?__biz=MjM5NTc2MDYxMw==&mid=2458572205&idx=1&sn=037af557718d72d8db060a1cbb23f632&chksm=b18de52786fa6c31878a9daaa7333ec2819b1c3b17e8c93345f9a47e790af8444ffbac32c447&scene=58&subscene=0#rd
如有侵权请联系:admin#unsafe.sh