1. 静态调试 - baby
逆向入门题。
解题脚本
input_string = "welcome_to_2024hustncc!!!!" str1 = [ 0x1, 0x8, 0x0F, 0x18, 0x1B, 0x5, 0x0C, 0x2C, 0x2B, 0x6, 0x2C, 0x6D, 0x51, 0x6D, 0x47, 0x1, 0x18, 0x3, 0x18, 0x0B, 0x3C, 0x11, 0x44, 0x57, 0x0F, 0x5C ] # 执行逐位异或 result = [] for i in range(len(input_string)): result.append(chr(ord(input_string[i]) ^ str1[i])) print(''.join(result)) # vmc{this_is_a_simple_rev.}
2. 动态调试 - game
逆向选修课原题,原本是用动态调试修改判断条件,绕过分数限制,直接打印 flag。但还可以直接看打印 flag 的函数,重写成脚本即可获取 flag。
解题脚本
def sub_403B93(): v2 = [ 158, 149, 99, 155, 113, 157, 81, 156, 109, 103, 97, 103, 110, 157, 150, 150, 153, 103, 111, 92, 149, 109, 103, 105, 147, 150, 124, 103, 105, 156, 133] for i in range(len(v2)): v1 = 0 v1 ^= v2[i] v1 ^= 0x14 v1 -= 20 print(chr(v1), end='') if __name__ == "__main__": sub_403B93() # vmc{Qu1te_a_funny_g4me_isnT_it}
3. 迷宫逆向 - maze
逆向选修课原题,题解参考 《maze程序分析》
解题脚本
from collections import deque def find_shortest_path(s, start_index): # 定义四种可能的移动 moves = { 's': 13, # 向前移动一步 'd': 1, # 向后移动一步 'w': -13, # 向前移动13步 'a': -1 # 向后移动13步 } # 队列保存 (当前索引, 当前路径) queue = deque([(start_index, '')]) visited = set() # 记录已访问的索引,防止重复访问 while queue: index, path = queue.popleft() # 如果当前字符是 '#', 返回当前路径 if s[index] == '#': return path # 遍历所有可能的移动 for move, delta in moves.items(): new_index = index + delta if 0 <= new_index < len(s) and s[new_index] != '*' and new_index not in visited: visited.add(new_index) queue.append((new_index, path + move)) # 如果没有找到路径,返回空字符串 return "" # 定义输入字符串 s = "@@***********-***-**-*****--*****-*****-***#**-*****--**----******-*****-******-****--******---**-*******-*-----******-------*****************" # 从第二个字符开始,即索引1 start_index = 1 # 查找最短路径 shortest_path = find_shortest_path(s, start_index) # 打印最短路径 print("Shortest Path:", shortest_path) # assssdsssddsdddwwdwwaaaw # 最终flag是:vmc{assssdsssddsdddwwdwwaaaw}
4.c++ 语言特性 - stl
逆向选修课原题,但是不会
5. 加密算法 - enc
这一题比较麻烦,需要分析多个函数,并写出其逆函数。我的做题过程是:
1. 进行静态分析,发现程序用 sub_401CB1
函数和 char v5[176]
数组对输入字符串 Str
进行加密,并把密文与 Buf2
比较。那么我们的思路就是写出 sub_401CB1
的逆函数,并输入 char v5[176]
和 Buf2
进行解密。
2. 进行 ida 动态调试,定位 char v5[176]
在栈上的位置,用 IDApython 脚本输出内容,拿到 char v5[176]
数据。
3. 静态分析 sub_401CB1
函数,写出 sub_401B47
、sub_4018C8
、sub_401975
、sub_40181E
的逆函数。
– 由于 sub_40181E
直接进行异或操作,所以逆函数就是它本身 – 因为 sub_401B47
进行的是轮换操作,所以逆函数就是倒着轮换 – sub_4018C8
函数进行查表代换,所以逆函数把值和索引调换重新建表,再查表就行 –sub_401975
函数按照四个字符一组的顺序进行复杂的异或操作,这个我不知道怎么直接解开,只能枚举一个值,然后用代换关系推出其他三个值,再把这四个字符重新加密,如果与给定的密文相同,则成功解密
解题脚本
#include <stdio.h> #include <cstring> unsigned char byte_405000[] = { 0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 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, 0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75, 0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84, 0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF, 0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8, 0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2, 0xCD, 0x0C, 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, 0x0B, 0xDB, 0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 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, 0x08, 0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A, 0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 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, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16}; char reverse_map[256]; void generate_reverse_map(void) { for (int i = 0; i < 256; i++) { reverse_map[(unsigned char)byte_405000[i]] = (char)i; } } void sub_40181E(int a1, char *a2, char *a3) { for (int i = 0; i <= 3; ++i) for (int j = 0; j <= 3; ++j) a2[j + 4 * i] ^= a3[4 * (4 * a1 + i) + j]; return; } void sub_4018C8_reverse(char *a1) { for (int i = 0; i <= 3; ++i) { for (int j = 0; j <= 3; ++j) { a1[4 * j + i] = reverse_map[(unsigned char)a1[4 * j + i]]; } } } void sub_401B47_reverse(char *a1) { char v2; v2 = a1[13]; a1[13] = a1[9]; a1[9] = a1[5]; a1[5] = a1[1]; a1[1] = v2; v2 = a1[10]; a1[10] = a1[2]; a1[2] = v2; v2 = a1[14]; a1[14] = a1[6]; a1[6] = v2; v2 = a1[7]; a1[7] = a1[11]; a1[11] = a1[15]; a1[15] = a1[3]; a1[3] = v2; } unsigned char sub_401948(unsigned char a1) { return (2 * a1) ^ (27 * (unsigned int)(a1 >> 7)); } unsigned char sub_401948_reverse(unsigned char a1) { if (a1 & 1) return ((a1 ^ 27) >> 1) + 128; return a1 >> 1; } void sub_401975_part(char *a1) { char v2 = a1[0]; char v1 = a1[0] ^ a1[1] ^ a1[2] ^ a1[3]; a1[0] ^= v1 ^ sub_401948(a1[0] ^ a1[1]); a1[1] ^= v1 ^ sub_401948(a1[1] ^ a1[2]); a1[2] ^= v1 ^ sub_401948(a1[2] ^ a1[3]); a1[3] ^= v1 ^ sub_401948(a1[3] ^ v2); } void sub_401975_reverse(char *a1) { char v1, tmp[5] = {0}; unsigned char ans[5] = {0}; for (int i = 0; i <= 3; ++i) { v1 = a1[4 * i] ^ a1[4 * i + 1] ^ a1[4 * i + 2] ^ a1[4 * i + 3]; for (int j = 0; j < 256; ++j) { ans[0] = j; ans[1] = sub_401948_reverse(a1[4 * i] ^ v1 ^ ans[0]) ^ ans[0]; ans[2] = sub_401948_reverse(a1[4 * i + 1] ^ v1 ^ ans[1]) ^ ans[1]; ans[3] = sub_401948_reverse(a1[4 * i + 2] ^ v1 ^ ans[2]) ^ ans[2]; memcpy(tmp, (char *)ans, 5); sub_401975_part(tmp); if (memcmp(tmp, a1 + 4 * i, 4) == 0) { memcpy(a1 + 4 * i, ans, 4); break; } } } } void sub_401C2A_revese(char *a1, char *a2) { generate_reverse_map(); sub_40181E(10, a1, a2); sub_401B47_reverse(a1); sub_4018C8_reverse(a1); for (int i = 9; i >= 1; --i) { sub_40181E(i, a1, a2); sub_401975_reverse(a1); sub_401B47_reverse(a1); sub_4018C8_reverse(a1); } sub_40181E(0, a1, a2); return; } unsigned char v5[] = { 0x27, 0x76, 0xC4, 0xEA, 0x40, 0xC1, 0xB6, 0xD2, 0x83, 0x8F, 0x3D, 0xDB, 0xFA, 0xB7, 0x8F, 0xB1, 0x8F, 0x05, 0x0C, 0xC7, 0xCF, 0xC4, 0xBA, 0x15, 0x4C, 0x4B, 0x87, 0xCE, 0xB6, 0xFC, 0x08, 0x7F, 0x3D, 0x35, 0xDE, 0x89, 0xF2, 0xF1, 0x64, 0x9C, 0xBE, 0xBA, 0xE3, 0x52, 0x08, 0x46, 0xEB, 0x2D, 0x63, 0xDC, 0x06, 0xB9, 0x91, 0x2D, 0x62, 0x25, 0x2F, 0x97, 0x81, 0x77, 0x27, 0xD1, 0x6A, 0x5A, 0x55, 0xDE, 0xB8, 0x75, 0xC4, 0xF3, 0xDA, 0x50, 0xEB, 0x64, 0x5B, 0x27, 0xCC, 0xB5, 0x31, 0x7D, 0x90, 0x19, 0x47, 0x3E, 0x54, 0xEA, 0x9D, 0x6E, 0xBF, 0x8E, 0xC6, 0x49, 0x73, 0x3B, 0xF7, 0x34, 0x52, 0x71, 0x5F, 0xB1, 0x06, 0x9B, 0xC2, 0xDF, 0xB9, 0x15, 0x04, 0x96, 0xCA, 0x2E, 0xF3, 0xA2, 0x23, 0x7C, 0x65, 0xC5, 0x25, 0xE7, 0xA7, 0x1A, 0x9C, 0xF2, 0xA3, 0x8C, 0x56, 0xDC, 0x50, 0x2E, 0x25, 0x2F, 0x54, 0x74, 0x00, 0xC8, 0xF3, 0x6E, 0x9C, 0x3A, 0x50, 0xE2, 0xCA, 0xE6, 0x00, 0xCC, 0xB0, 0x4C, 0x1F, 0x00, 0xB0, 0x84, 0xEC, 0x6E, 0x2C, 0xBE, 0xBC, 0x8C, 0xE6, 0x58, 0xBC, 0x40, 0xEC, 0x29, 0x16, 0x8E, 0x5C, 0xAD, 0xFA, 0xE0, 0x70, 0x13, 0x46, 0x6C, 0x96, 0x4B, 0xFA, 0x2C}; char Buf2[16] = {-6, -91, 6, 63, -3, -65, 37, 69, 29, -64, 94, 63, 94, -28, -71, -107}; int main() { sub_401C2A_revese(Buf2, (char *)v5); puts(Buf2); return 0; } // aa8e45738c8e5096 // 最终flag是:vmc{aa8e45738c8e5096}
6. 飞翔的小鸟 - bird
这一题感觉比前几题简单
解题脚本
def print_(a1): for char in a1: if ord(char) <= 96 or ord(char) > 122: print(char, end='') else: transformed_char = chr((ord(char) - 84) % 26 + 97) print(transformed_char, end='') if __name__ == "__main__": flag = "izp{lbh_ner_fb_pyrire}" print_(flag) # vmc{you_are_so_clever}
7. 我的 ida 怎么坏了 - asm - 花指令
在分析这一题的反汇编时,会遇到下面问题:
- 指令
jz short near ptr loc_4010AE+1
和jnz short near ptr loc_4010AE+1
的跳转目标位置有点奇怪。 - 在地址
loc_4010AE
处存在一条奇怪的汇编指令:jmp near ptr 33D45E40h
,而loc_4010AE
本身包含了一个字节,设置为E8
(即jmp
指令)。 - 此
jmp
指令使得反汇编工具在分析过程中将0x4010AE
后的4
个字节识别为地址,导致后续的汇编代码难以正常解析。
经过进一步分析,我们发现可以通过将 0x4010AE
字节修改为 nop
,使得反汇编能够正常进行并恢复原有的指令流。
解题脚本
ida_chars = bytearray([ 0x3E, 0x0D, 0xED, 0xBE, 0x4A, 0x8A, 0x7D, 0xBC, 0x7C, 0xFC, 0x2E, 0x2A, 0x79, 0x9D, 0x6A, 0x1A, 0xCC, 0x3D, 0x4A, 0xF8, 0x3C, 0x79, 0x69, 0x39, 0xD9, 0xDD, 0x9D, 0xA9, 0x69, 0x4C, 0x8C, 0xDD, 0x59, 0xE9, 0xD7 ]) def swap_nibbles_and_subtract(): # 交换每个字节的高 4 位和低 4 位 for i in range(len(ida_chars)): ida_chars[i] = ((ida_chars[i] & 0x0F) << 4) | ( (ida_chars[i] & 0xF0) >> 4) # 从倒数第二个字节开始,依次执行 ida_chars[i] -= ida_chars[i+1] for i in range(len(ida_chars) - 2, -1, -1): ida_chars[i] -= ida_chars[i + 1] if __name__ == "__main__": swap_nibbles_and_subtract() for char in ida_chars: print(chr(char), end='') # 将每个字节转换为字符输出 # vmc{p4tch_pr0gr4m_t0_d3c0mpi1e_it!}
8. 朴实无华的 vm-vm - 虚拟机逆向
这一题较为繁琐,首先看懂 vm 指令集,再看懂要执行的指令,再写出逆函数进行还原。我的做题过程是: 1. 静态分析 vm,发现经典的 switch 结构,总共 12 条指令,较为关键的是指令2
、指令10
、指令11
,指令2
从输入数组 dest 中获取值,指令10
进行判断并设置标志位、指令11
查看标志位并进行跳转。
2. 分析 ptr 中虚拟机代码:
"03 00 00 00 00 00" // mov $0, %%reg0 "03 00 00 00 00 01" // mov $0, %%reg1 "03 00 00 00 00 02" // mov $0, %%reg2 "03 47 00 00 00 03" // mov $0x47, %%reg3 "03 25 5b 00 00 04" // mov $0x5B25, %%reg4 "03 91 e5 25 01 05" // mov $0x0125E591, %%reg5 "03 7f 00 00 00 06" // mov $0x7F, %%reg6 "03 18 00 00 00 07" // mov $0x18, %%reg7 check: "02 02 00" // mov (%%reg2), %%reg0 "09 02" // inc %%reg2 "01 03 01" // mov %%reg3, %%reg1 "06 04 01" // mul %%reg4, %%reg1 "04 05 01" // add %%reg5, %%reg1 "08 06 01" // mod %%reg6, %%reg1 "01 01 03" // mov %%reg1, %%reg3 "0A 00 01" // cmp %reg0, %reg1 "0B 0A" // jnz exit "0A 02 07" // cmp %reg2, %reg7 "0B de" // jnz check "0C" // tag = 1 exit: " 00 00"; // end // 以伪代码形式表示 random = 0x47 for i in range(24): random = (random * 0x5B25 + 0x0125E591) % 0x7F assest(dest[i] == random)
解题脚本
dest = [ 0x42, 0x3F, 0x53, 0x24, 0x2C, 0x66, 0x5D, 0x5F, 0x7F, 0x0A, 0x27, 0x39, 0x5F, 0x1D, 0x0B, 0x0F, 0x2E, 0x00, 0x6B, 0x2B, 0x5B, 0x19, 0x5C, 0x41 ] output = [] random = 0x47 for i in range(24): random = (random * 0x5B25 + 0x0125E591) % 0x7F char = chr(random ^ dest[i]) output.append(char) print("".join(output)) # vmc{It_is_an_amazing_vm}