Loading... # Reverse(十) ## csaw2013reversing2 这道题需要ollydbg来做更方便些,需要修改汇编代码 本题直接运行弹窗是乱码的,因此首先ida32查看下代码逻辑 ~~~c int __cdecl __noreturn main(int argc, const char **argv, const char **envp) { int v3; // ecx CHAR *lpMem; // [esp+8h] [ebp-Ch] HANDLE hHeap; // [esp+10h] [ebp-4h] hHeap = HeapCreate(0x40000u, 0, 0); lpMem = (CHAR *)HeapAlloc(hHeap, 8u, SourceSize + 1); // 很明显从内存中取出 memcpy_s(lpMem, SourceSize, &unk_409B10, SourceSize); if ( !sub_40102A() && !IsDebuggerPresent() ) // 默认进去 { MessageBoxA(0, lpMem + 1, "Flag", 2u); // 此处弹窗显示flag HeapFree(hHeap, 0, lpMem); HeapDestroy(hHeap); ExitProcess(0); } __debugbreak(); sub_401000(v3 + 4, lpMem); ExitProcess(0xFFFFFFFF); } ~~~ 要想获得正确flag,需要经过sub_401000函数处理,因此下图中判断完是否调试器后test完,修改ZF位,使得跳转不成立,去执行sub_401000函数 ![image-20231223153658173.png](http://xherlock.top/usr/uploads/2024/02/1206814871.png) 同样要注意INT3是异常中断指令,因此可以右键在下一行mov上选择EIP为此时位置,调用完到JMP时再切换回下面调用MessageBoxA的开始位置(即008B10B9),运行即可显示flag ![image-20231223154108485.png](http://xherlock.top/usr/uploads/2024/02/796994268.png) 补充:整型溢出 ![image-20231229174945097.png](http://xherlock.top/usr/uploads/2024/02/996011247.png) ## re4-unvm-me pyc文件反编译即可 ~~~python # uncompyle6 version 3.9.0 # Python bytecode version base 2.7 (62211) # Decompiled from: Python 3.9.12 (main, Apr 4 2022, 05:22:27) [MSC v.1916 64 bit (AMD64)] # Embedded file name: unvm_me.py # Compiled at: 2016-12-21 05:44:01 import md5 md5s = [ 174282896860968005525213562254350376167, 137092044126081477479435678296496849608, 126300127609096051658061491018211963916, 314989972419727999226545215739316729360, 256525866025901597224592941642385934114, 115141138810151571209618282728408211053, 8705973470942652577929336993839061582, 256697681645515528548061291580728800189, 39818552652170274340851144295913091599, 65313561977812018046200997898904313350, 230909080238053318105407334248228870753, 196125799557195268866757688147870815374, 74874145132345503095307276614727915885] print 'Can you turn me back to python ? ...' flag = raw_input('well as you wish.. what is the flag: ') if len(flag) > 69: print 'nice try' exit() if len(flag) % 5 != 0: print 'nice try' exit() for i in range(0, len(flag), 5): s = flag[i:i + 5] if int('0x' + md5.new(s).hexdigest(), 16) != md5s[i / 5]: print 'nice try' exit() print 'Congratz now you have the flag' ~~~ 暴力破解,虽然可能花些时间,有个关键点注意m = hashlib.md5(),这句话必须每次都要创建一个新的对象,否则加密结果是错的,不能重复调用 ~~~python # 暴力破解5位md5 import hashlib p = ['', '', '', '', '', '', '', '', '', '', '', '', ''] md5s = [ 174282896860968005525213562254350376167, 137092044126081477479435678296496849608, 126300127609096051658061491018211963916, 314989972419727999226545215739316729360, 256525866025901597224592941642385934114, 115141138810151571209618282728408211053, 8705973470942652577929336993839061582, 256697681645515528548061291580728800189, 39818552652170274340851144295913091599, 65313561977812018046200997898904313350, 230909080238053318105407334248228870753, 196125799557195268866757688147870815374, 74874145132345503095307276614727915885] md5s = [str(hex(i))[2:] for i in md5s] for i1 in range(48, 127): for i2 in range(48, 127): for i3 in range(48, 127): for i4 in range(48, 127): for i5 in range(48, 127): m = hashlib.md5() str = chr(i1)+chr(i2)+chr(i3)+chr(i4)+chr(i5) m.update(str.encode('utf8')) md5 = m.hexdigest() if md5 in md5s: p[md5s.index(md5)] = str print(p) # ~~~ 当然最好还是直接找md5解密网站快多了 ## crackme exeinfo查看得知是nSpack壳,学习下相关知识 * esp定律:也称堆栈平衡定律,如果要返回父程序,则当我们在堆栈中进行堆栈的操作的时候,一定要保证在RET这条指令之前,ESP指向的是我们压入栈中的地址。 * OEP:程序入口点,可以直接OD载入 以题目为例手动脱nSpack壳(北斗壳): 1. OD载入直接跳到了PUSHFD ![image-20231230172218265.png](http://xherlock.top/usr/uploads/2024/02/723594515.png) 2. F8执行到**PUSHAD**,此时ESP变化,对ESP右键设置断点(HW break) ![image-20231230172355310.png](http://xherlock.top/usr/uploads/2024/02/3945947158.png) 3. F9执行到**POPFD**,下面有一条JMP指令 ![image-20231230172511127.png](http://xherlock.top/usr/uploads/2024/02/2202300703.png) 4. F8跳转到JMP位置,00401336就是OEP地址 ![image-20231230174055022.png](http://xherlock.top/usr/uploads/2024/02/636293473.png) 5. 右键使用Dump Process,将EIP设置为OEP,转储 ![image-20231230175952059.png](http://xherlock.top/usr/uploads/2024/02/3026141170.png) 另一种方法是到这里,使用PEtool管理员运行,找到这个程序(3fdxxx.exe)右键Dump all ![image-20231230180216359.png](http://xherlock.top/usr/uploads/2024/02/229326098.png) 6. 再去ida反编译可以成功看到伪代码 ~~~c int __cdecl main(int argc, const char **argv, const char **envp) { void (__cdecl *v3)(char *); // esi int v5; // eax char v6; // [esp+4h] [ebp-38h] BYREF char v7[51]; // [esp+5h] [ebp-37h] BYREF v6 = 0; sub_4018F4(v7, 0, 49); v3 = (void (__cdecl *)(char *))dword_402094; dword_402094(aPleaseInputFla); // Please Input Flag: dword_402090(&v6, 44); if ( strlen(&v6) == 42 ) { v5 = 0; while ( (v7[v5 - 1] ^ byte_402130[v5 % 16]) == dword_402150[v5] )// v7其实就是v6数组 { if ( ++v5 >= 42 ) { v3(aRight); // right! return 0; } } v3(aError); return 0; } else { v3(aError); return -1; } } ~~~ ![image-20231230181223178.png](http://xherlock.top/usr/uploads/2024/02/4120310690.png) ## debug exeinfo提示dotfuscator进行了加壳混淆,因此使用de4dot来脱壳 ![image-20231230195310984.png](http://xherlock.top/usr/uploads/2024/02/2391011688.png) 然后dnspy查看.net程序,并定位关键函数 ![image-20231230200343781.png](http://xherlock.top/usr/uploads/2024/02/2661826919.png) ~~~c using System; using System.Security.Cryptography; using System.Text; // Token: 0x02000002 RID: 2 internal class Class0 { // Token: 0x06000001 RID: 1 RVA: 0x000020C8 File Offset: 0x000002C8 private static int smethod_0(int int_0, int int_1) { return (new int[] { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113 })[int_1] ^ int_0; } // Token: 0x06000002 RID: 2 RVA: 0x000020E8 File Offset: 0x000002E8 private static string smethod_1(string string_0) { byte[] bytes = Encoding.ASCII.GetBytes(string_0); return "flag{" + BitConverter.ToString(new MD5CryptoServiceProvider().ComputeHash(bytes)).Replace("-", "") + "}"; } // Token: 0x06000003 RID: 3 RVA: 0x00002130 File Offset: 0x00000330 private static void smethod_2(string string_0, int int_0, ref string string_1) { int num = 0; if (0 < string_0.Length) { do { char c = string_0[num]; int num2 = 1; do { c = Convert.ToChar(Class0.smethod_0(Convert.ToInt32(c), num2)); num2++; } while (num2 < 15); string_1 += c; num++; } while (num < string_0.Length); } string_1 = Class0.smethod_1(string_1); } // Token: 0x06000004 RID: 4 RVA: 0x00002198 File Offset: 0x00000398 private static void Main(string[] args) { string b = null; string value = string.Format("{0}", DateTime.Now.Hour + 1); string string_ = "CreateByTenshine"; Class0.smethod_2(string_, Convert.ToInt32(value), ref b); // 加密函数赋给b string a = Console.ReadLine(); if (a == b) { Console.WriteLine("u got it!"); Console.ReadKey(true); } else { Console.Write("wrong"); } Console.ReadKey(true); } } ~~~ 解密脚本 ~~~c++ #include<iostream> #include<string> using namespace std; int main() { int a[] = {2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103,107,109,113}; string s = "CreateByTenshine"; string flag = ""; for (int i = 0; i < s.length(); i++) { int num2 = 1; int c = (int)s[i]; do { c = a[num2] ^ c; num2++; } while (num2 < 15); flag += c; } cout << flag << endl; } ~~~ 得到的结果md5取大写,然后加上flag{}即可 ## ReverseMe-120 https://blog.csdn.net/xiao__1bai/article/details/120107210 这道题难度在于读懂代码,涉及到很多考点 ~~~c int __cdecl main(int argc, const char **argv, const char **envp) { unsigned int v3; // edx unsigned int v4; // ecx __m128i si128; // xmm1 unsigned int v6; // esi const __m128i *v7; // eax __m128i v8; // xmm0 int v9; // eax char v11[100]; // [esp+0h] [ebp-CCh] BYREF char v12[100]; // [esp+64h] [ebp-68h] BYREF unsigned int v13; // [esp+C8h] [ebp-4h] printf("please input your flah:"); memset(v11, 0, sizeof(v11)); scanf("%s", v11); // 输入赋给了v11 memset(v12, 0, sizeof(v12)); sub_401000(v11, strlen(v11)); // 一个核心函数,后面分析可知是base64解密函数 v3 = v13; // 这里最开始搞不懂为啥v13没初始化就有值,其实是在上面sub_401000函数里 v4 = 0; if ( v13 ) { if ( v13 >= 0x10 ) { si128 = _mm_load_si128((const __m128i *)&xmmword_414F20);// 用于加载数据,从内存到暂存器,查看可知都是0x25 v6 = v13 - (v13 & 0xF); v7 = (const __m128i *)v12; do { v8 = _mm_loadu_si128(v7); // 读取16字节 v4 += 16; ++v7; v7[-1] = _mm_xor_si128(v8, si128); // 相当于v12结果每一位和si128即0x25异或 } while ( v4 < v6 ); } for ( ; v4 < v3; ++v4 ) // 没用,貌似得调试发现 v12[v4] ^= 0x25u; } v9 = strcmp(v12, "you_know_how_to_remove_junk_code");// 说明有垃圾代码没有运行起干扰作用 if ( v9 ) v9 = v9 < 0 ? -1 : 1; if ( v9 ) printf("wrong\n"); else printf("correct\n"); system("pause"); return 0; } ~~~ sub_401000之所以参数多于实际参数是因为有寄存器传参 ~~~c int __usercall sub_401000@<eax>(unsigned int *a1@<edx>, _BYTE *a2@<ecx>, unsigned __int8 *a3, unsigned int a4) { int v4; // ebx unsigned int v5; // eax int v6; // ecx unsigned __int8 *v7; // edi int v8; // edx bool v9; // zf unsigned __int8 v10; // cl char v11; // cl _BYTE *v12; // esi unsigned int v13; // ecx int v14; // ebx unsigned __int8 v15; // cl char v16; // dl int v20; // [esp+14h] [ebp-4h] unsigned int v21; // [esp+14h] [ebp-4h] int i; // [esp+24h] [ebp+Ch] v4 = 0; v5 = 0; v6 = 0; v20 = 0; if ( !a4 ) return 0; v7 = a3; do { v8 = 0; v9 = v5 == a4; if ( v5 < a4 ) { do { if ( a3[v5] != 32 ) break; ++v5; ++v8; } while ( v5 < a4 ); v9 = v5 == a4; } if ( v9 ) break; if ( a4 - v5 >= 2 && a3[v5] == 13 && a3[v5 + 1] == 10 || (v10 = a3[v5], v10 == 10) ) { v6 = v20; } else { if ( v8 ) return -44; if ( v10 == 61 && (unsigned int)++v4 > 2 ) return -44; if ( v10 > 0x7Fu ) return -44; v11 = byte_414E40[v10]; if ( v11 == 127 || (unsigned __int8)v11 < 0x40u && v4 ) return -44; v6 = ++v20; } ++v5; } while ( v5 < a4 ); if ( !v6 ) return 0; v12 = a2; v13 = ((unsigned int)(6 * v6 + 7) >> 3) - v4; if ( a2 && *a1 >= v13 ) { v21 = 3; v14 = 0; for ( i = 0; v5; --v5 ) { v15 = *v7; if ( *v7 != 13 && v15 != 10 && v15 != 32 ) { v16 = byte_414E40[v15]; v21 -= v16 == 64; v14 = v16 & 0x3F | (v14 << 6); // base64解密特征 if ( ++i == 4 ) { i = 0; if ( v21 ) *v12++ = BYTE2(v14); if ( v21 > 1 ) *v12++ = BYTE1(v14); if ( v21 > 2 ) *v12++ = v14; } } ++v7; } *a1 = v12 - a2; return 0; } *a1 = v13; return -42; } ~~~ 流程:base64解密--xor 0x25--明文比较 逆向:明文逐位和0x25异或--base64加密 得到:XEpQek5LSlJ6TUpSelFKeldASEpTQHpPUEtOekZKQUA= ## EASYHOOK 找到主函数和hook函数 ~~~c int __cdecl main(int argc, const char **argv, const char **envp) { HANDLE FileA; // eax DWORD NumberOfBytesWritten; // [esp+4h] [ebp-24h] BYREF char Buffer[32]; // [esp+8h] [ebp-20h] BYREF sub_401370(aPleaseInputFla); scanf("%31s", Buffer); if ( strlen(Buffer) == 19 ) { sub_401220(); // hook WriteFile FileA = CreateFileA(FileName, 0x40000000u, 0, 0, 2u, 0x80u, 0);// Your_input WriteFile(FileA, Buffer, 0x13u, &NumberOfBytesWritten, 0); sub_401240(Buffer, &NumberOfBytesWritten); if ( NumberOfBytesWritten == 1 ) sub_401370(aRightFlagIsYou); else sub_401370(aWrong); system(Command); return 0; } else { sub_401370(aWrong); system(Command); return 0; } } int sub_401220() { HMODULE LibraryA; // eax DWORD CurrentProcessId; // eax CurrentProcessId = GetCurrentProcessId(); hProcess = OpenProcess(0x1F0FFFu, 0, CurrentProcessId); LibraryA = LoadLibraryA(LibFileName); WriteFile_0 = (BOOL (__stdcall *)(HANDLE, LPCVOID, DWORD, LPDWORD, LPOVERLAPPED))GetProcAddress(LibraryA, ProcName); lpAddress = WriteFile_0; if ( !WriteFile_0 ) return sub_401370(&unk_40A044); unk_40C9B4 = *(_DWORD *)lpAddress; *((_BYTE *)&unk_40C9B4 + 4) = *((_BYTE *)lpAddress + 4); byte_40C9BC = -23; dword_40C9BD = (char *)sub_401080 - (char *)lpAddress - 5; // 计算被替换成的函数与WriteFile函数地址偏移 return sub_4010D0(); // hook } ~~~ 定位hook替换的函数 ~~~c int __stdcall sub_401080( HANDLE hFile, LPCVOID lpBuffer, // 输入的字符串 DWORD nNumberOfBytesToWrite, LPDWORD lpNumberOfBytesWritten, // 要确保lpNumberOfBytesWritten==1 LPOVERLAPPED lpOverlapped) { // 实际上被hook替换成的函数 int v5; // ebx v5 = sub_401000(lpBuffer, nNumberOfBytesToWrite); // 关键函数 sub_401140(); WriteFile(hFile, lpBuffer, nNumberOfBytesToWrite, lpNumberOfBytesWritten, lpOverlapped); // 原来的功能 if ( v5 ) // v5必须不为0 *lpNumberOfBytesWritten = 1; return 0; } int __cdecl sub_401000(int a1, int a2) { char i; // al char v3; // bl char v4; // cl int v5; // eax for ( i = 0; i < a2; ++i ) // 对输入字符串加密 { if ( i == 18 ) { *(_BYTE *)(a1 + 18) ^= 0x13u; } else { if ( i % 2 ) v3 = *(_BYTE *)(i + a1) - i; else v3 = *(_BYTE *)(i + a1 + 2); *(_BYTE *)(i + a1) = i ^ v3; } } v4 = 0; if ( a2 <= 0 ) return 1; v5 = 0; while ( byte_40A030[v5] == *(_BYTE *)(v5 + a1) ) { v5 = ++v4; if ( v4 >= a2 ) return 1; // 要确保到这里,也就是说循环不能退出 } return 0; } ~~~ 写脚本逆向,一定要记得运算符优先性,记得加括号,然后第一位可以猜到是f(没法逆向知道具体值) ~~~python b = [0x61, 0x6A, 0x79, 0x67, 0x6B, 0x46, 0x6D, 0x2E, 0x7F, 0x5F, 0x7E, 0x2D, 0x53, 0x56, 0x7B, 0x38, 0x6D, 0x4C, 0x6E] a1 = ['' for i in range(19)] for i in range(0, 19): if i == 18: a1[i] = chr(b[i] ^ 0x13) else: if i % 2: a1[i] = chr((b[i] ^ i) + i) else: a1[i + 2] = chr(b[i] ^ i) flag = ''.join(a1) print(flag) ~~~ ## gametime 运行发现是个游戏,首先ida静态分析下,主函数超长,经过分析发现只有game里的函数(两个,sub_401435和sub_401507,功能基本一样)是关键 ~~~c int __cdecl main(int argc, const char **argv, const char **envp) { int v3; // edi unsigned int v4; // eax int v5; // ecx int v6; // ecx int v7; // ecx void (__stdcall *v8)(DWORD); // ebx int v9; // esi int v10; // esi int v11; // esi int v12; // esi int v13; // esi int i; // edi int v15; // esi unsigned __int8 *v16; // esi int v17; // ebx int v18; // esi int v19; // eax int v20; // esi int v21; // eax char v22; // cl int v23; // eax int v25; // [esp+10h] [ebp-20h] int v26; // [esp+14h] [ebp-1Ch] BYREF char v27; // [esp+1Bh] [ebp-15h] _WORD v28[3]; // [esp+1Ch] [ebp-14h] BYREF int v29; // [esp+22h] [ebp-Eh] int v30; // [esp+26h] [ebp-Ah] __int16 v31; // [esp+2Ah] [ebp-6h] strcpy((char *)v28, " "); v26 = 7630702; *(_DWORD *)&v28[1] = 0; v29 = 0; v30 = 0; v31 = 0; v3 = 0; v25 = 0; printf((int)"\r\tZOMGZOMGOZMGZOMGZOMGOZMGZOMGZOMGOZMGZOMGZOMGOZMG\n"); printf((int)"\tkey is %s (%s)", (const char *)&v26, (const char *)v28); sub_401423(); // 相当于消除上一行,光标移动到开始了 printf((int)"\r\tZOMGZOMG ZOMGZOMG\n"); printf((int)"\tkey is %s (%s)", (const char *)&v26, (const char *)v28); sub_401423(); printf((int)"\r\tZOMGZOMG TAP TAP REVOLUTION!!!!!!! ZOMGZOMG\n"); printf((int)"\tkey is %s (%s)", (const char *)&v26, (const char *)v28); sub_401423(); printf((int)"\r\tZOMGZOMG ZOMGZOMG\n"); printf((int)"\tkey is %s (%s)", (const char *)&v26, (const char *)v28); sub_401423(); printf((int)"\r\tZOMGZOMGOZMGZOMGZOMGOZMGZOMGZOMGOZMGZOMGZOMGOZMG\n\n\n"); printf((int)"\tkey is %s (%s)", (const char *)&v26, (const char *)v28); sub_401423(); printf((int)"\r\t R U READDY?!\n\n\n"); printf((int)"\tkey is %s (%s)", (const char *)&v26, (const char *)v28); sub_401423(); printf((int)"\rThe game is starting in...\n"); v4 = _time64(0); srand(v4); sub_4012B2(); // 10秒倒计时 sub_4012D5(0xC8u); // 打印Get ready to play if ( !sub_401435(0x1F4u, 32, 10, v5, (const char *)v28, (const char *)&v26) )// sub_401435传入参数,第一个倒计时时间,第二个需要按下的字符,第三个点号出现次数(出现完要按下空格),后两个没用 return 0; // 必须三个game都成功,否则return if ( !sub_401435(0x12Cu, 120, 8, v6, (const char *)v28, (const char *)&v26) ) return 0; if ( !sub_401435(0x12Cu, 109, 5, v7, (const char *)v28, (const char *)&v26) ) return 0; printf((int)"key is %s (%s)", (const char *)&v26, (const char *)v28); printf((int)"\rTRAINING COMPLETE! \n"); v8 = Sleep; v9 = 20; do { Sleep(0xC8u); printf((int)"\n"); --v9; } while ( v9 ); // 换行20次 printf((int)"key is %s (%s)", (const char *)&v26, (const char *)v28); printf((int)"\rNow you know everything you need to know"); v10 = 4; do { printf((int)"."); Sleep(0x3E8u); --v10; } while ( v10 ); printf((int)"\n\n\nfor the rest of your life!\n"); v11 = 20; do { Sleep(0xC8u); printf((int)"\n"); --v11; } while ( v11 ); printf((int)"LETS PLAY !\n"); v12 = 20; do { Sleep(0xC8u); printf((int)"\n"); --v12; } while ( v12 ); sub_4012B2(); sub_4012D5(0x64u); if ( !sub_401507(5, 32, 0xC8u, (const char *)v28, (const char *)&v26) )// 看着跟上面很像,传入参数第一个是点号打印次数,第二是按下的字符,第三个是必须按下字符的规定时间,后两个还是没用 return 0; if ( !sub_401507(2, 120, 0xC8u, (const char *)v28, (const char *)v28) ) return 0; if ( !sub_401507(1, 109, 0xC8u, (const char *)v28, (const char *)v28) ) return 0; printf((int)"key is %s (%s)", (const char *)&v26, (const char *)v28); sub_401423(); printf((int)"\rooooh, you fancy!!!\n"); if ( !sub_401507(5, 109, 0xC8u, (const char *)v28, (const char *)&v26)// 再来一轮,必须都成功 || !sub_401507(2, 120, 0xC8u, (const char *)v28, (const char *)&v26) || !sub_401507(1, 32, 0xC8u, (const char *)v28, (const char *)&v26) ) { return 0; } printf((int)"key is %s (%s)", (const char *)&v26, (const char *)v28); printf((int)"\b\b"); printf((int)"NIIICE JOB)!!!!\n"); v13 = 20; do { Sleep(0x32u); printf((int)"\n"); --v13; } while ( v13 ); v27 = 1; do // 从这里开始难以静态分析,因此直接让代码自己动态运行就好,关键是绕开game的判断 { if ( v3 % 3 == 1 ) { printf((int)"key is %s (%s)", (const char *)&v26, (const char *)v28); sub_401423(); printf((int)"\rTURBO TIME! \n"); for ( i = 0; i < 20; ++i ) { v8(0x32u); printf((int)"\n"); // 打印换行符,直到最后一次 if ( i == 19 ) { v15 = sub_40141D(); sub_401D02(v28, v15 - 5514); dword_41A1F8 = (int)v28; dword_41A1FC = v15 - 5498; sub_401AA5(); sub_401CC9(); printf((int)"key is %s (%s)", byte_417D02, (const char *)&v26); printf((int)"\b\b"); v16 = (unsigned __int8 *)v28; v17 = 16; do { printf((int)"%02x", *v16++); --v17; } while ( v17 ); printf((int)")\n\n"); v8 = Sleep; } } v18 = 0; while ( 1 ) { v19 = rand(); if ( !sub_401507(1, byte_417B08[v19 % 3], 0x64u, (const char *)v28, (const char *)&v26) ) break; if ( ++v18 >= 10 ) goto LABEL_33; } v27 = 0; LABEL_33: v3 = v25; } v20 = 0; while ( 1 ) { v21 = rand(); v22 = v27; v23 = v21 % 3; if ( v27 ) break; LABEL_38: if ( ++v20 >= 10 ) goto LABEL_41; } if ( sub_401507(v23 + 3, byte_417B08[v23], 0x64u, (const char *)v28, (const char *)&v26) ) { v22 = v27; goto LABEL_38; } v22 = 0; v27 = 0; LABEL_41: if ( v3 == 1337 ) { sub_4012F6(); // 成功结算 v22 = v27; } v25 = ++v3; } while ( v22 ); return 0; } ~~~ sub_401435和sub_401507中我们只看一个代码就好 ~~~c char __usercall sub_401435@<al>(DWORD a1@<edx>, int a2@<ecx>, int a3, int a4, const char *a5, const char *a6) { int v8; // edi printf((int)"key is %s (%s)", a6, a5); sub_401423(); printf((int)"\rZOMGZOMGOZMGZOMGZOMGOZMGZOMGZOMGOZMGZOMGZOMGOZMG\n"); if ( a2 == 32 ) printf((int)"\nWhen you see an 's', press the space bar\n\n"); else printf((int)"\nWhen you see an '%c', press the '%c' key\n\n", a2, a2); printf((int)"key is %s (%s)", a6, a5); sub_401423(); printf((int)"\rZOMGZOMGOZMGZOMGZOMGOZMGZOMGZOMGOZMGZOMGZOMGOZMG\n"); sub_4012D5(a1); v8 = a3; if ( a3 > 0 ) { do { printf((int)"."); Sleep(0xC8u); --v8; } while ( v8 ); } if ( (unsigned __int8)sub_401260(a2, 100000) )// 判断是否规定时间内按下对应字符 return 1; printf((int)"key is %s (%s)\r", a6, a5); sub_401423(); printf((int)"\rUDDER FAILURE! http://imgur.com/4Ajx21P \n");// 失败 return 0; } ~~~ 只需保证return为1即可游戏成功,因此ollydbg定位所有带“UDDER FAILURE! http://imgur.com/4Ajx21P”这句话,后面的return改成1 如图,原来是XOR AL, AL,表示清0返回0,现在改成和下面一样,return 1(注意改两处) ![image-20240128100436735.png](http://xherlock.top/usr/uploads/2024/02/1060716346.png) F9让ollydbg自动跑,一段时间后就会发现打印了key ![image-20240128100004858.png](http://xherlock.top/usr/uploads/2024/02/1266933266.png) ## notsequence ida打开,定位主函数 ~~~c int __cdecl main() { _DWORD *v0; // eax int v2; // [esp+14h] [ebp-Ch] _DWORD *v3; // [esp+1Ch] [ebp-4h] memset(&unk_8049BE0, 0, 0x4000u); puts("input raw_flag please:"); v3 = &unk_8049BE0; do { v0 = v3++; scanf("%d", v0); } while ( *(v3 - 1) ); v2 = sub_80486CD((int)&unk_8049BE0); // 第一个加密,返回不能为0 if ( v2 == -1 ) { printf("check1 not pass"); system("pause"); } if ( (unsigned __int8)sub_8048783((int)&unk_8049BE0, v2) != 1 )// 第二个加密,返回必须为1 { printf("check2 not pass!"); exit(0); } if ( v2 == 20 ) // 这里可知v2=20 { puts("Congratulations! fl4g is :\nRCTF{md5(/*what you input without space or \\n~*/)}"); exit(0); } return 0; } ~~~ 分析两个加密函数 1. sub_80486CD ~~~c int __cdecl sub_80486CD(int a1) { int j; // [esp+0h] [ebp-14h] int v3; // [esp+4h] [ebp-10h] int i; // [esp+8h] [ebp-Ch] int v5; // [esp+Ch] [ebp-8h] v5 = 0; for ( i = 0; i <= 1024 && *(_DWORD *)(4 * i + a1); i = v5 * (v5 + 1) / 2 )// v5<=44 { // i=0、1、3、6 ... 19x(19+1)/2=190 v3 = 0; for ( j = 0; j <= v5; ++j ) v3 += *(_DWORD *)(4 * (j + i) + a1); // int数组 if ( 1 << v5 != v3 ) // v3必须等于2^v5 return -1; ++v5; } return v5; // 由主函数知v5=20 } ~~~ 该代码逻辑是:逐个取1、2、3...n...19个数,每次取的数之和等于$2^{n-1}$ 2. sub_8048783 ~~~c int __cdecl sub_8048783(int a1, int a2) { int v3; // [esp+10h] [ebp-10h] int v4; // [esp+14h] [ebp-Ch] int i; // [esp+18h] [ebp-8h] int v6; // [esp+1Ch] [ebp-4h] v6 = 0; for ( i = 1; i < a2; ++i ) // a2=20 { v4 = 0; v3 = i - 1; if ( !*(_DWORD *)(4 * i + a1) ) return 0; while ( a2 - 1 > v3 ) { v4 += *(_DWORD *)(4 * (v3 * (v3 + 1) / 2 + v6) + a1);// V6比i小1 ++v3; } if ( *(_DWORD *)(4 * (v3 * (v3 + 1) / 2 + i) + a1) != v4 )// 最后一行 return 0; ++v6; } return 1; } ~~~ 具体逻辑不好分析代码:大意是分为1、2、3...n...19个数,**除了最后一组的其他每组数的第i个数之和=最后一组数第i+1数**(至此可知是杨辉三角构造,最好别钻牛角尖想最后一行,直接类推分析,只有两行(1 11)、只有三行(1 11 121)、...就能看出来了) 至此代码分析完成,得知输入的是19行杨辉三角数,去除空格再md5 ~~~python import hashlib def get_row(n): row = [1] * (n + 1) for i in range(1, n): # 由于每一行都是上一行的基础上得到的,我们可以从第二项开始进行遍历 for j in range(i, 0, -1): # 每一项都是它前面两项之和 row[j] = row[j] + row[j - 1] row = [str(i) for i in row] return row yanghui = [] for i in range(0, 20): yanghui.extend(get_row(i)) print("".join(yanghui)) md5 = hashlib.md5() md5.update("".join(yanghui).encode('utf-8')) print(md5.hexdigest()) # 37894beff1c632010dd6d524aa9604db ~~~ ## key 这道题主函数十分复杂,但是可以ida看流程 首先第一处分支:走错会打印what happen?(深入去读会发现是一个读取文件操作,不管直接看正确路线) ![image-20240129103219165.png](http://xherlock.top/usr/uploads/2024/02/3001767100.png) 读不懂,看了wp,结合代码前期逻辑知道代码会生成一段字符串和从flag文件中的内容比较 ![image-20240129105544163.png](http://xherlock.top/usr/uploads/2024/02/2365376490.png) ~~~c int sub_401100() { signed int v0; // esi signed int v1; // esi unsigned int v2; // edi void **v3; // ebx void **v4; // eax int v5; // ecx int v6; // ST04_4 int v7; // ST08_4 int v8; // ST0C_4 int v9; // eax int v10; // ST0C_4 char *v11; // esi int v12; // ecx void **v13; // eax int v14; // eax int v15; // ST0C_4 int v16; // eax int v17; // ST0C_4 int v18; // eax int v19; // ST0C_4 int v20; // eax int v21; // ST0C_4 int v22; // eax int v23; // ST0C_4 int v24; // eax int v25; // ST0C_4 int v26; // eax int v27; // ST0C_4 int v28; // eax int result; // eax int v30; // [esp-4h] [ebp-13Ch] int Dst; // [esp+14h] [ebp-124h] char v32[4]; // [esp+20h] [ebp-118h] char v33; // [esp+24h] [ebp-114h] int v34; // [esp+5Ch] [ebp-DCh] char v35; // [esp+61h] [ebp-D7h] int v36; // [esp+64h] [ebp-D4h] int v37; // [esp+68h] [ebp-D0h] char v38; // [esp+6Ch] [ebp-CCh] FILE *File; // [esp+70h] [ebp-C8h] char v40; // [esp+84h] [ebp-B4h] void *v41; // [esp+CCh] [ebp-6Ch] int v42; // [esp+DCh] [ebp-5Ch] unsigned int v43; // [esp+E0h] [ebp-58h] void *v44; // [esp+E4h] [ebp-54h] int v45; // [esp+F4h] [ebp-44h] unsigned int v46; // [esp+F8h] [ebp-40h] void *Memory[4]; // [esp+FCh] [ebp-3Ch] int v48; // [esp+10Ch] [ebp-2Ch] unsigned int v49; // [esp+110h] [ebp-28h] __int128 v50; // [esp+114h] [ebp-24h] __int16 v51; // [esp+124h] [ebp-14h] char v52; // [esp+126h] [ebp-12h] int v53; // [esp+134h] [ebp-4h] v46 = 15; v45 = 0; LOBYTE(v44) = 0; v53 = 0; v43 = 15; v42 = 0; LOBYTE(v41) = 0; LOBYTE(v53) = 1; v0 = 0; v48 = 1684630885; LOWORD(v49) = 97; *(_OWORD *)Memory = xmmword_40528C; // 小端'adimehtadimehtadimeht',正常大端是'themidathemidathemida' v51 = 11836; v52 = 0; v50 = xmmword_4052A4; // 小端'.<<<<....++++---->',正常大端是'>----++++....<<<<.' do { sub_4021E0(1u, (*((_BYTE *)Memory + v0) ^ *((_BYTE *)&v50 + v0)) + 22); // 做了异或再+22 ++v0; } while ( v0 < 18 ); v1 = 0; v49 = 15; v48 = 0; LOBYTE(Memory[0]) = 0; LOBYTE(v53) = 2; v2 = v43; v3 = (void **)v41; do { v4 = &v41; if ( v2 >= 0x10 ) v4 = v3; sub_4021E0(1u, *((_BYTE *)v4 + v1++) + 9); // 再+9 } while ( v1 < 18 ); memset(&Dst, 0, 0xB8u); sub_401620(v5, v6, v7, v8); LOBYTE(v53) = 3; if ( v32[*(_DWORD *)(Dst + 4)] & 6 ) { v9 = sub_402A00(std::cerr, "?W?h?a?t h?a?p?p?e?n?", sub_402C50); std::basic_ostream<char,std::char_traits<char>>::operator<<(v9, v10); exit(-1); } sub_402E90(&Dst, &v44); v11 = &v33; if ( File ) { if ( !(unsigned __int8)sub_4022F0(&v33) ) v11 = 0; if ( fclose(File) ) v11 = 0; } else { v11 = 0; } v38 = 0; v35 = 0; std::basic_streambuf<char,std::char_traits<char>>::_Init(&v33); v36 = dword_408590; File = 0; v37 = dword_408594; v34 = 0; if ( !v11 ) std::basic_ios<char,std::char_traits<char>>::setstate((char *)&Dst + *(_DWORD *)(Dst + 4), 2, 0); v13 = Memory; if ( v49 >= 0x10 ) v13 = (void **)Memory[0]; if ( sub_4020C0(v12, v45, v13, v48) ) { v28 = sub_402A00(std::cout, "=W=r=o=n=g=K=e=y=", sub_402C50); } else { v14 = sub_402A00(std::cout, "|------------------------------|", sub_402C50); std::basic_ostream<char,std::char_traits<char>>::operator<<(v14, v15); v16 = sub_402A00(std::cout, "|==============================|", sub_402C50); std::basic_ostream<char,std::char_traits<char>>::operator<<(v16, v17); v18 = sub_402A00(std::cout, "|==============================|", sub_402C50); std::basic_ostream<char,std::char_traits<char>>::operator<<(v18, v19); v20 = sub_402A00(std::cout, "|==============================|", sub_402C50); std::basic_ostream<char,std::char_traits<char>>::operator<<(v20, v21); v22 = sub_402A00(std::cout, "\\ /\\ /\\ /\\ /\\==============|", sub_402C50); std::basic_ostream<char,std::char_traits<char>>::operator<<(v22, v23); v24 = sub_402A00(std::cout, " \\/ \\/ \\/ \\/ \\=============|", sub_402C50); std::basic_ostream<char,std::char_traits<char>>::operator<<(v24, v25); v26 = sub_402A00(std::cout, " |-------------|", sub_402C50); std::basic_ostream<char,std::char_traits<char>>::operator<<(v26, v27); std::basic_ostream<char,std::char_traits<char>>::operator<<(std::cout, sub_402C50); v28 = sub_402A00(std::cout, "Congrats You got it!", sub_402C50); } std::basic_ostream<char,std::char_traits<char>>::operator<<(v28, v30); sub_401570(&v40); std::basic_ios<char,std::char_traits<char>>::~basic_ios<char,std::char_traits<char>>(&v40); if ( v49 >= 0x10 ) sub_402630(Memory[0], v49 + 1); if ( v2 >= 0x10 ) sub_402630(v3, v2 + 1); result = v46; if ( v46 >= 0x10 ) result = sub_402630(v44, v46 + 1); return result; } ~~~ python解密脚本 ![image-20240129110052537.png](http://xherlock.top/usr/uploads/2024/02/3583981723.png) ## Windows_Reverse1 upx脱壳,ida分析 ~~~c int __cdecl main(int argc, const char **argv, const char **envp) { char v4[1024]; // [esp+4h] [ebp-804h] BYREF char v5[1024]; // [esp+404h] [ebp-404h] BYREF memset(v5, 0, sizeof(v5)); memset(v4, 0, sizeof(v4)); printf("please input code:"); scanf("%s", v5); sub_401000(v5); //加密函数,对输入做处理 if ( !strcmp(v4, "DDCTF{reverseME}") ) // 这里是用v4来比较,说明上面函数中对v4做了处理 printf("You've got it!!%s\n", v4); else printf("Try again later.\n"); return 0; } ~~~ 查看sub_401000函数,发现唯一可能赋值给主函数里v4的是v1 ~~~c unsigned int __cdecl sub_401000(const char *a1) { _BYTE *v1; // ecx unsigned int v2; // edi unsigned int result; // eax const char *v4; // ebx v2 = 0; result = strlen(a1); if ( result ) { v4 = (const char *)(a1 - v1); do { *v1 = byte_402FF8[(char)v1[(_DWORD)v4]]; ++v2; ++v1; result = strlen(a1); } while ( v2 < result ); } return result; } ~~~ 为了分析v1到底是什么,需要去阅读汇编代码。回到主函数看v4,可以看到在调用加密函数前使用lea指令将v4地址传入了ecx ![image-20240131135227471.png](http://xherlock.top/usr/uploads/2024/02/28809447.png) 再进加密函数看汇编代码,可以看到v1指向的正是ecx,同时后面还有位赋值操作 ![image-20240131140659267.png](http://xherlock.top/usr/uploads/2024/02/519932206.png) 接着就可以安心分析加密函数,首先byte_402FF8是一个字符数组,所以里面应该是数字型下标 ![image-20240131140927795.png](http://xherlock.top/usr/uploads/2024/02/2739482330.png) `v4 = (const char *)(a1 - v1);`这句代码中得到的是a1(输入的字符串地址)-v1(主函数v4的基地址),即地址差值 同时在循环中`*v1 = byte_402FF8[(char)v1[(_DWORD)v4]];`表示v1的基地址+(地址差值),取输入字符串一个字符(ascii值),从数组里获取对应值赋给v1(很绕)。总结来说思路很简单就是一个替换加密,由于没法直接找到这个字符数组值或者右键导出,因此使用ida自带python获取地址范围内的值 ~~~python addr=0x00402FF8 arr=[] for i in range(0x00403078-0x00402FF8): #数组的个数 arr.append(idc.get_wide_byte(addr+i)) print(arr) enflag = 'DDCTF{reverseME}' flag = '' for s in enflag: flag += chr(arr[ord(s)]) print(flag) # ZZ[JX#,9(9,+9QY! ~~~ 最后修改:2024 年 02 月 05 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 0 如果觉得我的文章对你有用,请随意赞赏