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}