专栏名称: 看雪学苑
致力于移动与安全研究的开发者社区,看雪学院(kanxue.com)官方微信公众帐号。
目录
相关文章推荐
家电圈  ·  松下断舍离的企业困境与产业警示 ·  17 小时前  
家电圈  ·  松下断舍离的企业困境与产业警示 ·  17 小时前  
福州新闻网  ·  公开遴选!福州市委网信办发布最新公告! ·  2 天前  
贵州药监  ·  服务业继续发挥我国经济增长主动力作用 ·  4 天前  
贵州药监  ·  服务业继续发挥我国经济增长主动力作用 ·  4 天前  
51好读  ›  专栏  ›  看雪学苑

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

看雪学苑  · 公众号  · 互联网安全  · 2024-09-06 17:59

主要观点总结

本文介绍了2024 KCTF 大赛的相关内容,包括比赛设置的评分体系、赛题的解析等。文章还详细描述了比赛中的一道题目《试探》的设计思路、解题过程以及对于相关混淆技术的处理。

关键观点总结

关键观点1: 比赛概述

KCTF大赛于2024年8月15日正式开赛,设置了多维度的评分体系,旨在引导竞赛的难度和趣味度,使其更具挑战性和吸引力。

关键观点2: 赛题解析

赛题《试探》是一个采用拼图游戏设计的题目,初始状态和目标状态已知,需要通过移动元素达到目标状态。整个算法隐藏在一段shellcode中,涉及混淆技术。

关键观点3: 解题过程

解题过程包括使用IDA查看main函数,分析字符串加密和shellcode逻辑,处理混淆技术,包括字节替换和无效指令去除等。

关键观点4: 棋盘问题解析

赛题中的棋盘问题是一个简单的8-puzzle问题,通过一系列步骤手动解出,最终得到注册码。

关键观点5: 注册码获取

通过解决棋盘问题得到的走法路径转换成三进制数,最终得到flag即注册码为“011110202122”。


正文

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了。




然后分析主校验函数:





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







请到「今天看啥」查看全文