DASCTF 2023六月挑战赛 二进制专项 RE writeup
一、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 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 #include using namespace std; #include #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", sent[0], sent[1]); btea(sent, -11, key); //printf("decoded:%x %x", 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了。
