涉及知识点
入口函数位置混淆
调用原始代码进行数据爆破
setjmp/longjmp机制
rc4密码算法
onexit机制
先运行发现输出错误信息
IDA打开,分析main函数可知:在main函数前面还有其他函数(不是启动函数),
- swap_xor利用异或交换数据,并且传入的是地址,所以swap_xor(&v7, &v7)返回0,而在main函数其他地方也没有发现有输出的地方,所以main函数不是入口函数
- 对main函数交叉引用跟踪到sub_4011B0函数
在sub_4011B0函数、main函数前分别下断点
- 可以发现,main函数还没有执行,但报错信息已经输出
查看trace信息
- 可以发现,在main函数前面sub_402218函数调用了puts函数,且sub_402357函数调用了sub_402218函数
跟到sub_402218函数
- 可以看到该函数先对错误信息字符串解密,然后输出,再加密
对sub_402218交叉引用跟到sub_402357函数
- 可以看到,当dword_4099D0[0]=0时,就会输出错误信息,所以在这里我们要让dword_4099D0[0]不等于0
- 跟进sub_402268函数,发现其对dword_4099D[0]进行了相关赋值
跟入sub_402268函数(这里我已经打好了补丁)
- 该函数的原始逻辑是:srand(v2[1]),所以我们要爆破时间戳
爆破时间戳
这里直接调用程序里面的sub_402268函数进行爆破;Note:要给sub_402268中的其他函数赋一个地址
time:5B00E398
dword_4099D0[0]: 322CE7A4
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include "crypt.h"
#include<Windows.h>
typedef unsigned int(*check_time)();
static UINT time = 0x5AFFE78F + 1; //时间戳种子在(0x5AFFE78F,0x5B28A8F]之间
UINT mytime(int) { //遍历时间戳
return time++;
}
char byte_405020_bak[256] = { 0 };
int main() {
UINT64 * pTime64 = (UINT64 *)0x40A38C;
UINT64 * pSrand = (UINT64 *)0x40A414;
UINT64 * pRand = (UINT64 *)0x40A3FC;
UINT64 * pMemset = (UINT64 *)0x40A3DC;
HMODULE hmd = LoadLibrary(TEXT("your path\magic.exe"));
memcpy(byte_405020_bak, (void*)0x405020, 256);
check_time check = (check_time)0x402268;
*pTime64 = (UINT64)mytime;
*pSrand = (UINT64)srand;
*pRand = (UINT64)rand;
*pMemset = (UINT64)memset;
while (TRUE) {
memcpy((void*)0x405020, byte_405020_bak, 256);
UINT res = check();
if (res) {
printf("time:%20X\ndword_4099D0[0]:%9X", time-1, res);
break;
}
}
return 0;
}
给程序打个补丁
打好补丁后,在运行程序
- 此时,第一关已经攻破
保持sub_4011B0函数、main函数前的断点,再在sub_402268函数前下个断点,我们继续动态调试
- sub_402357执行完后,回到sub_4032A0函数
- 继续往后跟,跟到sub_40318函数
- 发现onexit()函数,这个函数的作用是注册一个函数,使得程序在exit()的时候会调这个被注册的函数,这个被注册的函数就是sub_403260
- 继续跟,会执行main函数,这里就不看了,因为main函数没啥用;main函数执行完后会调用exit函数(还记得上面的onexit🐎
- 执行exit时,回调到sub_403260函数,这里跟进result就到都头了
- 跟进sub_4023B1,这个函数就是上面的result,
- 首先对我们的输入进行rc4加密(rc4的算法特征很明显
- 然后再进入虚拟机(sub_4029C7(data)函数)
对虚拟机进行分析
题型:给定了可执行程序和opcode,逆向emulator,结合opcode文件,推出flag
这个其实挺简单的,指令作用都特别明显
只是这里用了setjmp/longjmp机制(百度看一下,很简单)
"""
0ABh, 3, 0 reg[3] = 0
0ABh, 4, 1Ah reg[4] = 0x1A
0ABh, 0, 'f' reg[0] = 'f'
s1:
0AAh, 5, 2 reg[5] = reg[2]
0A9h, 'S' reg[5] += reg[3]
0A0h, 5 reg[5] = reg[5][0]
0ABh, 6, 0CCh reg[6] = 0xCC
0A9h, 'V' reg[5] += reg[6]
0ABh, 6, 0FFh reg[6] = 0xFF
0ACh, 'V' reg[5] &= reg[6]
0AEh, 'P' reg[5] ^= reg[0]
0ADh, 0 reg[0] = ~LOBYTE(reg[0])
0AAh, 6, 5 reg[6] = reg[5]
0AAh, 5, 1 reg[5] = reg[1]
0A9h, 'S' reg[5] += reg[3]
0A0h, 5 reg[5] = reg[5][0]
0AFh, 'V', 0
dword_409060 = 5;
dword_409064 = 6;
if ( !setjmp(::Buf) )
byte_405340[v7] = 5 / 0; --> 异常 --> reg[5] = reg[5] == reg[6]
v7 += 2;
0A7h, 1
if reg[5]:
v7 += 1;
++v7;
cmp reg[5] == 1
jz s2
0CCh
s2:
0A9h, '5' reg[3] += reg[5]
0AAh, 5, 3 reg[5] = reg[3]
0AFh, 'T', 0
if ( !setjmp(::Buf) )
byte_405340[v7] = 5 / 0; --> 异常 --> reg[5] = reg[5] == reg[4]
v7 += 2;
0A6h, 0D1h
if reg[5] == 0:
v7 += 1;
++v7;
cmp reg[5] == 1
jz s1
0CCh
0, 0, 0, 0, 0, 0, 0
"""
指令序列对应汇编
"""
r1 = [
0x89, 0xC1, 0xEC, 0x50, 0x97, 0x3A, 0x57, 0x59, 0xE4, 0xE6,
0xE4, 0x42, 0xCB, 0xD9, 0x08, 0x22, 0xAE, 0x9D, 0x7C, 0x07,
0x80, 0x8F, 0x1B, 0x45, 0x04, 0xE8, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00
]
r2 = rc4_input
mov r3, 0
mov r4, 0x1A
mov r0, 0x66
s1:
mov r5, r2
add r5, r3
mov r5, byte ptr r5
mov r6, 0xCC
add r5, r6
mov r6, 0xFF
and r5, r6=0xFF
xor r5, r0
mov r0, ~LOBYTE(r0)
mov r6, r5
mov r5, r1
add r5, r3
mov r5, byte ptr r5
cmp r5, r6
jmp s2:
exit
s2:
add r3, r5
mov r5, r3
cmp r5, r4
jmp s1
exit
"""
指令序列对应python代码
rc4_input = "abcdefg"
r0 = 0x66
for i in range(len(rc4_input)):
r6 = ((ord(rc4_input[i]) + 0xCC) & 0xFF) ^ r0
r0 = (~r0)&0xFF
if r1[i] != r6: break
直接爆破rc4_input:
- 得到238cbefd25d765f4b6b3b6fe174a2effc384ed21a4ab11096a5
r0 = 0x66
r1 = [
0x89, 0xC1, 0xEC, 0x50, 0x97, 0x3A, 0x57, 0x59, 0xE4, 0xE6,
0xE4, 0x42, 0xCB, 0xD9, 0x08, 0x22, 0xAE, 0x9D, 0x7C, 0x07,
0x80, 0x8F, 0x1B, 0x45, 0x04, 0xE8, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00
]
rc4_input = []
input = ""
for i in range(26):
for j in range(0x100):
r = ((j + 0xCC) & 0xFF) ^ r0
if r1[i] == r:
rc4_input.append(hex(j))
break
r0 = (~r0)&0xFF
for i in rc4_input:
input += i[2:]
print(input)
然后直接rc4解密就行:密钥就是前面的dword_4099D0[0],这里采用大佬的方法直接修改内存数据取解密,因为不知道dword_4099D0[0]有没有在其他地方改改,所以这样其实更加保险
脚本如下:
from idaapi import *
rc4 = [0x23,0x8c,0xbe,0xfd,0x25,0xd7,0x65,0xf4,0xb6,0xb3,0xb6,0xf,0xe1,0x74,0xa2,0xef,0xfc,0x38,0x4e,0xd2,0x1a,0x4a,0xb1,0x10,0x96,0xa5]
i = 0
for addr in range(data_addr, data_addr + 26): #我这里data_addr = 0x60FD54
idc.patch_byte(addr, rc4[i])
i = i + 1
结果如下:
参考文章: