常年不去上早八课的人,今天居然在八点前就醒了,要问为什么?这比赛从早上八点打到晚上八点 >…<(悲)
不过,咱们队伍在下午就把逆向题 ak 了~居然没有坐牢到晚上 (●’◡’●)
题目附件: 点击下载
# careful
动态调试,题目里有个 inline hook, 在这里打个断点
那么网址就是 Just_An_APIH00k11.com
# babyre
die 查一下壳
有 sleep 反调试,把 sleep nop 掉
这里读取了名称为 cod 的资源,用 resource hacker
把资源复制下来
然后向下执行,这里是一个对 cod 资源进行解密的地方
这里要注意的是如果检测到调试器,那么 byte_7FF6DA64F000[3]
将会被赋值为 36
所以要把这个 if 语句通过修改 ZF 标志位的方式来绕过反调试
cod 资源解密脚本如下
arr = [0x18, 0x57, 0x68, 0x64] | |
with open('COD101.bin', 'rb') as f: | |
b = f.read() | |
b = bytearray(b) | |
for i in range(len(b)): | |
b[i] = b[i] ^ arr[i % 4] | |
with open('COD_de.bin', 'wb') as f: | |
f.write(b) |
用 ida 打开,看到有花指令
nop 一下,主要的改动有这几处
于是得到如下的伪代码
看算法是魔改的 RC4,exp 如下
class RC4: | |
def __init__(self, key) -> None: | |
self.key = key | |
self.S = 0 | |
self.__rc4_init__() | |
def __rc4_init__(self): | |
S = [i for i in range(256)] | |
j = 0 | |
for i in range(256): | |
j = (2 * j + S[i] + key[i % len(key)]) % 256 | |
S[i], S[j] = S[j], S[i] | |
self.S = S | |
def rc4_encrypt(self, plain) -> list: | |
i = 0 | |
j = 0 | |
cipher = [] | |
cnt = 0 | |
for p in plain: | |
p = (p + 256 - cnt % 0xd) % 256 | |
cnt += 1 | |
i = (i + j) % 256 | |
j = (j + self.S[i]) % 256 | |
self.S[i], self.S[j] = self.S[j], self.S[i] | |
tmp = self.S[(self.S[i] + self.S[j] + j) % 256] | |
k = p ^ tmp | |
cipher.append(k) | |
return cipher | |
key = [0x5D , 0x42 , 0x62 , 0x29 , 0x3, 0x36 , 0x47 , 0x41 , 0x15, 0x36] | |
data = [0xF7, 0x2E, 0x34, 0xF0, 0x72, 0xCF, 0x5E, 0x0A, 0xBB, 0xEC, 0xB1, 0x2B, 0x70, 0x88, 0x88, 0xED, | |
0x46, 0x38, 0xDB, 0xDA, 0x6C, 0xBD, 0xD4, 0x06, 0x77, 0xF2, 0xCF, 0x56, 0x88, 0xC6, 0x31, 0xD2, | |
0xB7, 0x5A, 0xC1, 0x42, 0xB0, 0xF4, 0x48, 0x37, 0xF5, 0x2C, 0xF5, 0x58] | |
rc4 = RC4(key) | |
plain = rc4.rc4_encrypt(data) | |
print(''.join(map(chr,plain))) |
# ez_exe
查个壳,是 python 逆向
用 pyinstxtractor
脱一下
用在线网站看一下 ez_py.pyc 的源代码
#!/usr/bin/env python | |
# visit https://tool.lu/pyc/ for more information | |
# Version: Python 3.11 | |
import ctypes | |
from time import * | |
from ctypes import * | |
from ctypes import wintypes | |
from hashlib import md5 | |
class _STARTUPINFO(Structure): | |
_fields_ = [ | |
('cb', c_ulong), | |
('lpReserved', c_char_p), | |
('lpDesktop', c_char_p), | |
('lpTitle', c_char_p), | |
('dwX', c_ulong), | |
('dwY', c_ulong), | |
('dwXSize', c_ulong), | |
('dwYSize', c_ulong), | |
('dwXCountChars', c_ulong), | |
('dwYCountChars', c_ulong), | |
('dwFillAttribute', c_ulong), | |
('dwFlags', c_ulong), | |
('wShowWindow', c_ushort), | |
('cbReserved2', c_ushort), | |
('lpReserved2', c_char_p), | |
('hStdInput', c_ulong), | |
('hStdOutput', c_ulong), | |
('hStdError', c_ulong)] | |
class _PROCESS_INFORMATION(Structure): | |
_fields_ = [ | |
('hProcess', c_void_p), | |
('hThread', c_void_p), | |
('dwProcessId', c_ulong), | |
('dwThreadId', c_ulong)] | |
StartupInfo = _STARTUPINFO() | |
ProcessInfo = _PROCESS_INFORMATION() | |
key1 = bytes(md5(b'bin1bin1bin1').hexdigest().encode()) | |
file = open('bin1', 'rb').read() | |
arr = range(len(file))() | |
open('bin1', 'wb').write(bytes(arr)) | |
sleep(0) | |
bet = ctypes.windll.kernel32.CreateProcessA(b'bin1', ctypes.c_int(0), ctypes.c_int(0), ctypes.c_int(0), ctypes.c_int(0), ctypes.c_int(0), ctypes.c_int(0), ctypes.c_int(0), byref(StartupInfo), byref(ProcessInfo)) | |
ctypes.windll.kernel32.WaitForSingleObject(ctypes.c_int(ProcessInfo.hProcess), ctypes.c_int(-1)) | |
open('bin1', 'wb').write(file) |
用 ida 反编译 bin1 失败,看来是被加密了
用这个代码看一下字节码
import marshal, dis | |
f = open("ez_py.pyc", "rb").read() | |
code = marshal.loads(f[16:]) #这边从 16 位开始取因为是 python3 python2 从 8 位开始取 | |
dis.dis(code) |
在最后面得到了这个
Disassembly of <code object <listcomp> at 0x00000297CC7F8E70, file "ez_py.py", line 59>: | |
59 0 RESUME 0 | |
2 BUILD_LIST 0 | |
4 LOAD_FAST 0 (.0) | |
>> 6 FOR_ITER 50 (to 108) | |
8 STORE_FAST 1 (i) | |
10 LOAD_GLOBAL 0 (key1) | |
22 LOAD_FAST 1 (i) | |
24 LOAD_GLOBAL 3 (NULL + len) | |
36 LOAD_GLOBAL 0 (key1) | |
48 PRECALL 1 | |
52 CALL 1 | |
62 BINARY_OP 6 (%) | |
66 BINARY_SUBSCR | |
76 LOAD_GLOBAL 4 (file) | |
88 LOAD_FAST 1 (i) | |
90 BINARY_SUBSCR | |
100 BINARY_OP 12 (^) | |
104 LIST_APPEND 2 | |
106 JUMP_BACKWARD 51 (to 6) | |
>> 108 RETURN_VALUE |
那么解密代码如下
from hashlib import md5 | |
key1 = bytes(md5(b'bin1bin1bin1').hexdigest().encode()) | |
# print(key1) | |
file = open('bin1', 'rb').read() | |
arr = [key1[i % len(key1)] ^ file[i] for i in range(len(file))] | |
# open('bin1', 'wb').write(bytes(arr)) | |
with open('bin1__','wb') as f: | |
f.write(bytes(arr)) |
反编译出来是这个
那根据提示我们把上面的解密脚本稍作修改
from hashlib import md5 | |
key1 = bytes(md5(b'bin2bin2bin2').hexdigest().encode()) | |
# print(key1) | |
file = open('bin2', 'rb').read() | |
arr = [key1[i % len(key1)] ^ file[i] for i in range(len(file))] | |
# open('bin1', 'wb').write(bytes(arr)) | |
with open('bin2__','wb') as f: | |
f.write(bytes(arr)) |
然后用 ida 反编译 bin2__
那么这就是正常的逆向题了
btea
函数里面是这个,这是一个 xxtea 算法
写一下 exp
#include <iostream> | |
#include <stdio.h> | |
using namespace std; | |
#include <stdint.h> | |
#define DELTA 0x7937B99E | |
#define MX (((z>>5^y<<2) + (y>>3^z<<4)) ^ ((sum^y) + (key[(p&3)^e] ^ z))) | |
void btea(uint32_t* v, int n, uint32_t const key[4]) { | |
uint32_t y, z, sum; | |
unsigned p, rounds, e; | |
if (n > 1) { /* Coding Part */ | |
rounds = /*6 + */52 / n; | |
sum = 0; | |
z = v[n - 1]; | |
do { | |
sum += DELTA; | |
e = (sum >> 2) & 3; | |
for (p = 0; p < n - 1; p++) { | |
y = v[p + 1]; | |
z = v[p] += MX; | |
} | |
y = v[0]; | |
z = v[n - 1] += MX; | |
} while (--rounds); | |
} | |
else if (n < -1) { /* Decoding Part */ | |
n = -n; | |
rounds = /*6 + */52 / n; | |
sum = rounds * DELTA; | |
y = v[0]; | |
do { | |
e = (sum >> 2) & 3; | |
for (p = n - 1; p > 0; p--) { | |
z = v[p - 1]; | |
y = v[p] -= MX; | |
} | |
z = v[n - 1]; | |
y = v[0] -= MX; | |
} while ((sum -= DELTA) != 0); | |
} | |
} | |
int main() | |
{ | |
uint32_t const key[4] = { 0x4B5F, 0xDEAD, 0x11ED, 0xB3CC }; | |
uint32_t data[11] = { 0xCC45699D, 0x683D5352,0xB8BB71A0,0xD3817AD,0x7547E79E,0x4BDD8C7C,0x95E25A81,0xC4525103,0x7049B46F,0x5417F77C,0x65567138 }; | |
uint32_t* sent = data; | |
//btea(sent, 11, key); | |
//printf("coded:%x %x\n", sent[0], sent[1]); | |
btea(sent, -11, key); | |
//printf("decoded:%x %x\n", sent[0], sent[1]); | |
for (int i = 0; i < 11; i++) { | |
for (int j = 0; j < 4; j++) | |
{ | |
printf("%c", sent[i] & 0xff); | |
sent[i] >>= 8; | |
} | |
} | |
return 0; | |
} | |
//DASCTF{7eb20cb2-deac-11ed-ae42-94085339ce84} |
# cap
在这个地方动调
可以发现数组的下标在 0~12 之间循环
我们随便打开一个 BMP 类型的文件,用 010 看看
对于 BMP 类型的文件前两个字节必定是 43 4D
既然这个加密的 bmp 的每一个字节进行的都是异或,那我们可以将前两个字节异或看看
n 和 c 是密钥 enc_by_dasctf
的第 2 个和第 3 个字符,按照这个序列,我们向后将密钥向后延申看看后面的情况如何
所以我们写个脚本,从密钥的第二位开始,循环异或
key = "enc_by_dasctf" | |
with open('cap.bin', 'rb') as f: | |
s = bytearray(f.read()) | |
for i in range(len(s)): | |
s[i] ^= ord(key[(i+1) % len(key)]) | |
with open('flag.bmp', 'wb') as f: | |
f.write(s) |
得到 flag
# unsym
查一下壳,是 go 逆向
用这个脚本恢复一下 go 符号 https://github.com/renshareck/IDAGolangHelper_SupportGo1.20
依次点击如下按钮
首先判断 key 正确与否,看来这是个 rsa
用 yafu 解一下 p 和 q
然后解出密钥
import gmpy2 | |
from Crypto.Util.number import long_to_bytes | |
n = 0x1d884d54d21694ccd120f145c8344b729b301e782c69a8f3073325b9c5 | |
p = 37636318457745167234140808130156739 | |
q = 21154904887215748949280410616478423 | |
c = 0xfad53ce897d2c26f8cad910417fbdd1f0f9a18f6c1748faca10299dc8 | |
e = 0x10001 | |
phi = (p - 1) * (q - 1) | |
d = gmpy2.invert(e, phi) | |
m = pow(c, d, n) | |
print(long_to_bytes(m)) | |
# E@sy_RSA_enc7ypt |
再往后看,
动调了一下看到 iv 和 key 都是一样的
所以直接写个 exp 把加密的文件解密
from Crypto.Cipher import AES | |
password = b'E@sy_RSA_enc7ypt' # 秘钥必须为 16 字节或者 16 字节的倍数的字节型数据 | |
iv = b'E@sy_RSA_enc7ypt' # iv 偏移量,bytes 类型 | |
with open('encrypted.bin','rb') as f: | |
en_text = f.read() | |
aes = AES.new(password, AES.MODE_CBC, iv) # CBC 模式下解密需要重新创建一个 aes 对象 | |
de_text = aes.decrypt(en_text) | |
with open('decrypt.exe','wb') as f: | |
f.write(de_text) |
运行一下解密出的 exe, 就得到 flag 了