







-
0x413824处的指令jmp qword ptr cs:45BF481Dh非法,查找交叉引用发现0x413826处有跳转到这里的条件分支,因此将该处前2个字节patch为ud2指令。 -
0x41669E处的两个00直接patch为nop指令。 -
0x41878D处patch一个ud2指令加上9字节的nop,否则会干扰sub_418779的函数分析。
综上所述,我们可以编写出处理花指令混淆的解混淆idc脚本:
#include <idc.idc>
static main()
{
auto seg, current_ea, ea;
// 遍历所有段
for (seg = get_first_seg(); seg != BADADDR; seg = get_next_seg(seg))
{
auto seg_name = get_segm_name(seg);
// 检查段名是否符合要求
if (seg_name != ".text" && seg_name != "UPX0")
{
//Message("跳过段: %s (0x%X)n", seg_name, seg);
continue;
}
Message("正在处理段: %s (0x%X)n", seg_name, seg);
// 获取段的起始和结束地址
auto start_ea = seg;
auto end_ea = get_segm_end(seg);
current_ea = start_ea;
auto pattern1 = "0F 84 01 00 00 00";
while (1)
{
ea = find_binary(current_ea, SEARCH_DOWN, pattern1);
if (ea > end_ea || ea == BADADDR)
break;
Message("Found pattern1 at 0x%Xn", ea);
patch_byte(ea + 6, 0x90);
current_ea = ea + 1;
}
current_ea = start_ea;
auto pattern2 = "0F 84 02 00 00 00";
while (1)
{
ea = find_binary(current_ea, SEARCH_DOWN, pattern2);
if (ea > end_ea || ea == BADADDR)
break;
Message("Found pattern2 at 0x%Xn", ea);
patch_word(ea + 6, 0x9090);
current_ea = ea + 1;
}
current_ea = start_ea;
auto pattern3 = "0F 84 08 00 00 00";
while (1)
{
ea = find_binary(current_ea, SEARCH_DOWN, pattern3);
if (ea > end_ea || ea == BADADDR)
break;
Message("Found pattern3 at 0x%Xn", ea);
patch_qword(ea + 6, 0x9090909090909090);
current_ea = ea + 1;
}
current_ea = start_ea;
auto pattern4 = "0F 8D 06 00 00 00";
while (1)
{
ea = find_binary(current_ea, SEARCH_DOWN, pattern4);
if (ea > end_ea || ea == BADADDR)
break;
Message("Found pattern4 at 0x%Xn", ea);
patch_word(ea + 6, 0x0B0F);
patch_dword(ea + 8, 0x90909090);
current_ea = ea + 1;
}
current_ea = start_ea;
auto pattern5 = "E9 06 00 00 00 00";
while (1)
{
ea = find_binary(current_ea, SEARCH_DOWN, pattern5);
if (ea > end_ea || ea == BADADDR)
break;
Message("Found pattern5 at 0x%Xn", ea);
patch_word(ea + 5, 0x0B0F);
patch_dword(ea + 7, 0x90909090);
current_ea = ea + 1;
}
current_ea = start_ea;
auto pattern6 = "EB E9";
while (1)
{
ea = find_binary(current_ea, SEARCH_DOWN, pattern6);
if (ea > end_ea || ea == BADADDR)
break;
Message("Found pattern6 at 0x%Xn", ea);
patch_word(ea, 0x0B0F);
patch_dword(ea + 2, 0x90909090);
current_ea = ea + 1;
}
current_ea = start_ea;
auto pattern7 = "3C E9";
while (1)
{
ea = find_binary(current_ea, SEARCH_DOWN, pattern7);
if (ea > end_ea || ea == BADADDR)
break;
Message("Found pattern7 at 0x%Xn", ea);
patch_word(ea + 2, 0x0B0F); // ud2
patch_word(ea + 4, 0x9090); // nop
current_ea = ea + 1;
}
}
patch_word(0x413824, 0x0B0F);
patch_word(0x41669E, 0x0B0F);
patch_word(0x41878D, 0x0B0F);
patch_qword(0x41878F, 0x9090909090909090);
patch_byte(0x418797, 0x90);
Message("Finished.n");
}




#include <idc.idc>
static main()
{
auto seg, current_ea, mnemonic, op1, op2;
// 遍历所有段
for (seg = get_first_seg(); seg != BADADDR; seg = get_next_seg(seg))
{
auto seg_name = get_segm_name(seg);
// 检查段名是否符合要求
if (seg_name != ".text" && seg_name != "UPX0")
{
//Message("跳过段: %s (0x%X)n", seg_name, seg);
continue;
}
Message("正在处理段: %s (0x%X)n", seg_name, seg);
// 获取段的起始和结束地址
auto start_ea = seg;
auto end_ea = get_segm_end(seg);
// 遍历段中的每一条指令
current_ea = start_ea;
while (current_ea < end_ea && current_ea != BADADDR)
{
// 获取指令的助记符和操作数
mnemonic = print_insn_mnem(current_ea);
op1 = print_operand(current_ea, 0);
op2 = print_operand(current_ea, 1);
// 检查是否是目标指令
if (mnemonic == "movsx" && op2 == "cs:byte0")
{
Message("Target Ins at: 0x%Xn", current_ea);
if (op1 == "eax")
{
patch_byte(current_ea, 0xB8); // mov eax, imm32 的操作码
}
else if (op1 == "ecx")
{
patch_byte(current_ea, 0xB9); // mov ecx, imm32 的操作码
}
else if (op1 == "edx")
{
patch_byte(current_ea, 0xBA); // mov edx, imm32 的操作码
}
// 设置 imm32 = 0
patch_dword(current_ea + 1, 0); // imm32 = 0
patch_word(current_ea + 5, 0x9090); // nop
create_insn(current_ea); // 重新分析指令
}
// 检查是否是目标指令
if (mnemonic == "movsx" && op2 == "cs:byte1")
{
Message("Target Ins at: 0x%Xn", current_ea);
if (op1 == "eax")
{
patch_byte(current_ea, 0xB8); // mov eax, imm32 的操作码
}
else if (op1 == "ecx")
{
patch_byte(current_ea, 0xB9); // mov ecx, imm32 的操作码
}
else if (op1 == "edx")
{
patch_byte(current_ea, 0xBA); // mov edx, imm32 的操作码
}
// 设置 imm32 = 1
patch_dword(current_ea + 1, 1); // imm32 = 1
patch_word(current_ea + 5, 0x9090); // nop
create_insn(current_ea); // 重新分析指令
}
// 检查是否是目标指令
if (mnemonic == "movsx" && op2 == "cs:byte2")
{
Message("Target Ins at: 0x%Xn", current_ea);
if (op1 == "eax")
{
patch_byte(current_ea, 0xB8); // mov eax, imm32 的操作码
}
else if (op1 == "ecx")
{
patch_byte(current_ea, 0xB9); // mov ecx, imm32 的操作码
}
else if (op1 == "edx")
{
patch_byte(current_ea, 0xBA); // mov edx, imm32 的操作码
}
// 设置 imm32 = 2
patch_dword(current_ea + 1, 2); // imm32 = 2
patch_word(current_ea + 5, 0x9090); // nop
create_insn(current_ea); // 重新分析指令
}
// 检查是否是目标指令
if (mnemonic == "movsx" && op2 == "cs:byte3")
{
Message("Target Ins at: 0x%Xn", current_ea);
if (op1 == "eax")
{
patch_byte(current_ea, 0xB8); // mov eax, imm32 的操作码
}
else if (op1 == "ecx")
{
patch_byte(current_ea, 0xB9); // mov ecx, imm32 的操作码
}
else if (op1 == "edx")
{
patch_byte(current_ea, 0xBA); // mov edx, imm32 的操作码
}
// 设置 imm32 = 3
patch_dword(current_ea + 1, 3); // imm32 = 3
patch_word(current_ea + 5, 0x9090); // nop
create_insn(current_ea); // 重新分析指令
}
// 检查是否是目标指令
if (mnemonic == "movsx" && op2 == "cs:byte4")
{
Message("Target Ins at: 0x%Xn", current_ea);
if (op1 == "eax")
{
patch_byte(current_ea, 0xB8); // mov eax, imm32 的操作码
}
else if (op1 == "ecx")
{
patch_byte(current_ea, 0xB9); // mov ecx, imm32 的操作码
}
else if (op1 == "edx")
{
patch_byte(current_ea, 0xBA); // mov edx, imm32 的操作码
}
// 设置 imm32 = 4
patch_dword(current_ea + 1, 4); // imm32 = 4
patch_word(current_ea + 5, 0x9090); // nop
create_insn(current_ea); // 重新分析指令
}
// 检查是否是目标指令
if (mnemonic == "movsx" && op2 == "cs:byte5")
{
Message("Target Ins at: 0x%Xn", current_ea);
if (op1 == "eax")
{
patch_byte(current_ea, 0xB8); // mov eax, imm32 的操作码
}
else if (op1 == "ecx")
{
patch_byte(current_ea, 0xB9); // mov ecx, imm32 的操作码
}
else if (op1 == "edx")
{
patch_byte(current_ea, 0xBA); // mov edx, imm32 的操作码
}
// 设置 imm32 = 5
patch_dword(current_ea + 1, 5); // imm32 = 5
patch_word(current_ea + 5, 0x9090); // nop
create_insn(current_ea); // 重新分析指令
}
// 检查是否是目标指令
if (mnemonic == "movsx" && op2 == "cs:byte6")
{
Message("Target Ins at: 0x%Xn", current_ea);
if (op1 == "eax")
{
patch_byte(current_ea, 0xB8); // mov eax, imm32 的操作码
}
else if (op1 == "ecx")
{
patch_byte(current_ea, 0xB9); // mov ecx, imm32 的操作码
}
else if (op1 == "edx")
{
patch_byte(current_ea, 0xBA); // mov edx, imm32 的操作码
}
// 设置 imm32 = 6
patch_dword(current_ea + 1, 6); // imm32 = 6
patch_word(current_ea + 5, 0x9090); // nop
create_insn(current_ea); // 重新分析指令
}
// 检查是否是目标指令
if (mnemonic == "movsx" && op2 == "cs:byte7")
{
Message("Target Ins at: 0x%Xn", current_ea);
if (op1 == "eax")
{
patch_byte(current_ea, 0xB8); // mov eax, imm32 的操作码
}
else if (op1 == "ecx")
{
patch_byte(current_ea, 0xB9); // mov ecx, imm32 的操作码
}
else if (op1 == "edx")
{
patch_byte(current_ea, 0xBA); // mov edx, imm32 的操作码
}
// 设置 imm32 = 7
patch_dword(current_ea + 1, 7); // imm32 = 7
patch_word(current_ea + 5, 0x9090); // nop
create_insn(current_ea); // 重新分析指令
}
// 检查是否是目标指令
if (mnemonic == "movsx" && op2 == "cs:byte8")
{
Message("Target Ins at: 0x%Xn", current_ea);
if (op1 == "eax")
{
patch_byte(current_ea, 0xB8); // mov eax, imm32 的操作码
}
else if (op1 == "ecx")
{
patch_byte(current_ea, 0xB9); // mov ecx, imm32 的操作码
}
else if (op1 == "edx")
{
patch_byte(current_ea, 0xBA); // mov edx, imm32 的操作码
}
// 设置 imm32 = 8
patch_dword(current_ea + 1, 8); // imm32 = 0
patch_word(current_ea + 5, 0x9090); // nop
create_insn(current_ea); // 重新分析指令
}
// 检查是否是目标指令
if (mnemonic == "movsx" && op2 == "cs:byte9")
{
Message("Target Ins at: 0x%Xn", current_ea);
if (op1 == "eax")
{
patch_byte(current_ea, 0xB8); // mov eax, imm32 的操作码
}
else if (op1 == "ecx")
{
patch_byte(current_ea, 0xB9); // mov ecx, imm32 的操作码
}
else if (op1 == "edx")
{
patch_byte(current_ea, 0xBA); // mov edx, imm32 的操作码
}
// 设置 imm32 = 9
patch_dword(current_ea + 1, 9); // imm32 = 9
patch_word(current_ea + 5, 0x9090); // nop
create_insn(current_ea); // 重新分析指令
}
// 移动到下一条指令(避免死循环)
auto next_ea = next_head(current_ea, end_ea);
if (next_ea == BADADDR || next_ea <= current_ea)
break;
current_ea = next_ea;
}
}
Message("Finished.n");
}
执行后发现程序反编译得到的代码中去除了很多用于混淆的永真永假分支:
staticmain()
{
auto seg, current_ea, mnemonic, op1, op2;
// 遍历所有段
for (seg = get_first_seg(); seg != BADADDR; seg = get_next_seg(seg))
{
auto seg_name = get_segm_name(seg);
// 检查段名是否符合要求
if (seg_name != ".text" && seg_name != "UPX0")
{
//Message("跳过段: %s (0x%X)n", seg_name, seg);
continue;
}
Message("正在处理段: %s (0x%X)n", seg_name, seg);
// 获取段的起始和结束地址
auto start_ea = seg;
auto end_ea = get_segm_end(seg);
// 遍历段中的每一条指令
current_ea = start_ea;
while (current_ea < end_ea && current_ea != BADADDR)
{
// 获取指令的助记符和操作数
mnemonic = print_insn_mnem(current_ea);
op1 = print_operand(current_ea, 0);
// 检查是否是目标指令
if (mnemonic == "call")
{
if (op1 == "returnarg1")
{
Message("Target Ins at: 0x%X (call returnarg1)n", current_ea);
// 替换为 mov rax, rcx (3 字节)
patch_byte(current_ea, 0x48); // REX.W 前缀
patch_byte(current_ea + 1, 0x89); // mov 操作码
patch_byte(current_ea + 2, 0xC8); // modrm: mov r/m64, r64 (rax = rcx)
// 填充 2 字节的 nop 指令
patch_byte(current_ea + 3, 0x90); // nop
patch_byte(current_ea + 4, 0x90); // nop
// 重新分析指令
create_insn(current_ea);
}
else if (op1 == "return_num1")
{
Message("Target Ins at: 0x%X (call return_num1)n", current_ea);
// 替换为 mov eax, 1 (5 字节)
patch_byte(current_ea, 0xB8); // mov eax, imm32 的操作码
patch_dword(current_ea + 1, 1); // imm32 = 1
// 重新分析指令
create_insn(current_ea);
}
else if (op1 == "return_arg2_except1695")
{
Message("Target Ins at: 0x%X (call return_arg2_except1695)n", current_ea);
// 替换为 mov rax, rdx (3 字节)
patch_byte(current_ea, 0x48); // REX.W 前缀
patch_byte(current_ea + 1, 0x89); // mov 操作码
patch_byte(current_ea + 2, 0xD0); // modrm: mov r/m64, r64 (rax = rdx)
// 填充 2 字节的 nop 指令
patch_byte(current_ea + 3, 0x90); // nop
patch_byte(current_ea + 4, 0x90); // nop
// 重新分析指令
create_insn(current_ea);
}
}
// 移动到下一条指令(避免死循环)
auto next_ea = next_head(current_ea, end_ea);
if (next_ea == BADADDR || next_ea <= current_ea)
break;
current_ea = next_ea;
}
}
Message("Finished.n");
}
执行后发现反编译生成的代码缩减了一大半,至此我们基本实现了语义解混淆,剩下的混淆内容可以在后续分析中去除:
unsigned char num[] =
{
0x52, 0xE1, 0x44, 0xE2, 0x39, 0xE1, 0x5E, 0x9B,
0x51, 0xDC, 0x19, 0x98, 0x50, 0x92, 0x39, 0xC1,
0x50, 0x9E, 0x52, 0x82, 0x27, 0x82, 0x26, 0xE7,
0x53, 0x80, 0x24, 0x80, 0x42, 0xDC, 0x39, 0x9E,
0x2, 0x94, 0x27, 0x81, 0x45, 0x83, 0x51, 0x93,
0x2, 0x80, 0x44, 0x81, 0x44, 0x81, 0x44, 0x81,
};
unsignedinthash(unsignedchar* data, unsignedint len)
{
unsigned int res = -1;
for (int i = 0; i < len; ++i)
{
res ^= data[i];
for (int j = 0; j < 8; ++j)
{
if (res & 1)
res = (res >> 1) ^ 0xEDB88320;
else
res >>= 1;
}
}
return ~res;
}
intmain()
{
for (int a = 0; a < 0xff; ++a)
{
for (int b = 0; b < 0xff; ++b)
{
unsigned char data[48];
for (int i = 0; i < 48; ++i)
{
data[i] = (i % 2) ? (b ^ num[i]) : (a ^ num[i]);
}
if (hash(data, 48) == 0xF703DF16)
{
printf("%x %xn", a, b);
goto success;
}
}
}
success:
return 10086;
}
最终程序输出为79 bc,这表明我们的命令行第二个参数内容为"79BC"即可。
五、 输出结果结合上述分析,我们得到了获取flag的步骤:
-
打开一个新进程(例如notepad.exe),获取其PID。 -
运行原始的re.exe,参数为79BC PID。
原文始发于微信公众号(吾爱破解论坛):2024 CISCN x 长城杯初赛Reverse赛题vt Writeup——攻城攻心的混淆大师
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论