逆向ak,但感觉自己代码阅读能力还是很差,耐心也不够,很多地方都是借助AI的
ps:不知道为什么图好像不见了,懒得再找了
encrypted
简单异或,用前缀palu{猜一下异或数字
a='qcoq~Vh{e~bccocH^@Lgt{gtg'for i in range(len(a)): print(chr(ord(a[i])^(i+1)),end='')# palu{PosltionalXOR_sample}CatchPalu
这一段hook了messageboxa的函数地址,在这段代码之后调用的messageboxa都会变成sub_401360
动态调试看看
这里flag输入要输入附件里面的,也就是palu{P1au_D0nt_Bel1eve},才能进入下面的messageboxa逻辑
进去就可以发现真正的比对函数,这里给硬编码的v8加密,似乎是魔改rc4,但不需要编写代码,直接进函数sub_CE1270,等函数走完看v8内存就行
ps:这里我做题的时候密钥被改变了,我当时手动修改回了forpalu,但这里复现写wp的时候密钥却是正确的,不知道什么情况…
Asymmetric
又是一个go,长得很丑得耐心点看
动态调试慢慢分析,可以发现其实是一个rsa,数据都给了,但是要因式分解,随便找个网站分一下
from Crypto.Util.number import inverseN_str = "100000000000000106100000000000003093"e = 65537C_str = "94846032130173601911230363560972235"
N = int(N_str)C = int(C_str)p1 = 3p2 = 47p3 = 2287p4 = 3101092514893p5 = 100000000000000003phi_N = (p1 - 1) * (p2 - 1) * (p3 - 1) * (p4 - 1) * (p5 - 1)d = inverse(e, phi_N)
M = pow(C, d, N)M_str = str(M)
print("计算出的输入字符串是:", M_str)# 2279348573780051194351488552157565a=2279348573780051194351488552157565print(a.to_bytes(16,'big'))# palu{3a5Y_R$A}PaluArray
简单的crackme,只不过函数长得很恶心
先根据字符串定位到主函数逻辑
其实就是要让v13=1145141919810
至于v13的生成逻辑,要看sub_7FF6987E1994
简单来说,就是根据传入的v4,在off_7FF6987E9B48里找索引值
动态调试可以发现,v4其实就是我们输入的字符串,那么先解出来我们输入的字符串是什么
a=[0x50,0x61,0x6C,0x75,0x5F,0x39,0x39,0x36,0x21,0x3F]b='1145141919810'for i in b: print(chr(a[int(i,10)]),end='')# aa_9a_a?a?!aP把这个输入就得到flag了
PaluFlat
.com文件,用7zip解压,解压得到一个1g的文件,有点吓人。。。
不过主要逻辑不难
我们的输入经过函数401550加密,再与硬编码的密文比对即可
sub_401550被控制流平坦化混淆,有些难看
但我把这个函数丢给ai它直接就分析出来了…运气还行
enc=[0x54,0x84,0x54,0x44,0xA4,0xB2,0x84,0x54,0x62,0x32,0x8F,0x54,0x62,0xB2,0x54,0x3,0x14,0x80,0x43]def decrypt(ciphertext): key1 = "palu" key2 = "flat" plaintext = [] for i, c in enumerate(ciphertext): # 选择密钥 key = key1 if i % 2 == 0 else key2 k = key[i % len(key)] # 解密步骤 v11 = ~c & 0xFF # 取反(注意处理符号) v11 = (v11 + 85) & 0xFF # 加 85 v11 = ((v11 << 4) (v11 >> 4)) & 0xFF # 交换高低四位 plaintext.append(chr(v11 ^ ord(k))) # 异或密钥 return ''.join(plaintext)
print(decrypt(enc))
# palu{Fat_N0t_Flat!}palugogogo
简单的go逆向,我用的ida9版本可以直接显示函数符号,不知道低版本的怎么样,go逆向就是函数长得太丑了很难看,但动态调试分析并不难
这里一开始有个反调试,hook掉返回值过掉就行
具体比对逻辑在下面
输入的flag经过encrypt这个函数加密,再与硬编码的密文比对,其中value与我们输入的无关,它是程序内部自生成的密钥,直接动调拿就可以,值是0x4F
至于这个加密也是耐心动调分析就能出,一开始我测试了一下,发现是逐字节加密,加密逻辑如下
a='palu{asdasdsajkhfjasf}'value=0x4ffor i in range(len(a)): print(hex(ord(a[i])+value+(i%5)),end=',')照着解密就可以
enc=[0xbf,0xb1,0xbd,0xc7,0xce,0x96,0x80,0x98,0x82,0x9a,0x7f,0xaf,0xc1,0xb3,0xbf,0xc4,0xcd]value=0x4ffor i in range(len(enc)): print(chr(enc[i]-value-(i%5)),end='')# palu{G0G0G0_palu}ParlooChecker
个人感觉最难的一道题,太考验耐心了
apk逆向,但jadx无法分析,转到jeb
主要加密比对逻辑被藏在本地方法parloo里面,解压apk看一下so文件,so文件的导出表只有一个函数,就是oncreat3
函数没任何符号,动调so文件又有些麻烦,只能一个个看了,前面的没什么用,直接看else部分
sub_29390、sub_291D0两个函数进行初始化,这部分使用rc4创建了后面加密要用的密钥,后面有很多函数都不用过多分析,大多起到混淆、分配内存或是格式化字符串作用
sub_28D30里面藏了一个tea
大致逻辑如上,主要调用rc4生成密钥之类的,再调用tea加密原文,大多数函数都没有任何作用
ps:如果不用AI我可能要花更多时间。。。
import structimport sys
a=[0x99,0xDD,0x56,0xFF,0x6D,0xD9,0x55,0x54,0x42,0x4D,0x79,0x1A,0x34,0xB7,0x81,0x2F]UNK_10170 = b''for i in a: UNK_10170+=i.to_bytes(1)b=[0x87,0xC1,0x56,0xC0,0x4C,0xF4,0x63,0x4F]UNK_10180 = b''for i in b: UNK_10180+=i.to_bytes(1)enc=[0xA9,0xB,0x5C,0x1C,0xA3,0x41,0x88,0xCA,0x66,0xD9,0x77,0x1D,0x78,0x3,0x8E,0x7A,0xBA,0x7B,0xD4,0x90,0xCD,0x50,0x7,0x83,0x41,0x4A,0x82,0x9C,0x79,0x1D,0xCC,0x6F,0x9D,0x2F,0x39,0x2D,0xA2,0xDA,0x83,0x1B]TARGET_CIPHERTEXT = b''for i in enc: TARGET_CIPHERTEXT+=i.to_bytes(1)def rc4_ksa(key): """RC4 Key-Scheduling Algorithm (based on sub_2C740)"""S = list(range(256)) j = 0 key_bytes = key key_len = len(key_bytes)
for i in range(256): j = (j + S[i] + key_bytes[i % key_len]) % 256 S[i], S[j] = S[j], S[i] return S
def rc4_prga_sub_293E0(initial_S, input_data): """RC4 Pseudo-Random Generation Algorithm (based on sub_293E0 loop)"""S = list(initial_S) output_data = bytearray(len(input_data))
v8 = 0 v7 = 0
for k in range(len(input_data)): v8 = (v8 + 1) % 256 v7 = (v7 + S[v8]) % 256 S[v8], S[v7] = S[v7], S[v8]
keystream_index = (S[v7] + S[v8]) % 256 keystream_byte = S[keystream_index] output_data[k] = keystream_byte ^ input_data[k]
return bytes(output_data)
XTEA_DELTA_CONSTANT = 1640531527 # 0x61C88647
def calculate_encryption_v4_sequence(key_bytes): """计算加密轮次中 v4 值的序列。"""v4_sequence = [0] v4_current = 0 k = [ struct.unpack('<I', key_bytes[i*4 : i*4+4])[0] for i in range(4) ]
for round_index in range(32): k2_index = round_index & 3 k2 = k[k2_index] v4_update_term = (round_index ^ k2) - XTEA_DELTA_CONSTANT v4_current = (v4_current + v4_update_term) & 0xFFFFFFFF v4_sequence.append(v4_current)
return v4_sequence
def decrypt_block_round(v6_end, v5_end, v4_start_of_round, v4_end_of_round, round_index, key_bytes): """解密定制 XTEA 变种的一轮。"""v6_end = v6_end & 0xFFFFFFFF v5_end = v5_end & 0xFFFFFFFF v4_start_of_round = v4_start_of_round & 0xFFFFFFFF v4_end_of_round = v4_end_of_round & 0xFFFFFFFF
k = [ struct.unpack('<I', key_bytes[i*4 : i*4+4])[0] for i in range(4) ]
k3_index = (v4_end_of_round >> 11) & 3 k3 = k[k3_index] intermediate_term2 = v6_end + ((v6_end >> 5) ^ (v6_end << 4)) term2 = (k3 + v4_end_of_round) ^ intermediate_term2 v5_start = (v5_end - term2) & 0xFFFFFFFF
k1_index = v4_start_of_round & 3 k1 = k[k1_index] intermediate_term1 = v5_start + ((v5_start >> 5) ^ (v5_start << 4)) term1 = (k1 + v4_start_of_round) ^ intermediate_term1 v6_start = (v6_end - term1) & 0xFFFFFFFF
return (v6_start, v5_start)
def cbc_decrypt(ciphertext, key, iv): """使用定制 XTEA 变种以 CBC 模式解密密文。"""block_size = 8 # 字节 if len(ciphertext) % block_size != 0: print(f"错误:密文长度 ({len(ciphertext)}) 不是分组大小 ({block_size}) 的倍数。无法执行 CBC 解密。") sys.exit(1)
v4_sequence = calculate_encryption_v4_sequence(key)
plaintext_bytes = bytearray() previous_ciphertext_block = iv
for i in range(0, len(ciphertext), block_size): current_ciphertext_block = ciphertext[i : i + block_size]
c0, c1 = struct.unpack('<II', current_ciphertext_block)
decrypted_block_state = (c0, c1)
for reverse_round_index in range(31, -1, -1): v4_start = v4_sequence[reverse_round_index] v4_end = v4_sequence[reverse_round_index + 1]
decrypted_block_state = decrypt_block_round( decrypted_block_state[0], decrypted_block_state[1], v4_start, v4_end, reverse_round_index, key )
p_prime_bytes = struct.pack('<II', decrypted_block_state[0], decrypted_block_state[1])
plaintext_block = bytes([ p_prime_bytes[j] ^ previous_ciphertext_block[j] for j in range(block_size) ])
plaintext_bytes.extend(plaintext_block)
previous_ciphertext_block = current_ciphertext_block
return bytes(plaintext_bytes)if not UNK_10170 or len(UNK_10170) != 16: print("错误:请提供 UNK_10170 的 16 字节数据,需从 SO 文件中提取。")elif not UNK_10180 or len(UNK_10180) != 8: print("错误:请提供 UNK_10180 的 8 字节数据,需从 SO 文件中提取。")elif not TARGET_CIPHERTEXT or len(TARGET_CIPHERTEXT) != 40: # <-- 检查长度为 40 字节 print(f"错误:请提供 TARGET_CIPHERTEXT 的 **完整的 40 字节** 数据,需从 SO 文件中提取。") print(f"(当前提供的长度为 {len(TARGET_CIPHERTEXT)})")else: print("占位符数据似乎已提供。尝试解密...")
rc4_key_material = b"DoNotHackMe" S_box_for_73040 = rc4_ksa(rc4_key_material) KEY_QWORD_73040 = rc4_prga_sub_293E0(list(S_box_for_73040), UNK_10170)
S_box_for_73050 = rc4_ksa(rc4_key_material) KEY_QWORD_73050 = rc4_prga_sub_293E0(list(S_box_for_73050), UNK_10180)
print(f"派生出的加密密钥 (qword_73040): {KEY_QWORD_73040.hex()}") print(f"派生出的 IV (qword_73050): {KEY_QWORD_73050.hex()}") print(f"目标密文 (40字节): {TARGET_CIPHERTEXT.hex()}")
decrypted_padded_plaintext = cbc_decrypt(TARGET_CIPHERTEXT, KEY_QWORD_73040, KEY_QWORD_73050)
plaintext = decrypted_padded_plaintext.rstrip(b'\x00')
print(f"\n解密后的填充明文 (40 字节): {decrypted_padded_plaintext.hex()}") # <-- 输出提示长度为 40
try: flag = plaintext.decode('utf-8') print(f"恢复的 Flag (解码为 UTF-8): {flag}") except UnicodeDecodeError: print(f"无法将恢复的明文解码为 UTF-8。") print(f"恢复的明文字节 (十六进制): {plaintext.hex()}") except Exception as e: print(f"在最终处理过程中发生未知错误: {e}")
# palu{thiS_T1Me_it_seeM5_tO_8e_ReAl_te@}Game
迷宫,bfs求解最短路径
但这道题明显多解,我最开始写的那个代码死活不对(其实也是AI给的)。。。
然后叫ai给我出了一个全解代码(一开始是128个答案,但题目后面给的hint把范围缩小到64个了)
import collectionsimport itertoolsimport hashlib
# Keep the original BFS to get distances, but modify it slightly or create a new one# to just compute the distance grid efficiently.def bfs_get_dist_grid(grid, start): """ 使用 BFS 计算从 start 到所有可达点的最短距离,并返回距离网格。 """rows = len(grid) cols = len(grid[0]) dist = [[float('inf') for _ in range(cols)] for _ in range(rows)] # 使用 inf 表示不可达或未访问
queue = collections.deque([(start[0], start[1])]) # (row, col) dist[start[0]][start[1]] = 0
dr = [-1, 1, 0, 0] # 方向:上, 下, 左, 右 dc = [0, 0, -1, 1]
# 可行走的字符集合 (保持与之前一致) walkable_chars = {'0', ' ', 'X', 'Y'}
while queue: r, c = queue.popleft()
for i in range(4): nr, nc = r + dr[i], c + dc[i]
if 0 <= nr < rows and 0 <= nc < cols and grid[nr][nc] != '#' and dist[nr][nc] == float('inf'): dist[nr][nc] = dist[r][c] + 1 queue.append((nr, nc))
return dist # 返回完整的距离网格
def get_all_shortest_paths(grid, start, end, dist_grid): """ 给定距离网格,递归回溯从 start 到 end 的所有最短路径。 """if dist_grid[end[0]][end[1]] == float('inf'): return [] # 如果终点不可达
all_paths = []
# 递归回溯函数 def collect_paths(current_node, path_so_far): r, c = current_node
if (r, c) == start: all_paths.append(list(reversed(path_so_far + [current_node]))) # 到达起点,添加完整路径 (反转) return
# 探索邻居,只回溯到距离小 1 的邻居 (即在最短路径上) dr = [-1, 1, 0, 0] dc = [0, 0, -1, 1]
for i in range(4): pr, pc = r + dr[i], c + dc[i] # Parent row/col
# 检查边界 if 0 <= pr < len(grid) and 0 <= pc < len(grid[0]): # 确保是从距离小 1 的点过来的 if dist_grid[r][c] == dist_grid[pr][pc] + 1: # 检查这个点不是障碍 if grid[pr][pc] != '#': collect_paths((pr, pc), path_so_far + [current_node]) # 递归回溯
# 从终点开始回溯 collect_paths(end, [])
return all_paths # 返回所有找到的最短路径坐标列表
def coords_to_wasd(path_coords_list): """ 将坐标路径列表转换为 WASD 字符串。 (与之前相同) """wasd_string = "" for i in range(len(path_coords_list) - 1): r1, c1 = path_coords_list[i] r2, c2 = path_coords_list[i+1]
dr = r2 - r1 dc = c2 - c1
if dr == 1: wasd_string += 'S' # 向下 elif dr == -1: wasd_string += 'W' # 向上 elif dc == 1: wasd_string += 'D' # 向右 elif dc == -1: wasd_string += 'A' # 向左
return wasd_string
# 您提供的已经替换空格的迷宫地图字符串maze_string_0_filled = """##############################0##Y#0000000000000000000#0000000X#0#0#############0###0#0###0##0##0#000#000000000#0#000#000#000###0#####0#######0###0#0###0###0###000#000#00000#0#000#000#0#000#####0#0###0#####0#0#######0#0#####0#000#0#00000#0#00000#000#000###0#####0#0###0#0#0###0#0#####0###000000000#000#0#000#0000000#0###0#########0#0#0#############0###000#0#00000#0#000#00000000000#####0#0#0#########0#0#########0###0#0#0#00000000000#0#0000000#0###0#0#0#############0#0#####0#0###000#0000000#0000000#0#000#000##0X0##0###0###0#0#####0#0#########000#000#00000#000#0#0#000#000#####0###0#########0#0#0#0#0#0#0###0#0#000#000#000#0#0#0#0#0#0#0###0#0###0#0#0#0#0#0#0#0###0#0#0###0#000#0#0#000#000#000#000#0#0###0###0###0###########0#0#0#0#0###000#000#00000000000#0#0#0#0#0###0#####0#0#########0#0#0###0#0###000#000#000#0000000#0#0#000#0#####0#0#######0#######0#0#0###0###000#000#000#0#00000#0#000#000###0#####0#0#0#0#0#####0#####0#0###000000000#000#0000000000000#00#0X0############0X0###########0X#0##############0#############0#"""
# --- 解析迷宫字符串到网格 ---lines = maze_string_0_filled.strip().split('\n')grid = []expected_cols = 32
for i, line in enumerate(lines): if len(line) < expected_cols: processed_line = line.ljust(expected_cols, '0') elif len(line) > expected_cols: processed_line = line[:expected_cols] else: processed_line = line grid.append(list(processed_line))
rows = len(grid)cols = len(grid[0]) if grid else 0print(f"解析后的迷宫尺寸: {rows}x{cols}")
# 在网格中找到起始点 'Y' 和所有出口 'X' 的坐标start_coord = Noneexit_coords = []
for r in range(rows): for c in range(cols): if grid[r][c] == 'Y': start_coord = (r, c) elif grid[r][c] == 'X': exit_coords.append((r, c))
print(f"在地图中找到的起始点 Y: {start_coord}")print(f"在地图中找到的出口点 X: {exit_coords} (共 {len(exit_coords)} 个)")
# 在继续之前,验证是否找到一个 Y 和 5 个 Xif start_coord is None: print("\n错误:未在地图中找到起始点 Y。")elif len(exit_coords) != 5: print(f"\n错误:在地图中找到的出口数量不为 5 个,而是 {len(exit_coords)} 个。无法进行计算。")else: print("\n成功在地图中找到所有关键点 (1个 Y, 5个 X)。正在计算关键点之间的最短路径距离...")
# 预先计算所有关键点对之间的最短距离 points = [start_coord] + exit_coords dist_data = {} # 存储 ((p1_r, p1_c), (p2_r, p2_c)): distance
# 为了获取所有路径,我们需要从每个起点运行 BFS 来获取距离网格 # 存储从每个关键点出发的距离网格 dist_grids = {} for p in points: dist_grids[p] = bfs_get_dist_grid(grid, p)
# 填充 dist_data 使用计算好的距离网格 for i in range(len(points)): for j in range(i + 1, len(points)): p1 = points[i] p2 = points[j] # 从 p1 出发的距离网格中查找 p2 的距离 dist = dist_grids[p1][p2[0]][p2[1]] if dist != float('inf'): dist_data[(p1, p2)] = dist dist_data[(p2, p1)] = dist # 距离是双向的
# 找到访问所有出口的最短总路径 (从 Y 开始,遍历所有 X) min_total_dist = float('inf') optimal_permutations = [] # 存储所有达到最小距离的出口访问顺序
print("\n正在查找访问所有出口的最短总距离...")
for perm in itertools.permutations(exit_coords): current_total_dist = 0 current_point = start_coord valid_permutation = True
# 计算当前排列顺序的总距离 for next_point in perm: pair = (current_point, next_point) if pair not in dist_data: valid_permutation = False break # 某段不可达
current_total_dist += dist_data[pair] current_point = next_point
if valid_permutation: if current_total_dist < min_total_dist: min_total_dist = current_total_dist optimal_permutations = [perm] # 找到更短的,清空并记录新的 elif current_total_dist == min_total_dist: optimal_permutations.append(perm) # 找到同样短的,添加记录
print(f"\n计算出的访问所有出口的最短总路径距离 (从 Y 开始): {min_total_dist} 步。") print(f"提示的最短路径距离: 290 步。")
if min_total_dist == 290: print("计算结果与提示相符!正在生成所有最短路径的 WASD 序列...")
all_shortest_wasd_paths = set() # 使用集合存储唯一的 WASD 序列 (大写)
# 现在为每个达到最短总距离的排列组合生成所有可能的路径 for opt_perm in optimal_permutations: segment_paths_lists = [] # 存储每个路径段的所有最短路径列表
current_point = start_coord # 对于排列中的每个段 (Y -> X1, X1 -> X2, ...) for next_point in opt_perm: # 从当前点的距离网格中获取该段的所有最短路径 segment_dist_grid = dist_grids[current_point] # 调用新的函数获取所有路径 all_segment_paths = get_all_shortest_paths(grid, current_point, next_point, segment_dist_grid) if not all_segment_paths: segment_paths_lists = [] break segment_paths_lists.append(all_segment_paths) current_point = next_point
if segment_paths_lists: # 使用 itertools.product 组合所有路径段的所有可能组合 for path_combination in itertools.product(*segment_paths_lists): combined_path_coords = [] for i, segment_path in enumerate(path_combination): if i == 0: combined_path_coords.extend(segment_path) else: combined_path_coords.extend(segment_path[1:])
wasd_path = coords_to_wasd(combined_path_coords) # 生成大写 WASD 路径 all_shortest_wasd_paths.add(wasd_path)
# --- 筛选路径 (最后四步 SS DS), 转小写, 计算 MD5 --- filtered_md5s = [] filter_suffix_uppercase = "SSDS" # 筛选条件 (大写)
print(f"\n过滤路径:寻找最后四步为 '{filter_suffix_uppercase}' 的路径...")
# 遍历所有找到的最短路径 (大写) for wasd_path_uppercase in all_shortest_wasd_paths: # 检查路径是否足够长且以指定的后缀结束 if len(wasd_path_uppercase) >= len(filter_suffix_uppercase) and wasd_path_uppercase.endswith(filter_suffix_uppercase): # 将符合条件的路径转为小写 wasd_path_lowercase = wasd_path_uppercase.lower() # 计算小写路径的 MD5 哈希值 md5_hash = hashlib.md5(wasd_path_lowercase.encode('utf-8')).hexdigest() filtered_md5s.append(md5_hash)
# 排序筛选后的哈希值以便输出顺序稳定 sorted_filtered_md5s = sorted(filtered_md5s)
print(f"\n共找到 {len(sorted_filtered_md5s)} 条长度为 {min_total_dist} 且最后四步为 '{filter_suffix_uppercase}' 的唯一最短路径。") print("这些路径(转为小写后)的 MD5 哈希值如下:")
for i, md5_hash in enumerate(sorted_filtered_md5s): print(f"Filtered Path {i+1} MD5: {md5_hash}")
print("\n对应的达到最短总距离的出口访问顺序 (从 Y 出发) 可能有:") point_names = {start_coord: 'Y'} for i, coord in enumerate(exit_coords): point_names[coord] = f'X{i+1}'
for i, opt_perm in enumerate(optimal_permutations): path_points_sequence = [start_coord] + list(opt_perm) print(f"顺序 {i+1}: " + " -> ".join([f"{point_names.get(c, str(c))}{c}" for c in path_points_sequence]))
else: print("计算结果与提示不符,请检查地图、坐标或算法实现。") print("\n未能找到访问所有出口的有效路径或最短距离不为290。")一个个试,试到第41个对了
palu{990fd7773f450f1f13bf08a367fe95ea}
如果这篇文章对你有帮助,欢迎分享给更多人!
部分信息可能已经过时





















































































