专栏名称: 看雪学苑
致力于移动与安全研究的开发者社区,看雪学院(kanxue.com)官方微信公众帐号。
目录
相关文章推荐
软件小妹  ·  某盘不限速脚本,速度达15M/S! ·  昨天  
嘶吼专业版  ·  GitLab 公布存在严重的管道执行漏洞 ·  4 天前  
经济日报  ·  央行宣布:降准!降息! ·  5 天前  
经济日报  ·  央行宣布:降准!降息! ·  5 天前  
51好读  ›  专栏  ›  看雪学苑

2024长城杯初赛RE(部分题目)

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

正文




easyre


一、分析




有用的伪代码就这一段。
看得出来关键加密就是for循环里的异或加密。

分析这一段加密

  
input = argv[1];
j = 1i64;
for ( i = 0i64; i != 43; ++i )
{
input->m128i_i8[i] ^= input->m128i_u8[i + 1 + -42 * (j / 42)];
++j;
}

简化一下就是:

for(int i = 0; i < 43; ++i)
{
input[i] ^= input[i + 1 - 42 * (i + 1 / 42)]
}

◆前41位,input的前一位xor后一位
◆input[41] ^= input[0]
◆input[42] ^= input[1]

二、确定cipher


if (_mm_movemask_epi8(_mm_and_si128(_mm_cmpeq_epi8(_mm_loadu_si128(input), data1),
_mm_cmpeq_epi8(_mm_loadu_si128(input + 1), data2))) == 0xFFFF)

data1 = [0x0A, 0x0D, 0x06, 0x1C, 0x1D, 0x05, 0x05, 0x5F, 0x0D, 0x03,
0x04, 0x0A, 0x14, 0x49, 0x05, 0x57, 0x0A, 0x0D, 0x06, 0x1C,
0x1D, 0x05, 0x05, 0x5F, 0x0D, 0x03, 0x04, 0x0A, 0x14, 0x49,
0x05, 0x57, 0x00, 0x1B, 0x19, 0x02, 0x01, 0x54, 0x4E, 0x4C,
0x56, 0x00, 0x51, 0x4B, 0x4F, 0x57, 0x05, 0x54, 0x55, 0x03,
0x53, 0x57, 0x01, 0x03, 0x07, 0x04, 0x4A, 0x77, 0x0D]

data2 = [0x00, 0x1B, 0x19, 0x02, 0x01, 0x54, 0x4E, 0x4C, 0x56, 0x00,
0x51, 0x4B, 0x4F, 0x57, 0x05, 0x54]

根据这个if语句中的条件,确定来确定cipher。

解释


◆_mm_loadu_si128(input):
这是一个SSE指令,表示从
input地址处加载一个 128 位(16 字节)的未对齐数据到一个 SSE 寄存器中。

◆_mm_cmpeq_epi8(_mm_loadu_si128(input), data1):
这是一个字节级的比较指令,比较
_mm_loadu_si128(input)中的每个字节和data1中的每个字节;结果是每个字节比较的结果:如果某个字节相等,则对应的结果字节为 0xFF(即全1),否则为 0x00(全0)。

◆_mm_and_si128(...):这是一个按位与操作,对前两个比较结果的每个字节进行按位与运算。相当于都为 0xFF(全1,表示相等)时,结果字节才为 0xFF;否则结果字节为 0x00。

◆_mm_movemask_epi8(...):这是将每个字节的最高位(即第8位)提取出来,形成一个 16 位的整数;若某个字节的最高位是1(即该字节为 0xFF),则对应的位为1;否则为0。

◆最后结果要==0xFFFF,相当于全为1。

换言之,取input的32位进行对比,相同则正确,根据data2可以确定校验的后16位在data1中的位置,往前推16位即是校验的前16位。

因此,正确的cipher为剩下的data1。


三、解题


根据以上分析,可以将整个题简化为一个异或加密,结果与cipher对比。

直接写个正面爆破即可完成:

exp

cipher = [
0x0A, 0x0D, 0x06, 0x1C, 0x1D, 0x05, 0x05, 0x5F, 0x0D, 0x03, 0x04, 0x0A,
0x14, 0x49, 0x05, 0x57, 0x00, 0x1B, 0x19, 0x02, 0x01, 0x54, 0x4E, 0x4C,
0x56, 0x00, 0x51, 0x4B, 0x4F, 0x57, 0x05, 0x54, 0x55, 0x03, 0x53, 0x57,
0x01, 0x03, 0x07, 0x04, 0x4A, 0x77, 0x0D
]

x = ord('f')
input = []

for i in range(len(cipher)):
for num in range(32, 128):
if x ^ num == cipher[i]:
input.append(num)
x = num
break

flag = ''
for j in range(len(input)):
flag += chr(input[j])

print('f' + flag)

# flag{fcf94739-da66-467c-a77f-b50d12a67437}




tmaze


根据题目猜测它是T型迷宫。
结果上网搜全是动物实验。

一、分析


DIE查询可知是64位程序,进入ida分析 →


初始并没有发现有什么引导输入的伪代码,开调!

1.1传参方式及输入字符


if(v5 == 43)处下断点,调试可以发现终端并未让输入。

这里可以确定程序是命令行传参。


在此输入再进行调试即可。


在下面的switch-case逻辑中,可以看出要求输入字符xyz。

1.2 确定输入长度



可以看出v4是输入的path。

i = 0;
do
j = i++;
while ( path[j] );
if ( i == 43 )

分析这一段,do-while循环是在统计输入的字符串长度,检索到0结束。
可以确定path的长度为42。

1.3 调试switch之前的逻辑


v7 = sub_7FF7D50A1230(&unk_7FF7D50D7000, dword_7FF7D50D7FA0, envp);

这个语句,unk_7FF7D50D7000中是若干1和0组成的数组,猜测是构成迷宫的参数,dword_7FF7D50D7FA0是固定参数10。

sub_7FF7D50A1230里是复杂的迷宫生成函数,对我来说难以分析我就先没管。


调试可以知道,data0、data1、data2分别存储了一个地址。

◆data0:1DDFFDACD70h
◆data1:1DDFFDAEA60h
◆data2:1DDFFDAEE20h

1.4 调试switch之后的逻辑


if ( argc != 1 )
{
m = *path == 0;
if ( *path )
{
v14 = 1;
v15 = data1;
k = 0i64;
do
{
chose = path[k];
switch ( chose )
{
case 'z':
v19 = *(v15 + 16);
if ( !v19 || *(v15 + 26) )
goto LABEL_12;
break;
case 'y':
v19 = *(v15 + 8);
if ( !v19 || *(v15 + 25) )
goto LABEL_12;
break;
case 'x':
v19 = *v15;
if ( !*v15 || *(v15 + 24) )
goto LABEL_12;
break;
default:
goto LABEL_12;
}
data1 = v19;
*(v19 + 27) = 1;
k = v14;
len = strlen(path);
++v14;
v15 = v19;
m = len <= k;
}
while ( !m );
}
LABEL_12:
if ( m && data1 == v7 )
{
v13 = sub_7FF7D50A1770(&qword_7FF7D50D92C0, "yes flag is flag{UUID(md5(your input))}");
sub_7FF7D50A1B70(v13);
}

根据最后的if判断语句和switch逻辑,可以知道data1指向的地址是会发生变化的。

v7的地址就是data2存的地址。

那么,可以分析出来,data1其实就是当前操作对应地址,最开始的的data1就是start_locdata2就是最后的地址end_loc。

再但拿一个case来分析:

case 'z':
   v19 = *(v15 + 16);
   if ( !v19 || *(v15 + 26) )
  goto LABEL_12;
   break;

◆都是对地址操作,v15就是now_loc,v19存储的是*(now_loc + 16)这个地址的值,而这个值恰好又是一个地址,可以说v19 是next。

◆要满足不去执行goto的操作,需要让if里的条件为,满足:next ≠ 0、*(now_loc + 26) = 0

分析完可以得到:


二、解题


根据输入全跟地址相关,以及地址操作全在下半段。
就可以考虑不管上半迷宫生成的逻辑。
我们可以把1.3分析的内容全部理解为:获得start_locend_loc。

2.1 解题思路


idapython,把start_locend_loc的内容当作迷宫,根据switch-case的限制逻辑,利用深度搜索算法来输出符合条件的xyz,即path。

参考文章:

[原创]关于爆破迷宫路径的一系列思考
https://bbs.kanxue.com/thread-282091.htm

CTF中迷宫题型的解题思路(https://blog.csdn.net/weixin_46175201/article/details/134166280

深度优先搜索(DFS) 总结(算法+剪枝+优化总结)(https://blog.csdn.net/yanweiqi1754989931/article/details/109603384

2.2 EXP


用递归算法实现深度搜索。

记录已经访问过的地址(相当于迷宫中已经走过的坐标)为all_visited_loc,来实现减少搜索量。

import idc

start_loc = 0x1DDFFDAEA60
end_loc = 0x1DDFFDAEE20

def dfs(path, all_visited_loc, now_loc):
if now_loc == end_loc:
if len(path) == 42:
print("Yes! Found the path")
for i in range(len(path)):
print(path[i], end='')
else:
print("Not Found the path")
return

if now_loc in all_visited_loc:
return

all_visited_loc.append(now_loc)

x_location = idc.get_qword(now_loc)
y_location = idc.get_qword(now_loc + 8)
z_location = idc.get_qword(now_loc + 16)

x_judge = idc.get_wide_byte(now_loc + 24)
y_judge = idc.get_wide_byte(now_loc + 25)
z_judge = idc.get_wide_byte(now_loc + 26)

if x_location > 0 and x_judge == 0:
path.append("x")
dfs(path, all_visited_loc, x_location)
path.pop()

if y_location > 0 and y_judge == 0:
path.append("y")
dfs(path, all_visited_loc, y_location)
path.pop()

if z_location > 0 and z_judge == 0:
path.append("z")
dfs(path, all_visited_loc, z_location)
path.pop()

all_visited_loc.pop()
return

path = []
all_visited_loc = []
dfs(path, all_visited_loc, start_loc)


2.3 验证






xiran_encrypto


Tips:xiran_encrypto是恶意样本分析,本质是从cha文件里解出一个数据流,clickme文件是chacha20对flag.png文件进行加密。对本人来说太难了,分析不出来QAQ,后续若能理解再来分享。




看雪ID:Sh4d0w

https://bbs.kanxue.com/user-home-1002267.htm

*本文为看雪论坛精华文章,由 Sh4d0w 原创,转载请注明来自看雪社区



# 往期推荐

1、Alt-Tab Terminator注册算法逆向

2、恶意木马历险记

3、VMP源码分析:反调试与绕过方法

4、Chrome V8 issue 1486342浅析

5、Cython逆向-语言特性分析



球分享

球点赞

球在看



点击阅读原文查看更多