test
这个apk首先要做一个检验,再判断是否进入下一层:
第一处校验:

注意看结果是=0(ASCII=48)才行,根据此写出解密脚本:
flag=''
for i in range(12):
flag+=chr(255-i-100-48)
print(flag)#kjihgfedcba`
然后进入第二层,是一个图片让找特征码,这里分析对应算法,只是发送了一个广播,没啥特殊含义
在assests文件夹找到一个zip压缩包,打开就是flag
老八小冒险
附件是一个自解压文件,解压后是一个RPG冒险小游戏,和老八的台词有些关系吧...
怀疑是RPG maker工具制作的,想办法获取脚本等即可。看ini配置文件,在data'文件夹。
官方说是用CE改数据,我这里用工具提取出来这个游戏的脚本了
根据脚本:

得到下面的字符,MD5即可
jbkjckjsdsfdsfjbkjckj
工具来源[RPGVX]Rvdata转Json工具_哔哩哔哩_bilibili
逢七必变
题目给了一个exe文件,先脱壳一下
源码很简单,就是一个简单的异或,不知道为啥是困难分类

高卢战记
这是一个obj文件,应该是VS编译产生的中间文件,实际上IDA可以直接分析,我们也可以用连接器先转变其为exe文件再操作。直接能看到一个字符串就是密文/
搜索得知高卢和凯撒有关,但是凯撒看了未果,看官方WP得知需要进行ASCII-3操作,说实话脑洞比较大,用随波逐流的ASCII偏移爆破解出来了。

动态酷
一个dll文件,IDA打开分析
发现一个函数GetFlag,只调用了一个函数:这个函数明显符合printf'的特征

跟进其参数发现以下字符串,很明显就是flag

Android
jadx反编译一眼就看到了DES三个字母

往上扫两眼看到一个函数,通过特征判断是base家族

即对某个数字进行base编码然后DES加密。我们用hook即可快速分析出密钥(代码中也可以看出来)但是看Dex代码来说,貌似没用到这个des加密函数

看看字符串比较函数(在native层)

所以到头来DES就没用,写出解密脚本:
s_box = [0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x1, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, 0x4, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x5, 0x9a, 0x7, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, 0x9, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x0, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x2, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 0xcd, 0xc, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0xb, 0xdb, 0xe0, 0x32, 0x3a, 0xa, 0x49, 0x6, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x8, 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x3, 0xf6, 0xe, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, 0x8c, 0xa1, 0x89, 0xd, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0xf, 0xb0, 0x54, 0xbb, 0x16]
str = [0x33, 0x50, 0xef, 0x85, 0x20, 0xd6, 0x9d, 0x8f, 0x92, 0xce, 0x5a, 0xf9, 0x40, 0x8f, 0x91, 0xcf, 0x20, 0x40, 0xb6, 0xfe]
#s_box[input[i]] - (5 * (i / 5) + 4 == i) == *string_point
flag=[]
for i in range(len(str)):
flag.append( s_box.index(str[i]+(5 * (i // 5) + 4 == i)))
#算法2:暴力破解
# for i in range(len(str)):
# for j in range(32,126):
# if (s_box[j]-(5 * (i // 5) + 4 == i) == str[i]):
# flag.append(j)
# break
print(''.join([chr(x)for x in flag]))
最短路
这是个图,只能让AI跑脚本了
#4991ce1781b53409a7a84e2baf9431d6
import heapq
def dijkstra_shortest_path():
# 1. 构建邻接表(Path[i][j] = w)
adj = {
1: {2:2, 3:3},
2: {3:5, 4:3, 10:20},
3: {5:6, 8:5},
4: {5:7},
5: {9:4, 8:1},
6: {7:3},
7: {14:6},
8: {15:21, 11:5},
9: {12:5},
11: {15:13},
12: {15:9},
14: {15:7},
15: {} # 终点无出边
}
# 2. Dijkstra 初始化:距离字典(无穷大表示不可达)
INF = float('inf')
dist = {node: INF for node in adj}
dist[15] = 0 # 终点到自身距离为 0
prev = {node: None for node in adj} # 记录前驱节点,用于回溯路径
heap = []
heapq.heappush(heap, (0, 15)) # 反向求(从 15 出发)
# 3. 执行 Dijkstra 算法
while heap:
current_dist, u = heapq.heappop(heap)
if current_dist > dist[u]:
continue
# 遍历 u 的所有前驱节点(反向找)
for v in adj:
if u in adj[v]: # v → u 有路径
weight = adj[v][u]
if dist[v] > dist[u] + weight:
dist[v] = dist[u] + weight
prev[v] = u
heapq.heappush(heap, (dist[v], v))
# 4. 回溯路径(以起点 1 为例)
start = 1
end = 15
path = []
current = start
while current is not None:
path.append(current)
current = prev[current]
# 5. 输出结果
if dist[start] == INF:
print(f"起点 {start} 到终点 {end} 无路径")
else:
print(f"最短路径:{' → '.join(map(str, path))}")
print(f"最短路径总权重:{dist[start]}")
# 运行算法
dijkstra_shortest_path()
CRe
文件头被改了,先修改为PE文件头,然后UPX脱壳一下
说是从起点(左上角)走到终点(右下角)推测只能往右边或者下边走。这个应该有算法实现,我是纯手撸的!!

ai写脚本,是动态规划+路径回溯
def max_coins_path(grid):
rows = len(grid)
cols = len(grid[0]) if rows > 0 else 0
# 初始化DP表(与网格同尺寸)
dp = [[0 for _ in range(cols)] for _ in range(rows)]
# 填充左上角起点
dp[0][0] = grid[0][0]
# 填充第一行(只能从左边来)
for j in range(1, cols):
dp[0][j] = dp[0][j-1] + grid[0][j]
# 填充第一列(只能从上面来)
for i in range(1, rows):
dp[i][0] = dp[i-1][0] + grid[i][0]
# 填充其他位置(取上面或左边的最大值)
for i in range(1, rows):
for j in range(1, cols):
dp[i][j] = max(dp[i-1][j], dp[i][j-1]) + grid[i][j]
# 回溯路径(从终点倒推到起点)
path = []
i, j = rows - 1, cols - 1
while i >= 0 and j >= 0:
path.append((i, j))
if i == 0 and j == 0:
break
# 优先向上回溯(若上面更大或已到左边界)
if i == 0:
j -= 1
elif j == 0:
i -= 1
else:
if dp[i-1][j] >= dp[i][j-1]:
i -= 1
else:
j -= 1
path.reverse() # 反转路径,从起点到终点
return dp[rows-1][cols-1], path
# 输入网格数据(8行8列)
grid = [
[0, 1, 0, 0, 2, 0, 0, 20],
[0, 1, 1, 5, 5, 0, 0, 0],
[1, 0, 1, 0, 0, 0, 2, 10],
[0, 0, 1, 0, 10, 0, 0, 0],
[2, 0, 0, 4, 0, 0, 0, 20],
[0, 0, 15, 0, 1, 0, 0, 0],
[0, 0, 1, 0, 25, 0, 6, 0],
[4, 0, 0, 5, 0, 6, 0, 20]
]
# 计算最大硬币数和路径
max_total, path = max_coins_path(grid)
# 输出结果
print(f"最大硬币总数:{max_total}")
print("路径(坐标从(0,0)到(7,7)):")
print(" → ".join([f"({i},{j})" for i, j in path]))
# 计算flag(总数的MD5)
import hashlib
flag = hashlib.md5(str(max_total).encode()).hexdigest()
print(f"flag: flag{{{flag}}}")
EasyRust
看逻辑有个you win,怀疑是win之后执行解密等其他函数,所以先X64patch一下看看

向下走,有循环则F4跳过,直接得到可疑字符串,这就是flag(不进行解密、哈希等操作)

app_login
分析代码,flag就是我们输入的字符串拼接起来

分析第二处,这有两个算法,首先md5计算zhishixuebao,然后取偶数位的值。现在开始

密码的生成见下算法,依稀记得前面也考过这个。就是一个简单的算法。由此写出脚本

import hashlib
username='zhishixuebao'.encode('utf-8')
md5_obj=hashlib.md5()
md5_obj.update(username)
md5_user=md5_obj.hexdigest()
flag1=''
for i in range(0,len(md5_user),2):
flag1+=str(md5_user[i])
flag2=''
# (char) ((((255 - i) + 2) - 98) - charArray[i]) == '0'
for j in range(0,15):
flag2+=chr((255-j+2-98-ord('0')))
print(f"flag{{{flag1}{flag2}}}")
kr
手工脱壳,直接看到flag

往哪走
源码如下:
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
char _11110100001010000101111_[29]; // [esp+17h] [ebp-35h] BYREF
int v4; // [esp+34h] [ebp-18h]
int n4; // [esp+38h] [ebp-14h] BYREF
int i; // [esp+3Ch] [ebp-10h]
_BYTE v7[12]; // [esp+40h] [ebp-Ch] BYREF
__main();
_11110100001010000101111_[26] = 0;
*(_WORD *)&_11110100001010000101111_[27] = 0;
v4 = 0;
strcpy(_11110100001010000101111_, "*11110100001010000101111#");
while ( 1 )
{
puts("you can choose one action to execute");
puts("1 up");
puts("2 down");
puts("3 left");
printf("4 right\n:");
scanf("%d", &n4);
if ( n4 == 2 )
{
++*(_DWORD *)&_11110100001010000101111_[25];
}
else if ( n4 > 2 )
{
if ( n4 == 3 )
{
--v4;
}
else
{
if ( n4 != 4 )
LABEL_13:
exit(1);
++v4;
}
}
else
{
if ( n4 != 1 )
goto LABEL_13;
--*(_DWORD *)&_11110100001010000101111_[25];
}
for ( i = 0; i <= 1; ++i )
{
if ( *(_DWORD *)&_11110100001010000101111_[4 * i + 25] > 4u )
exit(1);
}
if ( v7[5 * *(_DWORD *)&_11110100001010000101111_[25] - 41 + v4] == '1' )
exit(1);
if ( v7[5 * *(_DWORD *)&_11110100001010000101111_[25] - 41 + v4] == '#' )
{
puts("\nok, the order you enter is the flag!");
exit(0);
}
}
}
很容易看出是迷宫题,一共25个字符明显是5*5
画出迷宫:

Gobang
经过翻译Gobang是五子棋
点进去首先看到了judge函数和一个main函数,先看看judge,应该就是如果是斜着连一条线就是成功

最开始有一个init函数,赋值了大量语句
void init()
{
strcpy(str, "flag\n");
strcpy(&str[10], "flag\n");
strcpy(&str[20], "flag\n");
strcpy(&str[30], "flag\n");
strcpy(&str[40], "flag\n");
strcpy(&str[50], "flag\n");
strcpy(&str[60], "flag\n");
strcpy(&str[70], "flag\n");
strcpy(&str[80], "flag\n");
strcpy(&str[90], "flag\n");
strcpy(&str[100], "{re\n");
strcpy(&str[110], "is\n");
strcpy(&str[120], "so\n");
strcpy(&str[130], "hard}\n");
strcpy(&str[140], "{there\n");
strcpy(&str[150], "you\n");
strcpy(&str[160], "good\n");
strcpy(&str[170], "flag\n");
strcpy(&str[180], "flag\n");
strcpy(&str[190], "is\n");
strcpy(&str[200], "flag\n");
strcpy(&str[210], "go}\n");
strcpy(&str[220], "i\n");
strcpy(&str[230], "{zhsh\n");
strcpy(&str[240], "are\n");
strcpy(&str[250], "nice\n");
strcpy(&str[260], "flag\n");
strcpy(&str[270], "flag\n");
strcpy(&str[280], "my\n");
strcpy(&str[290], "go\n");
strcpy(&str[300], "{ez\n");
strcpy(&str[310], "have\n");
strcpy(&str[320], "has\n");
strcpy(&str[330], "so\n");
strcpy(&str[340], "well\n");
strcpy(&str[350], "flag\n");
strcpy(&str[360], "flag\n");
strcpy(&str[370], "like}\n");
strcpy(&str[380], "bang\n");
strcpy(&str[390], "{go\n");
strcpy(&str[400], "re\n");
strcpy(&str[410], "flag}\n");
strcpy(&str[420], "many\n");
strcpy(&str[430], "great\n");
strcpy(&str[440], "flag\n");
strcpy(&str[450], "flag\n");
strcpy(&str[460], "{i\n");
strcpy(&str[470], "have\n");
strcpy(&str[480], "a\n");
strcpy(&str[490], "flag}\n");
strcpy(&str[500], "by\n");
strcpy(&str[510], "words}\n");
strcpy(&str[520], "go\n");
strcpy(&str[530], "flag\n");
strcpy(&str[540], "flag\n");
strcpy(&str[550], "don`t\n");
strcpy(&str[560], "bang}\n");
strcpy(&str[570], "look\n");
strcpy(&str[580], "a\n");
strcpy(&str[590], "no\n");
strcpy(&str[600], "zhsh}\n");
strcpy(&str[610], "bang\n");
strcpy(&str[620], "flag\n");
strcpy(&str[630], "flag\n");
strcpy(&str[640], "know}\n");
strcpy(&str[650], "{bang\n");
strcpy(&str[660], "here\n");
strcpy(&str[670], "dream}\n");
strcpy(&str[680], "yes\n");
strcpy(&str[690], "666\n");
strcpy(&str[700], "go\n");
strcpy(&str[710], "flag\n");
strcpy(&str[720], "flag\n");
strcpy(&str[730], "flag\n");
strcpy(&str[740], "flag\n");
strcpy(&str[750], "flag\n");
strcpy(&str[760], "flag\n");
strcpy(&str[770], "flag\n");
strcpy(&str[780], "flag\n");
strcpy(&str[790], "flag\n");
strcpy(&str[800], "flag\n");
}
这里就是下棋环节,大概思路是假如在一个地方下棋,那对应的map0就会变为1.而且输出str对应字符串

根据judge函数的逻辑,写出下面解题代码:
#include <stdio.h>
int main()
{
int x;
int y;
unsigned char str[] = {0x66U, 0x6cU, 0x61U, 0x67U, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x66U, 0x6cU, 0x61U, 0x67U, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x66U, 0x6cU, 0x61U, 0x67U, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x66U, 0x6cU, 0x61U, 0x67U, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x66U, 0x6cU, 0x61U, 0x67U, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x66U, 0x6cU, 0x61U, 0x67U, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x66U, 0x6cU, 0x61U, 0x67U, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x66U, 0x6cU, 0x61U, 0x67U, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x66U, 0x6cU, 0x61U, 0x67U, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x66U, 0x6cU, 0x61U, 0x67U, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x7bU, 0x72U, 0x65U, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x69U, 0x73U, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x73U, 0x6fU, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x68U, 0x61U, 0x72U, 0x64U, 0x7dU, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x7bU, 0x74U, 0x68U, 0x65U, 0x72U, 0x65U, 0xaU, 0x0U, 0x0U, 0x0U, 0x79U, 0x6fU, 0x75U, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x67U, 0x6fU, 0x6fU, 0x64U, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x66U, 0x6cU, 0x61U, 0x67U, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x66U, 0x6cU, 0x61U, 0x67U, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x69U, 0x73U, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x66U, 0x6cU, 0x61U, 0x67U, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x67U, 0x6fU, 0x7dU, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x69U, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x7bU, 0x7aU, 0x68U, 0x73U, 0x68U, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x61U, 0x72U, 0x65U, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x6eU, 0x69U, 0x63U, 0x65U, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x66U, 0x6cU, 0x61U, 0x67U, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x66U, 0x6cU, 0x61U, 0x67U, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x6dU, 0x79U, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x67U, 0x6fU, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x7bU, 0x65U, 0x7aU, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x68U, 0x61U, 0x76U, 0x65U, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x68U, 0x61U, 0x73U, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x73U, 0x6fU, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x77U, 0x65U, 0x6cU, 0x6cU, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x66U, 0x6cU, 0x61U, 0x67U, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x66U, 0x6cU, 0x61U, 0x67U, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x6cU, 0x69U, 0x6bU, 0x65U, 0x7dU, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x62U, 0x61U, 0x6eU, 0x67U, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x7bU, 0x67U, 0x6fU, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x72U, 0x65U, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x66U, 0x6cU, 0x61U, 0x67U, 0x7dU, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x6dU, 0x61U, 0x6eU, 0x79U, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x67U, 0x72U, 0x65U, 0x61U, 0x74U, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x66U, 0x6cU, 0x61U, 0x67U, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x66U, 0x6cU, 0x61U, 0x67U, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x7bU, 0x69U, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x68U, 0x61U, 0x76U, 0x65U, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x61U, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x66U, 0x6cU, 0x61U, 0x67U, 0x7dU, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x62U, 0x79U, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x77U, 0x6fU, 0x72U, 0x64U, 0x73U, 0x7dU, 0xaU, 0x0U, 0x0U, 0x0U, 0x67U, 0x6fU, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x66U, 0x6cU, 0x61U, 0x67U, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x66U, 0x6cU, 0x61U, 0x67U, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x64U, 0x6fU, 0x6eU, 0x60U, 0x74U, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x62U, 0x61U, 0x6eU, 0x67U, 0x7dU, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x6cU, 0x6fU, 0x6fU, 0x6bU, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x61U, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x6eU, 0x6fU, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x7aU, 0x68U, 0x73U, 0x68U, 0x7dU, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x62U, 0x61U, 0x6eU, 0x67U, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x66U, 0x6cU, 0x61U, 0x67U, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x66U, 0x6cU, 0x61U, 0x67U, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x6bU, 0x6eU, 0x6fU, 0x77U, 0x7dU, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x7bU, 0x62U, 0x61U, 0x6eU, 0x67U, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x68U, 0x65U, 0x72U, 0x65U, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x64U, 0x72U, 0x65U, 0x61U, 0x6dU, 0x7dU, 0xaU, 0x0U, 0x0U, 0x0U, 0x79U, 0x65U, 0x73U, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x36U, 0x36U, 0x36U, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x67U, 0x6fU, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x66U, 0x6cU, 0x61U, 0x67U, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x66U, 0x6cU, 0x61U, 0x67U, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x66U, 0x6cU, 0x61U, 0x67U, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x66U, 0x6cU, 0x61U, 0x67U, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x66U, 0x6cU, 0x61U, 0x67U, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x66U, 0x6cU, 0x61U, 0x67U, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x66U, 0x6cU, 0x61U, 0x67U, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x66U, 0x6cU, 0x61U, 0x67U, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x66U, 0x6cU, 0x61U, 0x67U, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U, 0x66U, 0x6cU, 0x61U, 0x67U, 0xaU, 0x0U, 0x0U, 0x0U, 0x0U, 0x0U};
for (int i = 2; i <= 6; i++)
{
x = i;
y = i;
printf("%s", &str[90 * x + 10 * y]);
}
return 0;
}
两点半俱乐部
用GB2312编码失败,所以用UTF16编码

我们首先分析函数,太复杂,所以寻找字符串作为突破口。直接看到flag

标准思路是X64DBG改跳转,还要CE修改跳转按钮次数,有点脑洞。
bllbl_木马
点进去题目给的payload,找host和ip

这个不是说用127.127.127.127,而是默认是127.127.127.127
追踪到这一个push:

然后去IDA或者x32去搜搜分析


都可以定位到,解密即可。
AI写了个解密脚本,但是注意端序,IDA提取出来的是大端序,所以要倒着来。
import struct
import socket
# 定义输入的十六进制数据
ip_hex = 0x4E38220C # 原始IP
port_hex = 0x8E030002 # 原始端口
# 反转字节顺序
ip_hex_reversed = int.from_bytes(ip_hex.to_bytes(4, byteorder='big')[::-1], byteorder='big')
port_hex_reversed = int.from_bytes(port_hex.to_bytes(4, byteorder='big')[::-1], byteorder='big')
# 解密 IP 地址
ip = socket.inet_ntoa(struct.pack("!I", ip_hex_reversed))
# 解密端口
port = struct.unpack("!H", struct.pack("!I", port_hex_reversed)[2:])[0]
print(f"解密后的IP地址:{ip}")
print(f"解密后的端口:{port}")
decipher
反编译PYC:
# Decompiled with PyLingual (https://pylingual.io)
# Internal filename: dialogue.py
# Bytecode version: 3.10.0rc2 (3439)
# Source timestamp: 2024-08-09 13:18:00 UTC (1723209480)
import os
import base64
from Crypto.Cipher import AES
from Crypto.Protocol.KDF import PBKDF2
from Crypto.Util.Padding import pad
def generate_key_and_salt(password):
salt = os.urandom(16)
key = PBKDF2(password, salt, dkLen=32, count=1000000)
return (key, salt)
def encrypt(password, plaintext):
key, salt = generate_key_and_salt(password)
cipher = AES.new(key, AES.MODE_CBC)
ciphertext = cipher.encrypt(pad(plaintext.encode('utf-8'), AES.block_size))
encoded_ciphertext = base64.b64encode(salt + cipher.iv + ciphertext).decode('utf-8')
return encoded_ciphertext
def encrypt_password_file(password, input_file, output_file):
with open(input_file, 'r') as f:
plaintext = f.read()
encrypted_data = encrypt(password, plaintext)
with open(output_file, 'w') as f:
f.write(encrypted_data)
print(f'Encrypted password file saved as {output_file}')
if __name__ == '__main__':
password = input('Enter encryption password: ')
input_file = 'records.txt'
output_file = 'en_records.txt'
encrypt_password_file(password, input_file, output_file)
可以看到是利用标准AES加密加密了一个文件,密钥生成是32字节,暂时感觉无法暴力破解.只能从其他方面入手分析
眼瞎了兄弟,才看到题目给了password是2024
由于iv、salt都是已经写在文件内部,所以我们可以直接获得
AES脚本不是我的强项,让GPT基于原代码写一个解密函数
# Decompiled with PyLingual (https://pylingual.io)
# Internal filename: dialogue.py
# Bytecode version: 3.10.0rc2 (3439)
# Source timestamp: 2024-08-09 13:18:00 UTC (1723209480)
import os
import base64
from Crypto.Cipher import AES
from Crypto.Protocol.KDF import PBKDF2
from Crypto.Util.Padding import pad,unpad
def generate_key_and_salt(password):
salt = os.urandom(16)
key = PBKDF2(password, salt, dkLen=32, count=1000000)
return (key, salt)
def encrypt(password, plaintext):
key, salt = generate_key_and_salt(password)
cipher = AES.new(key, AES.MODE_CBC)
ciphertext = cipher.encrypt(pad(plaintext.encode('utf-8'), AES.block_size))
encoded_ciphertext = base64.b64encode(salt + cipher.iv + ciphertext).decode('utf-8')
return encoded_ciphertext
def encrypt_password_file(password, input_file, output_file):
with open(input_file, 'r') as f:
plaintext = f.read()
encrypted_data = encrypt(password, plaintext)
with open(output_file, 'w') as f:
f.write(encrypted_data)
print(f'Encrypted password file saved as {output_file}')
def decrypt(password, encrypted_file, output_file):
# 读取并 base64 解码
with open(encrypted_file, 'r') as f:
encoded = f.read().strip()
raw = base64.b64decode(encoded)
# 解析 salt, iv, ciphertext
salt = raw[:16]
iv = raw[16:32]
ciphertext = raw[32:]
# 重新生成 key
key = PBKDF2(str(password).encode(), salt, dkLen=32, count=1000000)
# 解密
cipher = AES.new(key, AES.MODE_CBC, iv)
plaintext = unpad(cipher.decrypt(ciphertext), AES.block_size)
# 写出
with open(output_file, 'w', encoding='utf-8') as f:
f.write(plaintext.decode('utf-8'))
print(f"Decrypted to {output_file}")
if __name__ == '__main__':
password = 2024 # 密码
input_file = 'en_records.txt' # 加密文件
output_file = 'original.txt' # 输出解密结果
decrypt(password, input_file, output_file)
出来一大串文本文件。这真坑,在这里藏着呢,md5一下。

o_O
很少见这种苹果平台的题目,还好IDA可以分析
先看成功提示,说是flag{md5{num}},说明我们要输入数字
观官方题解知道,开始运行时候会给出一个3(这个3是因为第一次循环本质是判断奇数偶数数量,判断目标是一个3奇数3偶数的数组,自然返回3)
这个循环的本质就是将v19添加到我们输入的数字的后方。判断条件就是字符串没有结束

第二个循环有意思,我们看到赋值的变量名字虽然不同,但是实际上就那几个。

下一步的算法实际上是对相应位置的数字乘个10,注意循环体第二行的代码实际上是8*(*取地址),并不是8的某次方。

随后和内置的一个数组比较,我们由此写出脚本:
cipher = [0x5a, 0x14, 0xa, 0x32, 0x0, 0x0, 0x14]
flag=[]
for i in cipher:
flag.append(i//10)
flag.reverse()
print(flag)
#flag:flag{7e4e85033d36b28767243dcb3d9c7049}
ReverseGame
进去之后,是一个登录界面
MT管理器定位到文本“游戏”

看到以下代码,判断存在一个隐藏游戏。
public final void invoke(Row3 Button, Composer $composer, int $changed) {
Intrinsics.checkNotNullParameter(Button, "$this$Button");
ComposerKt.sourceInformation($composer, "C366@11239L26:MainActivity.kt#nodowz");
if (($changed & 81) != 16 || !$composer.getSkipping()) {
TextKt.m2578Text4IGK_g("System Information", (Modifier) null, 0L, 0L, (FontStyle) null, (FontWeight) null, (FontFamily) null, 0L, (TextDecoration) null, (TextAlign) null, 0L, 0, false, 0, 0, (Function1<? super TextLayoutResult, Unit>) null, (TextStyle) null, $composer, 6, 0, 131070);
} else {
$composer.skipToGroupEnd();
}
}
});
/* renamed from: lambda-13 */
public static Function3<Row3, Composer, Integer, Unit> f488lambda13 = ComposableLambda2.composableLambdaInstance(-1227456803, false, new Function3<Row3, Composer, Integer, Unit>() { // from class: com.example.reversegame.ComposableSingletons$MainActivityKt$lambda-13$1
@Override // kotlin.jvm.functions.Function3
public /* bridge */ /* synthetic */ Unit invoke(Row3 row3, Composer composer, Integer num) {
invoke(row3, composer, num.intValue());
return Unit.INSTANCE;
}
public final void invoke(Row3 Button, Composer $composer, int $changed) {
Intrinsics.checkNotNullParameter(Button, "$this$Button");
ComposerKt.sourceInformation($composer, "C448@13562L10:MainActivity.kt#nodowz");
if (($changed & 81) != 16 || !$composer.getSkipping()) {
TextKt.m2578Text4IGK_g("确认", (Modifier) null, 0L, 0L, (FontStyle) null, (FontWeight) null, (FontFamily) null, 0L, (TextDecoration) null, (TextAlign) null, 0L, 0, false, 0, 0, (Function1<? super TextLayoutResult, Unit>) null, (TextStyle) null, $composer, 6, 0, 131070);
} else {
$composer.skipToGroupEnd();
}
}
});
/* renamed from: lambda-14 */
public static Function2<Composer, Integer, Unit> f489lambda14 = ComposableLambda2.composableLambdaInstance(274534897, false, new Function2<Composer, Integer, Unit>() { // from class: com.example.reversegame.ComposableSingletons$MainActivityKt$lambda-14$1
@Override // kotlin.jvm.functions.Function2
public /* bridge */ /* synthetic */ Unit invoke(Composer composer, Integer num) {
invoke(composer, num.intValue());
return Unit.INSTANCE;
}
public final void invoke(Composer $composer, int $changed) {
ComposerKt.sourceInformation($composer, "C444@13337L10,444@13292L68:MainActivity.kt#nodowz");
if (($changed & 11) == 2 && $composer.getSkipping()) {
$composer.skipToGroupEnd();
} else {
TextKt.m2578Text4IGK_g("隐藏游戏已解锁", (Modifier) null, 0L, 0L, (FontStyle) null, (FontWeight) null, (FontFamily) null, 0L, (TextDecoration) null, (TextAlign) null, 0L, 0, false, 0, 0, (Function1<? super TextLayoutResult, Unit>) null, MaterialTheme.INSTANCE.getTypography($composer, MaterialTheme.$stable).getTitleMedium(), $composer, 6, 0, 65534);
}
}
});
刚才老考虑代码审计思路,观看官网视频教程,知道利用MT管理器patch跳转来破解,绕过登录的这一个思路。注释掉几处判断即可,然后点击登录,直接进入这个页面

只有系统信息能点,结合jadx看到的信息,咱们追踪hiddenGame

这个判断条件还真不好找,哥们。这个大概就是判断谁点击大于10.功底不够,只能一个个点试试,没有追踪到哪个是HiddenGame。最后发现是安卓版本点击10次才是
依旧patch小游戏的判断条件,定位到flag密钥

定位到flag函数:
if (value$iv9 == Composer.INSTANCE.getEmpty()) {
value$iv9 = new MainActivityKt$HiddenGameScreen$1$1("ii5pccS1mAt2A0kpVV64zA==:C8Vw3Xoy7DYBbhHawxOVqIeqmZvSRchgcrvZygwEgDIS99DDCeXYFqysLSLJ4g3Q", "a_sd.9/1m2)d]_+=", decryptedFlag$delegate, null);
$composer3.updateRememberedValue(value$iv9);
}
做到这一步,我们也可以利用代码审计先定位到key,查找引用,直接看到flag密文
flag解密函数如下:大概就是根据:分割,取KEY和IV。让AI写一个脚本即可
public static final String decryptFlag(String encryptedFlag, String key) throws BadPaddingException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, InvalidKeyException, InvalidAlgorithmParameterException {
Intrinsics.checkNotNullParameter(encryptedFlag, "encryptedFlag");
Intrinsics.checkNotNullParameter(key, "key");
Log.d("Decryption", "Encrypted Flag: " + encryptedFlag);
List parts = StringsKt.split$default((CharSequence) encryptedFlag, new String[]{":"}, false, 0, 6, (Object) null);
if (parts.size() < 2) {
Log.e("Decryption", "Invalid encrypted data format: " + encryptedFlag);
throw new IllegalArgumentException("Invalid encrypted data format");
}
byte[] iv = Base64.decode((String) parts.get(0), 2);
byte[] encryptedData = Base64.decode((String) parts.get(1), 2);
byte[] keyBytes = key.getBytes(Charsets.UTF_8);
Intrinsics.checkNotNullExpressionValue(keyBytes, "getBytes(...)");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec ivSpec = new IvParameterSpec(iv);
SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes, "AES");
cipher.init(2, secretKeySpec, ivSpec);
byte[] decryptedBytes = cipher.doFinal(encryptedData);
Intrinsics.checkNotNull(decryptedBytes);
return new String(decryptedBytes, Charsets.UTF_8);
}
CyberChef也可以

你也知道鸢尾花吗(没搞懂)
参考这篇文章【Python】一文详细介绍 pkl格式 文件 - 知乎
这是python和序列化反序列化相关的文件,我们写代码读取这个文件即可,但是老是报错,说是某个字节识别不了,或者某个类没有方法。怀疑是我的python环境问题。
而且没听过这个鸢尾花模型,只能铩羽而归
PolarCrypt
还给了个img文件,好奇心upup!首先这是魔改UPX,010将patch后的改为UPX然后脱壳

通过大概看代码,起码知道了代码加密方法是AES。

这两处代码首先生成盐,随后生成iv,并且均写入文件之中。接下来跟进KEY算法就好了。

python实现:
src="A1B2C3D4E5F6123456789ABCDEF01122"
key=[]
for i in range(0,32):
key.append(ord(src[i])^0x5a^13*i)
print(key)
再看加密字节数:

AI写出解密脚本即可
from Crypto.Cipher import AES
import os
MAGIC_HEADER = b"ENCRYPTED_DISK_V1"
BLOCK_SIZE = 4096
SALT_SIZE = 16
KEY_SIZE = 32
IV_SIZE = 16
def generate_key(src):
key = []
for i in range(KEY_SIZE): # 生成 32 字节密钥
key.append((ord(src[i]) ^ ((i * 13) ^ 0x5A)) & 0xFF)
return bytes(key) # 转换为字节
def decrypt_file(input_path, output_path):
with open(input_path, 'rb') as f_in, open(output_path, 'wb') as f_out:
# 检查魔术头
header = f_in.read(len(MAGIC_HEADER))
if header != MAGIC_HEADER:
print("[-] 魔术头不匹配")
return
# 读取盐(16字节) - 实际未使用,但仍需要读取跳过
salt = f_in.read(SALT_SIZE)
if len(salt) != SALT_SIZE:
print("[-] 盐读取失败")
return
# 生成密钥
key = generate_key("A1B2C3D4E5F6123456789ABCDEF01122")
# 读取 IV(16字节)
iv = f_in.read(IV_SIZE)
if len(iv) != IV_SIZE:
print("[-] IV 读取失败")
return
# 初始化 AES 解密器
cipher = AES.new(key, AES.MODE_CBC, iv)
# 逐块读取、解密
plaintext = b""
while True:
chunk = f_in.read(BLOCK_SIZE)
if not chunk:
break
plaintext += cipher.decrypt(chunk)
# 手动去除填充(PKCS7) - 按照你提供的逻辑
pad_len = plaintext[-1]
if pad_len < 1 or pad_len > 16:
print("[-] 解密数据填充无效")
return
plaintext = plaintext[:-pad_len]
# 写出解密后的明文
f_out.write(plaintext)
print(f"[+] 文件已成功解密为 {output_path}")
if __name__ == "__main__":
input_file = "encrypted_file.bin"
output_file = "decrypted_flag.img"
if not os.path.exists(input_file):
print(f"[-] 文件 {input_file} 不存在")
else:
decrypt_file(input_file, output_file)
出来的用010打开,直接就看到了flag
DualSafe
点进程序,给了一个虚假flag

注意到调试信息,可能考察dll注入,我还是看了官方视频才意识到这一点。dll实现了一个RSA解密,代码就不贴了,刚进去就能看到一个大大的RSA私钥
在main.exe还能看到AES解密

与其傻傻的一行行分析代码,找加密算法在哪里(说实话我根本没找到,还不太会这种的分析),我们直接动态调试,结合题目说考的dll注入,先给dll注入进去看看效果
在X64的模块菜单处右键,注入模块,进行注入

看视频还对MessageBoxA进行一个断点操作,我也来断点(ctrl+G追踪到此函数,F2下断点)

可以看到成功运行了。而且出来了FLAG,说明我们注入dll成功了,视频讲解说flag就在断点附近,我是没有搞懂,所以持怀疑态度。
我自己尝试,就算不下断点,也会在调用堆栈这里看到flag,所以重点还是利用弹窗调用的堆栈来看flag。(看下图,上面有弹窗文字)

UU加速
方法一
分析代码,通过字符串定位到关键函数

接着看下一段代码,图片写错了,是request.txt文件。

所以我们先创建一个同名文件,看看提示什么再说,下面的代码较为复杂(实际上是懒得分析了)
提示卡密错误,我们跟踪代码。
漏看一段,问题出在这里了。

最终构造:
GGGGGGGG-GGGGGGGG-AAAAAAAA-AAAAAAAA-AAAAAAAA-AAAAAAAA-AAAAAAAA-AAAAAAAA
这个题目巧妙用G的ASCII=71,混淆我们逆向分析。其实我们还可以换个思路,看可否手撕加密算法。
方法二
n54 = 54;
do
{
++v8;
v20 = n54 ^ v15; // V15=80
n54 = a6174VV57175v9V[v8];
Buffer[v8 + 79] = v20;
}
while ( n54 );
卡密错误,请购买后,再次登录_1[51] = 0;
解密结果 = "解密结果";
n64 = 64;
卡密错误,请购买后,再次登录 = 卡密错误,请购买后,再次登录_1;
}
Buffer[v8+79]实际上是下面的中文变量,V15上文赋值为80(即16*5),我们于是推测这就是一个异或80的解密
如果有人疑惑我的IDA为何中文变量名字不被改为_,可以私信我,这需要patch字节,原帖子地址我暂时找不到了。

点击+++
先找到第一处flag

没想到附件的1是磁盘文件。挂载后解密即可。这里借用官方脚本
413

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



