NepCTF 2025
Solve
Realme
initterm与initterm_e都会执行起始指针First到结束指针Last中的方法(全局构造函数),后者会 捕获并处理 在构造函数中抛出的异常,前者若发生异常会直接崩溃

找到关键的smc函数,对着解密即可


Crackme
一道QT逆向,对于图形化逆向,主要找按钮发出的信号对应的槽函数,这题直接根据字符串定位sub_4021C0()
^[0-9a-fA-F]{2,}$是检测输入位16进制数,后面还有检测长度位16个十六进制数

qword_409040()函数对应sign()用来创建密钥与密文,用c加载libcrypto.dll,调用sign生成密钥与密文

aes_encrypt用了flat混淆,调试看有无魔改:AES加密在mixcolumn位置,将state异或了0x55
#include "aes.h"
#include <stdio.h>
#include <stdint.h>
#include <windows.h>
#include <iostream>
#include <vector>
#define AES128 1
static void phex(uint8_t *str) {
#if defined(AES256)
uint8_t len = 32;
#elif defined(AES192)
uint8_t len = 24;
#elif defined(AES128)
uint8_t len = 16;
#endif
unsigned char i;
for (i = 0; i < len; ++i)
printf("%.2x", str[i]);
printf("\n");
}
typedef int (*Func)(uint8_t *, int, uint8_t *); // 声明函数指针类型
int main() {
struct AES_ctx ctx;
std::string name[] = {
"qinzhang",
"mq",
"hd",
"chenmin",
"user",
"cjh",
"cyy",
"yw",
"liujun",
"ob",
"chenmei",
"ok",
"ltt",
"fgao",
"minzhao",
"haoliu",
"dp",
"zhangfang",
"kefuadmin",
"chenchao",
"jq",
"haozhang",
"zhangyan",
"xiulanyang",
"eo",
"liudan",
"minhuang",
"zhoujing",
"liyz",
"jzhang",
"sxy",
"liujie",
"wjj",
"zhoujie",
"wangk",
"lixiuzhen",
"zhangyl",
"lxy",
"hongyang",
"nu",
"liuyy",
"liangchen",
"lgr",
"cb",
"wangxiulan",
"kc",
"od",
"gzzhang",
"vh",
"chenf",
"taoliu",
"xb",
"wanggz",
"hmli",
"mm",
"xzzhang",
"wangbo",
"jhchen",
"cmli",
"danzhang",
"ligy",
"wangfy",
"jianhuawang",
"gn",
"wangx",
"wxm",
"liufang",
"km",
"hyang",
"xe",
"liwei",
"sp",
"xiuyingwu",
"danliu",
"yongzhou",
"hv",
"xinchen",
"test",
"wangsy",
"lb",
"xiuzhenyang",
"guilanli",
"liyun",
"yangxz",
"iq",
"huanliu",
"yuyingchen",
"zhanggl",
"meizhang",
"lsz",
"pingwang",
"wzq",
"ot",
"gh",
"lingli",
"tu",
"yumeiwang",
"zhangxh",
"ro",
"hchen"
};
HMODULE hDll = LoadLibraryA("libcrypto.dll");
if (!hDll) {
std::cerr << "LoadLibrary failed" << std::endl;
return 1;
}
// 获取函数地址
Func sign = (Func) GetProcAddress(hDll, "sign");
for (const auto &item: name) {
const auto &sss = item + "Showmaker11";
int len1 = sss.size();
std::vector<uint8_t> buffer{};
std::vector<uint8_t> key{};
buffer.resize(16);
key.resize(16);
int len2 = item.size();
sign((uint8_t *) sss.data(), len1, buffer.data());
sign((uint8_t *) item.data(), len2, key.data());
AES_init_ctx(&ctx, key.data());
AES_ECB_decrypt(&ctx, buffer.data());
phex(buffer.data());
std::cout<<std::endl;
}
printf("\n");
}
QRS
XSafe
一堆混淆与花指令,过了一部分,有些bug之后再修
import idapro
import idc
import ida_bytes
def get_number(ea, n):
print(idc.get_operand_value(ea, n))
def nop(start_addr, end_addr):
print(hex(start_addr), hex(end_addr))
for i in range(start_addr, end_addr):
ida_bytes.patch_byte(i, 0x90)
start_addr = end_addr
start_addr -= idc.get_item_size(start_addr)
def calladd5(start_addr, end_addr):
reg_map = {
'rax': 0,
'rcx': 1,
'rdx': 2,
'rsi': 6,
'rdi': 7,
'r8': 8,
'r10': 10,
'r11': 11,
'r14': 14
}
reg = {
0: 0, # rax
1: 0, # rcx
2: 0,
6: 0, # rsi
7: 0, # rdi
8: 0,
10: 0,
11: 0,
14: 0 # r14
}
ea = start_addr
while ea != end_addr:
idc.del_items(ea, flags=False)
idc.create_insn(ea)
if idc.print_insn_mnem(ea) == 'call':
is_end = idc.get_operand_value(ea, 0)
idc.create_insn(is_end)
if idc.get_bytes(ea + 1, 4) == b'\x00\x00\x00\x00' and idc.print_insn_mnem(is_end) == 'add':
is_end = ea + 5 + idc.get_operand_value(ea + 5, 1)
idc.create_insn(is_end)
if idc.print_insn_mnem(is_end) == 'pop':
if idc.get_operand_value(is_end, 1) == 0 or idc.get_operand_value(is_end, 1) == 1:
is_end += 1
else:
is_end += 2
nop(ea, is_end)
ea += idc.get_item_size(ea)
continue
if idc.print_insn_mnem(is_end) == 'add' and idc.get_operand_value(is_end, 0) == 4:
idc.create_insn(is_end + 5)
if idc.print_insn_mnem(is_end + 5) == 'retn':
is_end = ea + 5 + idc.get_operand_value(is_end, 1)
nop(ea, is_end)
elif idc.print_insn_mnem(ea) == 'jmp':
if idc.get_operand_type(ea, 0) == 1:
target = reg[idc.get_operand_value(ea, 0)]
offset = target - ea
if offset < 0:
offset = 0x100 + offset
else:
offset -= 2
if offset > 0x100:
ida_bytes.patch_bytes(ea - 3, b'\xE9' + offset.to_bytes(4, 'little', signed=True))
else:
ida_bytes.patch_bytes(ea, b'\xEB' + offset.to_bytes())
ea = target - idc.get_item_size(ea) # 结尾+1
print(hex(target))
else:
is_end = idc.get_operand_value(ea, 0)
idc.create_insn(is_end)
if idc.print_insn_mnem(is_end) == 'call' and ida_bytes.get_bytes(idc.get_operand_value(is_end, 0),
1) == b'\xc3':
nop(ea, is_end + 5)
else:
ea = is_end
elif idc.print_insn_mnem(ea) == 'push':
idc.create_insn(ea + 1)
if idc.print_insn_mnem(ea + 1) == 'call':
is_end = idc.get_operand_value(ea + 1, 0)
idc.create_insn(is_end)
if idc.print_insn_mnem(is_end) == 'pop' and idc.print_insn_mnem(is_end + 1) == 'pop':
is_end += 2
nop(ea, is_end)
idc.create_insn(ea + 2)
if idc.print_insn_mnem(ea + 2) == 'call':
is_end = idc.get_operand_value(ea + 2, 0)
idc.create_insn(is_end)
if idc.print_insn_mnem(is_end) == 'pop' and idc.print_insn_mnem(is_end + 2) == 'pop':
is_end += 4
nop(ea, is_end)
elif (idc.print_insn_mnem(ea) == 'jz' and idc.print_insn_mnem(ea + 2) == 'jnz' or
idc.print_insn_mnem(ea) == 'jnz' and idc.print_insn_mnem(
ea + 2) == 'jz') and idc.get_operand_value(ea, 0) == idc.get_operand_value(ea + 2, 0):
is_end = idc.get_operand_value(ea, 0)
nop(ea, is_end)
elif idc.print_insn_mnem(ea) == 'or':
is_end = ea + idc.get_item_size(ea)
idc.create_insn(is_end)
if idc.print_insn_mnem(is_end) == 'jnz' or idc.print_insn_mnem(is_end) == 'jmp':
is_end = idc.get_operand_value(is_end, 0)
elif idc.print_insn_mnem(is_end) == 'shr':
idc.create_insn(is_end + 4)
if idc.print_insn_mnem(is_end) == 'and':
is_end = is_end + idc.get_item_size(is_end)
while True:
idc.create_insn(is_end)
if idc.print_insn_mnem(is_end) == 'jnz' or idc.print_insn_mnem(is_end) == 'jmp':
is_end = idc.get_operand_value(is_end, 0)
nop(ea, is_end)
break
is_end += idc.get_item_size(is_end)
nop(ea, is_end) # 连mov一起nop需要ea-xxxxxx
elif idc.print_insn_mnem(ea) == 'sub' and idc.get_operand_value(ea, 0) == idc.get_operand_value(ea, 1):
is_end = ea
while True:
is_end += 1
idc.create_insn(is_end)
if idc.print_insn_mnem(is_end) == 'jz':
is_end = idc.get_operand_value(is_end, 0)
break
elif idc.print_insn_mnem(is_end) == 'jmp':
is_end = idc.get_operand_value(is_end, 0)
break
nop(ea, is_end) # 连mov一起nop需要ea-xxxxxx
elif idc.print_insn_mnem(ea) == 'movq' and idc.get_operand_value(ea, 0) == 10:
is_end = ea
while True:
is_end += idc.get_item_size(is_end)
idc.create_insn(is_end)
if idc.print_insn_mnem(is_end) == 'sub':
while True:
is_end += idc.get_item_size(is_end)
idc.create_insn(is_end)
if idc.print_insn_mnem(is_end) == 'jnz' or idc.print_insn_mnem(is_end) == 'jmp':
is_end = idc.get_operand_value(is_end, 0)
break
break nop(ea, is_end)
elif idc.print_insn_mnem(ea) == 'add':
if idc.get_operand_type(ea, 1) == 1 and idc.get_operand_type(ea, 0) == 1:
reg[idc.get_operand_value(ea, 0)] = (reg[idc.get_operand_value(ea, 1)] + reg[
idc.get_operand_value(ea, 0)]) & 0xffffffffffffffff
print(hex(ea), ': add ', idc.get_operand_value(ea, 0), idc.get_operand_value(ea, 1), reg)
if idc.get_operand_value(ea, 0) == idc.get_operand_value(ea, 1):
is_end = ea + idc.get_item_size(ea)
while True:
idc.create_insn(is_end)
if idc.print_insn_mnem(is_end) == 'jnz' or idc.print_insn_mnem(is_end) == 'jmp':
is_end = idc.get_operand_value(is_end, 0)
nop(ea, is_end)
break
is_end += idc.get_item_size(is_end)
elif idc.print_insn_mnem(ea) == 'mov':
idc.create_insn(ea + 3)
if idc.print_insn_mnem(ea + 3) == 'and' and idc.get_operand_value(ea, 0) == idc.get_operand_value(
ea + 3, 0):
is_end = ea + 4
while True:
idc.create_insn(is_end)
if idc.print_insn_mnem(is_end) == 'jz':
is_end = idc.get_operand_value(is_end, 0)
break
is_end += 1
nop(ea, is_end)
elif idc.get_operand_value(ea, 0) in reg:
if idc.get_operand_type(ea, 1) == 5: # 常数赋值
reg[idc.get_operand_value(ea, 0)] = idc.get_operand_value(ea, 1)
elif idc.get_operand_type(ea, 1) == 2:
reg[idc.get_operand_value(ea, 0)] = idc.get_qword(idc.get_operand_value(ea, 1))
elif idc.get_operand_type(ea, 1) == 3:
if idc.get_operand_value(ea, 1) == 4:
if idc.get_operand_value(ea, 0) == 0:
reg1 = reg_map['rax']
reg2 = reg_map['rcx']
elif idc.get_operand_value(ea, 0) == 2:
reg1 = reg_map['rdx']
reg2 = reg_map['rcx']
elif idc.get_operand_value(ea, 1) == 12:
if idc.get_operand_value(ea, 0) == 8:
reg1 = reg_map['r8']
reg2 = reg_map['r10']
elif idc.get_operand_value(ea, 0) == 11:
reg1 = reg_map['r10']
reg2 = reg_map['r11']
reg[idc.get_operand_value(ea, 0)] = idc.get_qword((reg[reg1] + reg[reg2]) & 0xffffffffffffffff)
print(hex(ea), ': mov ', idc.get_operand_value(ea, 0), idc.get_operand_value(ea, 1), reg)
ea += idc.get_item_size(ea)
if __name__ == '__main__':
file_name = r"D:\ctf\ctf_game\2025\7\NepNep\XSafe.sys.i64"
print(f"Opening database {file_name}...")
# Open database, analyze it and close it at last
idapro.open_database(file_name, True)
calladd5(0x140015e5e, 0x140144DCF)
idapro.close_database(False)
What I have learned
- 查看initterm与initterm_e有无可疑函数时,一定要仔细看,每个函数偏移都看一看
- aes加密在调试时,会因为列优先排序,行优先排序导致每一次的state的结果不同,但最终结果相同,且不同的结果也仅仅是行变化与列变化的区别,用tiny-aes调试魔改时可以肉眼转化一下,如果发现对不上,再细看此步魔改了什么
- 如何主动调用dll中的函数
- QT逆向,见learn learn QT Reverse

浙公网安备 33010602011771号