Polar Reverse(困难)

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是磁盘文件。挂载后解密即可。这里借用官方脚本

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值