2022-10月DASCTF X GFCTF
考点: 字节码、ollvm混淆、duilib程序逆向、sys驱动、异常处理
算法浓度不是很高,🐹🐹比较喜欢,babysys个人感觉题目不错,本文也主要介绍了该题的做题思路。
PYCODE
version3.9,dis拿字节码,直接翻译即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 from Crypto.Util.number import * cc='8b2e4e858126bc8478d6a6a485215f03' def extract_number (x ): x = x ^ (x >> 11 ) x=x&0xffffffff x = x ^ ((x << 7 ) & 2022072721 ) x = x & 0xffffffff x = x ^ ((x << 15 ) & 2323163360 ) x = x & 0xffffffff x = x ^ (x >> 18 ) x = x & 0xffffffff return xdef transform (m ): new_message = '' l = len (m) for i in range (l//4 ): enc = m[4 * i:4 * i + 4 ] enc = bytes_to_long(enc) enc = extract_number(enc) new_message += long_to_bytes(enc, 4 ) return new_message cc=bytes .fromhex(cc) c=[]for i in range (len (cc)//4 ): c.append(bytes_to_long(cc[4 *i:4 *i+4 ]))def re (x ): x=x^(x>>18 ) t=(x^((x<<15 )&2323163360 ))&0xffffffff x=x^((t<<15 )&2323163360 ) x = x & 0xffffffff t1=(x ^ ((x << 7 ) & 2022072721 ))&0xffffffff t2=(x ^ ((t1 << 7 ) & 2022072721 ))&0xffffffff t3=(x ^ ((t2 << 7 ) & 2022072721 ))&0xffffffff x=x ^ ((t3 << 7 ) & 2022072721 ) x = x & 0xffffffff t = x ^ (x >> 11 ) x=x ^ (t >> 11 ) return xfor i in range (len (c)): print (long_to_bytes(re(c[i])).hex (),end='' )
贪玩CTF
主要逻辑在sub_7FF6A72619C0
,TLS函数又反调试,nop掉即可,可以通过设置条件断点便于调试。
对账号加密单纯异或最后一位,对密码加密则是AES,首先会动态恢复sbox。
1 2 3 4 5 6 7 8 9 10 11 s=[ 0x04 , 0x1F , 0x1F , 0x1E , 0x43 , 0x4B , 0x43 , 0x45 , 0x44 , 0x00 , 0x16 , 0x10 , 0x55 , 0x17 , 0x12 , 0x73 ]for i in range (15 ): s[i]^=s[15 ]print (bytes (s)) k=bytes (s)from Crypto.Cipher.AES import * aes=new(k,MODE_ECB) c=bytes ([0x3C , 0x97 , 0x72 , 0x96 , 0x5A , 0x33 , 0x63 , 0x9C , 0x97 , 0x30 , 0x4D , 0x90 , 0x84 , 0xE8 , 0x5F , 0x56 ])print (aes.decrypt(c))
cutere
通过一场处理修改了rc4的秘钥和base64表,加密主体部分采用ollvm混淆过了,不过加密逻辑没变,调试出关键值直接解密即可。
直接cyberchef一把梭
rc4
换表base64
最后交替拼接即可
1 2 3 4 5 6 7 print ('a' *32 ) rc4='szv~' buf0='DST{Wo7Xj5Ad8Nx8' buf1='ACFg0Gw1Jo5Ix9C}' nbase='ghijklmnopqrstuvwxyz0123456789+/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef' for i in range (len (buf1)): print (buf0[i]+buf1[i],end='' )
babysys
无双击调试经验,只能硬动态分析。本题内核成分不多,主要是异常处理,感谢Retard爷的点播,🐹🐹我啊~ ,才理清程序逻辑。
异常补充
关于x64SEH相关补充,修复控制流的相关结构体
异常注册
每个非叶函数只要对应一个RUNTIME_FUNCTION
结构体,如果叶函数使用了SEH,也会有对应的结构体。
不调用函数、又没有修改栈指针,也没有使用 SEH 的函数就叫做“叶函数”,树的叶子节点。
x64 windows中异常注册信息发生了巨大的改变,异常注册信息在编译过程中生成,链接时写入 PE+ 头中的 ExceptionDirectory
,其中几乎包含所有函数的栈操作、异常处理等信息。
程序出现异常->查找函数的runtime_function
结构体->找到unwind_info
,判断flags,如果没有异常处理程序,那么根据异常的位置参照unwind_code
进行回滚,返回到上层函数,继续查runtime_function
结构体,查看有无处理函数,如果有则进行异常处理,如果无则继续回滚重复查runtime_function
。
至于findally块的执行,满足全局展开,会先找一个异常过滤程序执行,如果返回1
ExceptionDir
其中包含多条字段RUNTIME_FUNCTION
,一共12字节。
1 2 3 4 5 6 7 8 typedef struct _IMAGE_RUNTIME_FUNCTION_ENTRY { DWORD BeginAddress; DWORD EndAddress; union { DWORD UnwindInfoAddress; DWORD UnwindData; } DUMMYUNIONNAME; } RUNTIME_FUNCTION, *PRUNTIME_FUNCTION, _IMAGE_RUNTIME_FUNCTION_ENTRY, *_PIMAGE_RUNTIME_FUNCTION_ENTRY;
unwind结构体记录有关异常处理和函数操作的信息,用于回滚。
易失寄存器rcx、rdx、r8、r9、r10、r11
1 2 3 4 5 6 7 8 9 10 11 12 13 struct UNWIND_INFO_HDR { char Ver3_Flags; char PrologSize; char CntUnwindCodes; char FrReg_FrRegOff; };
当unwind_info—中的flag字段为后三种情况时,后续不定长数组unwind_code
的结构如下。
UWIND_CODE用于记录函数头中有关非易失性寄存器和RSP的操作。
1 2 3 4 5 6 7 struct UNWIND_CODE { char PrologOff; char OpCode_OpInfo; };
PrologOff
距离头部偏移的偏移量,例如值为x,那么在main函数x字节之外发生异常,则需要此次回滚,否则不进行回滚,info
则是根据op的类型所需的相关参数。
例如47a0
函数的一条unwind_code
如下,第一个值是4,结合函数处的汇编为sub rsp,0x38
表示的该4字节的栈操作,而0x62,低4位为2即UWOP_ALLOC_SMALL
,对于info
的值为6,查看官方文档。
在栈上开辟了一个8*info
+8大小的空间,即6*8+8 = 0x38, 也就该条unwind_code
记录着该函数前4字节做的操作在栈上开辟了0x38的空间,如果在之后出现异常,则回滚时则需要进行相应的栈处理。
unwind_code的处理顺序和函数中对栈操作的指令相反。
对于flags为1或2时,即函数存在异常处理/终止处理的程序。
unwind_info
中会塞入__C_specific_handler
函数,他根据SCOPE_TABLE
进行异常处理或终止处理,该函数后跟SCOPE_TABLE
的数量,之后便是,相关的scope结构体数组。
C_SCOPE_TABLE
结构体记录了异常处理的结构,大小16字节,4个RVA
。
1 2 3 4 5 6 7 8 9 10 11 struct C_SCOPE_TABLE { void *__ptr32 Begin; void *__ptr32 End; void *__ptr32 Handler; void *__ptr32 Target; };
综上,RUNTIME_FUNCTION
最后会指向一系列的unwind_code
用于回滚。
结构体汇总
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 typedef struct _RUNTIME_FUNCTION { ULONG BeginAddress; ULONG EndAddress; ULONG UnwindData; } RUNTIME_FUNCTION, *PRUNTIME_FUNCTION;typedef enum _UNWIND_OP_CODES { UWOP_PUSH_NONVOL = 0 , UWOP_ALLOC_LARGE, UWOP_ALLOC_SMALL, UWOP_SET_FPREG, UWOP_SAVE_NONVOL, UWOP_SAVE_NONVOL_FAR, UWOP_SPARE_CODE1, UWOP_SPARE_CODE2, UWOP_SAVE_XMM128, UWOP_SAVE_XMM128_FAR, UWOP_PUSH_MACHFRAME } UNWIND_OP_CODES, *PUNWIND_OP_CODES;typedef union _UNWIND_CODE { struct { UCHAR CodeOffset; UCHAR UnwindOp : 4 ; UCHAR OpInfo : 4 ; }; USHORT FrameOffset; } UNWIND_CODE, *PUNWIND_CODE; #define UNW_FLAG_NHANDLER 0x0 #define UNW_FLAG_EHANDLER 0x1 #define UNW_FLAG_UHANDLER 0x2 #define UNW_FLAG_CHAININFO 0x4 typedef struct _UNWIND_INFO { UCHAR Version : 3 ; UCHAR Flags : 5 ; UCHAR SizeOfProlog; UCHAR CountOfCodes; UCHAR FrameRegister : 4 ; UCHAR FrameOffset : 4 ; UNWIND_CODE UnwindCode[1 ]; } UNWIND_INFO, *PUNWIND_INFO;typedef struct _SCOPE_TABLE { ULONG Count; struct { ULONG BeginAddress; ULONG EndAddress; ULONG HandlerAddress; ULONG JumpTarget; } ScopeRecord[1 ]; } SCOPE_TABLE, *PSCOPE_TABLE;
详见官方: https://learn.microsoft.com/en-us/cpp/build/exception-handling-x64?view=msvc-170
异常信息
__finally
代码块为终止程序,__except(filter())
为异常处理程序,filter
为异常过滤程序,返回值有三种情况。
1 2 3 EXCEPTION_CONTINUE_EXECUTION(-1 ) EXCEPTION_EXECUTE_HANDLER(1 ) EXCEPTION_CONTINUE_SEARCH(0 )
糊~
异常处理中与异常信息相关结构体,可由GetExceptionInformation函数获取,返回EXCEPTION_POINTERS
类型的指针。
EXCEPTION_POINTERS
1 2 3 4 typedef struct _EXCEPTION_POINTERS { PEXCEPTION_RECORD ExceptionRecord; PCONTEXT ContextRecord; } EXCEPTION_POINTERS, *PEXCEPTION_POINTERS;
PEXCEPTION_RECORD
1 2 3 4 5 6 7 8 typedef struct _EXCEPTION_RECORD { DWORD ExceptionCode; DWORD ExceptionFlags; struct _EXCEPTION_RECORD *ExceptionRecord ; PVOID ExceptionAddress; DWORD NumberParameters; ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS]; } EXCEPTION_RECORD;
题解
用户程序访问驱动,通过CreateFile
访问设备。
同时使用DeviceIoControl
函数向驱动发送信息,控制设备执行相关操作,操作控制代码为0x222000。
1 2 3 4 5 6 7 8 9 BOOL DeviceIoControl ( (HANDLE) hDevice, NULL , 0 , (LPVOID) lpOutBuffer, (DWORD) nOutBufferSize, (LPDWORD) lpBytesReturned, (LPOVERLAPPED) lpOverlapped ) ;
驱动程序通过IoCreateDevice
创建设备名称,并使用IoCreateSymbolicLink
创建符号链接。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 typedef struct _DRIVER_OBJECT { CSHORT Type; CSHORT Size; PDEVICE_OBJECT DeviceObject; ULONG Flags; PVOID DriverStart; ULONG DriverSize; PVOID DriverSection; PDRIVER_EXTENSION DriverExtension; UNICODE_STRING DriverName; PUNICODE_STRING HardwareDatabase; PFAST_IO_DISPATCH FastIoDispatch; PDRIVER_INITIALIZE DriverInit; PDRIVER_STARTIO DriverStartIo; PDRIVER_UNLOAD DriverUnload; PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1 ]; } DRIVER_OBJECT, *PDRIVER_OBJECT;
并且驱动程序注册了一个DriverObject->MajorFunction[14]
的函数,0xe对应着IRP_MJ_DEVICE_CONTROL
,即应用程序调用 DeviceIoControl
,会指向注册的sub_14000175C
函数。
该函数会匹配操作控制码,并且获取应用程序传入的输入计算其长度,并使用sub_1400018d8
函数进行处理。
后续函数多使用异或来做SMC,并且在调用后恢复。
get_func_len
函数是通过访问runtime_function
结构体获取函数开始和结束位置做差获取。
如果对PE文件熟悉的话,0x3c
是dos头最后四字节,指向NT头,而0xA0则对应ExceptionDir
的首地址,相关结构体上文已给出。
使用ipy解密代码,但是发现后文并没有直接调用,而是在调用hook_2_func_encrypt
后就将解密后的代码还原了。
1 2 3 4 5 6 7 8 9 import idautils adr=0x140001288 end=0x1400014C1 size=end-adr code=bytearray (get_bytes(adr,size))for i in range (len (code)): code[i]^=0xab patch_byte(adr+i,code[i])print ('win' )
查看hook_2_func_encrypt
地址的函数,其通过触发int3异常来执行异常过滤程序,这样便能调用刚刚解密的 sub_140001288
函数。
sub_140001288
通过v3(cnt)从0开始计数,并比对v3的值完成不同操作。如果v3的值小于7,则执行另外两组操作。并且在最后通过EXCEPTION_POINTERS
获取异常ip,如果异常处指令为0xcc并且v3不等于1就将ip加1后返回,返回值为-1即继续在异常处执行。
如果v3为1或3时,会先对输入进行+i的处理,之后调用sub_140001AB4
函数。
sub_140001AB4
函数加密逻辑如下
通过计算两个固定函数的md5相异或,将得到的32个字符分为16个字节一组,第一组用做tea加密,第二组用作tea解密,类似3DES处理。
当v3等于2/4时,会首先调用 sub_140001C60
,并且异或0x10。
一共有6个int3,并且第一次不改ip故cnt一共加了7次,第7次进入if块处理。
首先会调用sub_140001868
函数,修改触发异常函数的runtime_function
结构体,修改其异常处理程序为0x140001009
处的func_encrypt
函数。
之后会将sub_1400014C4
函数异或0x66,并且返回-1,下次触发异常对应的cnt为8则返回1,那么异常由except块的异常处理程序进行处理,即调用刚刚修改的0x140001009
地址处的函数。
对sub_1400014C4
函数还会异或0xcc,调用结束后会异或0xaa,因为0x66^0xcc == 0xaa,这里套了一层。
函数的smc不在赘述,处理流程为将密文再异或0x99并使用sub_140001C60
进行tea加密,结果与v11比对。
sub_140001c60
函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 void __fastcall sub_140001C60 (unsigned int *a1) { __int64 v2; unsigned int v0; int sum; unsigned int v1; __int64 round; v2 = 5 i64; do { v0 = *a1; sum = 0 ; v1 = a1[1 ]; round = 32 i64; do { sum -= 0x61C88647 ; v0 += (16 * v1 + 17 ) ^ ((v1 >> 5 ) + 34 ) ^ (sum + v1); v1 += (16 * v0 + 51 ) ^ ((v0 >> 5 ) + 68 ) ^ (sum + v0); --round; } while ( round ); *a1 = v0; a1[1 ] = v1; a1 += 2 ; --v2; } while ( v2 ); }
整体加密流程简化后如下
1 2 3 4 5 6 7 8 9 ms[i]+=i sub_140001AB4(v2); sub_140001C60(v2); ms[i]^=0x10 ms[i]^=0x99 sub_140001C60(v2)
算法无魔改,只要取到key值即可,解密脚本如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 #include <iostream> #define ut32 unsigned int #define delta 0x9e3779b9 void __fastcall sub_140001C60 (unsigned int * a1) { __int64 v2; unsigned int v0; int sum; unsigned int v1; __int64 round; v2 = 1 i64; do { v0 = *a1; sum = 0 ; v1 = a1[1 ]; round = 32 i64; do { sum -= 0x61C88647 ; v0 += (16 * v1 + 17 ) ^ ((v1 >> 5 ) + 34 ) ^ (sum + v1); v1 += (16 * v0 + 51 ) ^ ((v0 >> 5 ) + 68 ) ^ (sum + v0); --round; } while (round); *a1 = v0; a1[1 ] = v1; a1 += 2 ; --v2; } while (v2); printf ("sum: %08x\n" , sum); }void Tea_Encrypt (ut32* src, ut32* k) { ut32 sum = 0 ; ut32 v0 = src[0 ]; ut32 v1 = src[1 ]; for (int i = 0 ; i < 0x20 ; i++) { sum += delta; v0 += ((v1 << 4 ) + k[0 ]) ^ (v1 + sum) ^ ((v1 >> 5 ) + k[1 ]); v1 += ((v0 << 4 ) + k[2 ]) ^ (v0 + sum) ^ ((v0 >> 5 ) + k[3 ]); } src[0 ] = v0; src[1 ] = v1; }void Tea_Decrypt (ut32* enc, ut32* k) { int sum = delta * 0x20 ; ut32 v0 = enc[0 ]; ut32 v1 = enc[1 ]; for (int i = 0 ; i < 0x20 ; i++) { v1 -= ((v0 << 4 ) + k[2 ]) ^ (v0 + sum) ^ ((v0 >> 5 ) + k[3 ]); v0 -= ((v1 << 4 ) + k[0 ]) ^ (v1 + sum) ^ ((v1 >> 5 ) + k[1 ]); sum -= delta; } enc[0 ] = v0; enc[1 ] = v1; }void output (ut32* m, ut32 n) { for (int i = 0 ; i < n; i++) printf ("0x%08x " , m[i]); printf ("\n" ); }int main () { ut32 m[10 ] = { 0xbb929b51 ,0x25fed57f ,0x988aa03d ,0xbdc35d79 ,0xe60aca2f ,0xf2755515 ,0x67aa0fc2 ,0xeb1992a8 ,0x62759e50 ,0x461e460c }; ut32 k1[4 ] = { 0x656565e ,0x57575207 ,0x5201040a ,0xf045605 }; ut32 k2[4 ] = { 0x7595701 ,0x5601560c ,0x5651565a ,0x5525056 }; ut32 k[4 ] = { 17 ,34 ,51 ,68 }; for (int i=0 ;i<10 ;i+=2 ) Tea_Decrypt(m+i, k); for (int i = 0 ; i < 10 ; i++) { m[i] ^= 0x99999999 ; } for (int j = 0 ; j < 2 ; j++) { for (int i = 0 ; i < 10 ; i++) m[i] ^= 0x10101010 ; for (int i = 0 ; i < 10 ; i += 2 ) { Tea_Decrypt(m + i, k); Tea_Encrypt(m + i, k2); Tea_Decrypt(m + i, k1); } for (int i = 0 ; i < 40 ; i++) { *((unsigned char *)m + i) -= i; } } for (int i = 0 ; i < 40 ; i++) { putchar (*((char *)m + i)); } return 0 ; }
参考
(向量化异111常处理)VEH hook
基于异常处理的Hook函数(VEH Hook)
Windows 驱动程序逆向工程方法
SEH分析笔记(X64篇)