【CTF】Rust逆向，BeingDebugged，异或取表算法逆向 内存取证 2023上海市赛初赛
DLL注入通过DLL注入可以对其他进程进行hook/热补丁/修复BUG，同时也是渗透其他进程的有效方法。DLL被加载到进程后会自动运行DIIMain()函数，用户可以把想执行的代码放到DIIMain()函数，每当加载DLL时，添加的代码就会自然而然得到执行。利用该特性可修复程序Bug，或向程序添加新功能。本篇博客将使用Inline Hook和DLL注入技术实现控制程序执行流DLL注入流程使用MS编译dll动态链接库后，可以通过一些常见的方式进行DLL的注入：创建远程线程（CreateRemoteThread() API）使用注册表（AppInit_DLLs值）消息勾取（SetWindowsHookEx() API）通过这些方法编写注入程序，然后对写有Inline Hook逻辑的DLL程序进行加载并且注入进目标进程代码编写DLL编写DLL加载到进程后会运行DllMain()函数，通过这个特点，我们可以修改上一篇博客中的Inline Hook代码为如下代码其中，DllMain()参数中的ul_reason_for_call与之前TLS_Callback学习博客中的dwReason是相同的。因此，在进程启动的时候我们调用InlineHook()对MessageBoxA()进行hook// dllmain.cpp : 定义 DLL 应用程序的入口点。 #include "pch.h" #include <windows.h> #include <stdio.h> #include <iostream> #include <tchar.h> //修改API入口为 jmp xxx 使得程序能跳转到自己的函数 BYTE __NewCode[7] = { 0xE9, 0x0, 0x0, 0x0, 0x0 }; BYTE __OldCode[7] = { 0 }; FARPROC Messagebox_FARPROC; HMODULE g_hMod; int WINAPI MyMessageBoxA( HWND hWnd, // handle to owner window LPCTSTR lpText, // text in message box LPCTSTR lpCaption, // message box title UINT uType // message box style ); void InlineHook() { DWORD dwOldProtect = 0; //首先获取user32.dll的模块句柄 HMODULE user32_HMODULE = LoadLibraryA("user32.dll"); //利用GetProcAddress获取MessageBoxA导出函数的地址 Messagebox_FARPROC = GetProcAddress(user32_HMODULE, "MessageBoxA"); //利用ReadProcessMemory读取内存中MessageBoxA的前5字节 if (!ReadProcessMemory(GetCurrentProcess(), Messagebox_FARPROC, __OldCode, 6, NULL)) { printf("[!] ReadProcessMemory Failed"); } printf("%x%x%x%x%x\n", __OldCode[0], __OldCode[1], __OldCode[2], __OldCode[3], __OldCode[4]); //E9跳转为jmp地址到目标地址的间隔，地址需要进行计算，计算出来后赋值给newcode的后4位 *((ULONG*)(__NewCode + 1)) = (ULONG)&MyMessageBoxA - ((ULONG)Messagebox_FARPROC + 5); //memcpy(&__NewCode[1], &JmpAddress, 4); printf("JmpAddress : %x\n", *((ULONG*)(__NewCode + 1))); //将权限替换为可读写执行 VirtualProtect(Messagebox_FARPROC, 6, PAGE_EXECUTE_READWRITE, &dwOldProtect); //直接写入内存 WriteProcessMemory(GetCurrentProcess(), Messagebox_FARPROC, __NewCode, 5, NULL); //恢复之前的权限 VirtualProtect(Messagebox_FARPROC, 6, dwOldProtect, &dwOldProtect); } int WINAPI MyMessageBoxA( HWND hWnd, // handle to owner window LPCTSTR lpText, // text in message box LPCTSTR lpCaption, // message box title UINT uType // message box style ) { printf("Hook Successful\n"); //恢复原本的MessageBoxA，否则不能调用 WriteProcessMemory(GetCurrentProcess(), Messagebox_FARPROC, __OldCode, 5, NULL); //可以使用参数进行调用 Sleep(3000); MessageBoxA(NULL, (const char*)lpText, (const char*)lpCaption, MB_OK); MessageBoxA(NULL, "Xunflash", "Hook successful", MB_OK); //恢复hook WriteProcessMemory(GetCurrentProcess(), Messagebox_FARPROC, __NewCode, 5, NULL); return 0; } BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { HANDLE hThread = NULL; g_hMod = (HMODULE)hModule; switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: InlineHook(); break; case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE; //InlineHook(); //调用MessageBoxA //MessageBoxA(NULL, "Hello World", "Title", MB_OK); } 被注入程序编写简单的调用MessageBoxA即可#include <windows.h> #include <stdio.h> int main() { MessageBoxA(NULL, "test", "test123", S_OK); } 注入使用代码注入器注入这里我们先使用Github上开源的代码注入器Xenos对程序进行注入，测试效果可以看到已经注入成功使用CreateRemoteThread() API注入引用《逆向工程核心原理》p204页，利用CreateRemoteThread() API进行注入的代码注入器具体步骤：OpenProcess获取已运行起来的目标进程句柄在目标进程中分配DLL文件名大小的内存将DLL路径写入分配的内存利用GetModuleHandle获取kernal32的句柄，并使用GetProcAddress获取LoadLibraryW() API的地址利用CreateRemoteThread在目标进程上启动线程运行LoadLibraryW，并且参数为刚刚在内存中写入的DLL路径，实现加载DLL#include<Windows.h> #include<tchar.h> BOOL InjectDll(DWORD dwPid, LPCTSTR szDllPath) { HANDLE hProcess = NULL, hThread = NULL; HMODULE hMod = NULL; LPVOID pRemoteBuf = NULL; DWORD dwBufSize = (DWORD)(_tcslen(szDllPath) + 1) * sizeof(TCHAR); LPTHREAD_START_ROUTINE pThreadProc; //1.使用dwPid获取 目标进程句柄 hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid); if (!hProcess) { _tprintf(L"OpenProcess(%d)v failed!!! [%d]\n", dwPid, GetLastError()); return FALSE; } //2.在目标进程中分配szDllName大小的内存,dll文件的路径 pRemoteBuf = VirtualAllocEx(hProcess, NULL, dwBufSize, MEM_COMMIT, PAGE_READWRITE); //3.将dll路径写入分配的内存 WriteProcessMemory(hProcess, pRemoteBuf, (LPVOID)szDllPath, dwBufSize, NULL); //4.获取LoadLibraryW()API的地址 hMod = GetModuleHandle(L"kernel32.dll"); pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hMod, "LoadLibraryW"); //5.在目标程序进程中运行线程 //pThreadProc为目标程序进程内存中的LoadLibraryW地址 //pRemoteBuf为写入到目标程序进程内存中的myhack.dll字符串地址 hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, pRemoteBuf, 0, NULL); WaitForSingleObject(hThread, INFINITE); CloseHandle(hThread); CloseHandle(hProcess); return TRUE; } int _tmain(int argc, TCHAR* argv[]) { if (argc != 3) { //检查输入程序的参数是否为3个 _tprintf(L"USAGE: %s Pid Dll_Path \n", argv[0]); } if (InjectDll((DWORD)_tstol(argv[1]), argv[2])) { _tprintf(L"InjectDll(\"%s\") Success!!!\n", argv[2]); } else { _tprintf(L"InjectDll(\"%s\") Failed!!!\n", argv[2]); } return 0; }使用AppInit_DLLs进行注入此方式其实就是对注册表进行修改，让系统初始化程序的时候就加载上DLL。只需要进入注册表中如下路径HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows然后将LoadAppInit_DLL的值修改为1，并修改AppInit_DLLs的值为你想要注入的DLL的路径。重启电脑即可这种方法实际上会向系统中所有程序都注入此DLL，可能导致系统出现问题。使用SetWindowsHookEx()进行注入这种方法利用了Windows的消息机制在事件发送到os之间设置一条钩链，来钩取不同的消息。这种方式只能勾取到SetWindowsHookEx中第一个参数指定的几种消息，如下摘取自MSDN（谷歌机翻）价值意义WH_CALLWNDPROC安装一个钩子过程，在系统将消息发送到目标窗口过程之前监视消息。有关详细信息，请参阅CallWndProc)挂钩过程。WH_CALLWNDPROCRET安装一个钩子过程，在消息被目标窗口过程处理后监视消息。有关详细信息，请参阅CallWndRetProc挂钩过程。WH_CBT安装一个钩子过程，它接收对 CBT 应用程序有用的通知。有关详细信息，请参阅CBTProc)挂钩过程。WH_DEBUG安装一个对调试其他挂钩过程有用的挂钩过程。有关详细信息，请参阅DebugProc)挂钩过程。WH_FOREGROUNDIDLE安装将在应用程序的前台线程即将变为空闲时调用的挂钩过程。此挂钩对于在空闲时间执行低优先级任务很有用。有关详细信息，请参阅ForegroundIdleProc)挂钩过程。WH_GETMESSAGE安装一个挂钩过程，用于监视发布到消息队列的消息。有关详细信息，请参阅GetMsgProc)挂钩过程。WH_JOURNALPLAYBACK安装一个挂钩过程，用于发布以前由WH_JOURNALRECORD挂钩过程记录的消息。有关详细信息，请参阅JournalPlaybackProc)挂钩过程。（Windows11已弃用）WH_JOURNALRECORD安装一个挂钩过程，记录发布到系统消息队列的输入消息。这个钩子对于录制宏很有用。有关详细信息，请参阅JournalRecordProc)挂钩过程。（Windows11已弃用）WH_KEYBOARD安装一个监视击键消息的挂钩过程。有关详细信息，请参阅KeyboardProc)挂钩过程。WH_KEYBOARD_LL安装一个监视低级键盘输入事件的挂钩过程。有关详细信息，请参阅LowLevelKeyboardProc)挂钩过程。WH_MOUSE安装一个监视鼠标消息的挂钩程序。有关详细信息，请参阅MouseProc)挂钩过程。WH_MOUSE_LL安装一个钩子程序来监视低级鼠标输入事件。有关详细信息，请参阅LowLevelMouseProc)挂钩过程。WH_MSGFILTER安装一个挂钩过程，用于监视由于对话框、消息框、菜单或滚动条中的输入事件而生成的消息。有关详细信息，请参阅MessageProc)挂钩过程。WH_SHELL安装一个钩子过程，它接收对 shell 应用程序有用的通知。有关详细信息，请参阅ShellProc)挂钩过程。WH_SYSMSGFILTER安装一个挂钩过程，用于监视由于对话框、消息框、菜单或滚动条中的输入事件而生成的消息。挂钩过程监视与调用线程位于同一桌面的所有应用程序的这些消息。有关详细信息，请参阅SysMsgProc)挂钩过程。似乎也不能通过这种方式进行一些hook...普遍是通过这种方式对键盘输入事件进行监视，而后通过相应的回调函数对指定的进程进行拦截详细过程可以参考其他文章。这里就不赘述
【CTF】Reverse Writeup Win API逆向，Python字节码 OLLVM，rc4 换表base64 2022DASCTF Oct 逆向学习
Win API逆向 反调试， Python字节码 ，OLLVM rc4 换表base64贪玩ctfTlsCallback_0有个Isdebuggerpresent反调试 直接nop就行这边messagebox有个小技巧，把中文显示出来，选这个直接显示中文 非常的方便通过中文定位到关键代码发现有个加密 往下翻可以看到可疑加密函数 进入后可以发现频繁调用sbox进行加密操作 并且有两个sbox 猜测为aes 实际动调后发现256字节的两个sbox回到前面先把account解出来acc = [0x04, 0x1F, 0x1F, 0x1E, 0x43, 0x4B, 0x43, 0x45, 0x44, 0x00, 0x16, 0x10, 0x55, 0x17, 0x12, 0x73] for i in range(len(acc)-1): acc[i] ^= acc[15] print(chr(acc[i]), end='') print(chr(acc[15]), end='') #wllm08067sec&das然后拿到密码的密文，这边动调能看到其实是pass的位置。找了一阵子没找到aes的key 但是想着key和account位数好像一致，直接拿去赛博厨子试一下 然后出了flag账号密码拼起来就行pycode队友做的 逆一下字节码然后用z3解就行了from Crypto.Util import number from Crypto.Util.number import * m=b"" from z3 import * def solve(x): a, b, c, d = BitVecs("a b c d", 35) s = Solver() s.add((a >> 11) ^ a == b) s.add(((b << 7) & 0x78866191) ^ b == c) s.add(((c << 15) & 2323163360) ^ c == d) s.add((d >> 18) ^ d == x) an = s.check() print(an) return long_to_bytes(s.model()[a].as_long()^(2**35-1)) enc = "8b2e4e858126bc8478d6a6a485215f03" enc=bytes.fromhex(enc) for i in range(len(enc) // 4): t = enc[i*4:i*4+4] t = number.bytes_to_long(t) m+=solve(t) print(bytes.hex(m))challenge改一下变量名字 断到memset前面可以发现上面一堆是把input的奇数位偶数位单独分开拿出来然后往下看 第一个函数sub4059f0没对input操作 不用管 往下看enc1这个 传了偶数位和sbox进去点进去纯纯的控制流平坦化，D-810一把梭，但是还是很丑（后续发现可能是我d810的插件的z3寄了 没去除完整？）但是看到这个，纯纯的rc4rc4对称加密那直接断点到这个函数前面，然后随便输入点东西，然后用插件把内存里面数据直接改成密文，断点到这个地方，直接看密文即可（不可以断到函数外面，因为下面有引起异常的，程序直接结束了）得到"ACFg0Gw1Jo5Ix9C}"try catch调不了enc2咋整 断点在这个位置直接改eip或者直接nop掉call enc1 让程序直接jmp到catch里面也行但是我们目的根本就不是看enc2是啥 enc2一眼丁真base64换表。目的只是拿到catch块里面换掉的表而已这里有个if，实际调起来会发现会直接退出 那jnz改成jz就行了 暴力一点然后断到这里直接看表即可当然你直接c跑出来表也行 毕竟代码都给了拿到表 直接赛博厨子一把梭最后拼起来就行了a = 'ACFg0Gw1Jo5Ix9C}' b = 'DST{Wo7Xj5Ad8Nx8' flag = '' for i in range(16): flag += b[i] flag += a[i] print(flag)
【CTF】Reverse Writeup TracerPid反调试，BUG()内核异常 2022长城杯决赛 逆向学习
前言长城杯决赛的逆向题目 感觉还是有点意思的 这种读TracerPid的反调试和BUG()内核异常跳转以前没见过 比赛的时候疯狂搜索 也没个解决办法，后来是自己试着调试调试把这个反调试过掉了 然后大概摸清楚了题目的运行逻辑。总的来说这题还是很有趣的。直接看题吧easy_re首先我们拿到题目，进来是start函数，这边可以看到main，init点进去init可以发现加载了三个函数，依次点进去看第一个是一些奇怪的操作，但是可以看到一个函数，这个是检测反调试的第二个是一个TracerPid反调试加BUG（）异常第三个不知道是啥玩意，感觉没啥用经过动态调试，f8断点可以发现程序是先执行init for循环里面的第一个函数，第二个函数，第一个函数里面的那个函数。我们过反调试也很简单，只要在这个位置断点，然后点进cmp左边的，按dddd把里面第一行的pid数字改成0即可然后进入到第一个函数里面的那个函数，这个其实是检测反调试的，之前被卡在这里卡了好久，一直不懂他是怎么传参的，后面发现其实是和之前运行的那个反调试有关系。把反调试过了之后也就进入主函数正常执行了。不过这底下有个函数需要注意，这个是init替换的主函数，实际上这个函数是当成main执行的。点进去看，可以发现有一堆东西，仔细观察汇编可以发现，对用户输入前四位和一些变量进行操作，最后要使得前四个变量值为0，最后可以得到flag。那我们直接用c爆破前四位输入#include<stdio.h> int main(){ int inp[4]; for (int i=30;i<127;i++){ for (int j=30;j<127;j++){ for (int m=30;m<127;m++){ for (int n=30;n<127;n++){ inp[0]=i; inp[1]=j; inp[2]=m; inp[3]=n; int dword_5611F8803040=0; int dword_5611F880303C=0; int dword_5611F8803030=0; int dword_5611F8803034=0; int dword_5611F8803038=0; dword_5611F8803040 += 16; dword_5611F880303C += dword_5611F8803040 * inp[3]; dword_5611F8803040 += 3; dword_5611F880303C += dword_5611F8803040 * inp[2]; dword_5611F8803040 -= 10; dword_5611F8803030 += dword_5611F8803040 * inp[3]; dword_5611F8803040 -= 2; dword_5611F8803034 += dword_5611F8803040 * inp[2]; dword_5611F8803040 += 13; dword_5611F8803030 += dword_5611F8803040 * inp[1]; dword_5611F8803040 -= 8; dword_5611F8803038 += dword_5611F8803040 * inp[1]; dword_5611F8803040 -= 7; dword_5611F8803038 -= 3481; dword_5611F8803040 += 3; dword_5611F8803034 -= 2422; dword_5611F8803040 += 9; dword_5611F8803030 += dword_5611F8803040 * inp[2]; dword_5611F8803040 -= 2; dword_5611F8803038 += dword_5611F8803040 * inp[2]; dword_5611F8803040 -= 6; dword_5611F8803030 -= 4518; dword_5611F8803040 += 7; dword_5611F880303C -= 5006; dword_5611F8803040 -= 9; dword_5611F8803034 += dword_5611F8803040 * inp[0]; dword_5611F8803040 += 5; dword_5611F8803030 += dword_5611F8803040 * inp[0]; dword_5611F8803038 += ++dword_5611F8803040 * inp[0]; dword_5611F8803040 -= 8; dword_5611F8803038 += dword_5611F8803040 * inp[3]; dword_5611F8803040 += 14; dword_5611F880303C += dword_5611F8803040 * inp[0]; dword_5611F8803040 -= 11; dword_5611F8803034 += dword_5611F8803040 * inp[3]; dword_5611F8803040 += 3; dword_5611F880303C += dword_5611F8803040 * inp[1]; dword_5611F8803040 -= 2; dword_5611F8803034 += dword_5611F8803040 * inp[1]; if(dword_5611F8803030==0 && dword_5611F8803034==0 && dword_5611F8803038==0 && dword_5611F880303C==0){ printf("%c %c %c %c",i,j,m,n); break; } } } } } }可以得到前四位flag，然后下面是根据前四位和之前v27的密文进行异或得到后面的flags=[0x0064, 0x0010, 0x007C, 0x007C, 0x0022, 0x0017, 0x002F, 0x0034, 0x0013, 0x0013, 0x000B, 0x003B, 0x001F, 0x001B, 0x000F, 0x0000, 0x001E, 0x0005, 0x0072, 0x003C, 0x0062, 0x001F, 0x0036, 0x0004, 0x001F, 0x002E, 0x0032, 0x003F] print(len(s)) input='QTEM' flag='' for i in range(len(s)): flag += chr(s[* i] ^ ord(input[i % 4])) print(flag) 'QTEM5D91sCjyBGNvNOJMOQ7q3KsINzwr'得到最终flag总结长城杯这次决赛就做了两题 其他方向还是不太会 但是我们团队得分应该是非常靠前的（有niyah大爹排第二）题目还是做慢了，动态调试技巧还需要提升。
【CTF】Reverse Writeup 魔改tea，smc加密与验证 2022hws 逆向学习
re1直接关键词捕捉Tea 那就是tea加密吧点进tea那个函数里面稍微美化下，可以发现其实是魔改过的xxtea，跑12轮，分了8组。那就直接开逆改一下现成的脚本#include<stdio.h> #include"defs.h" #include <stdint.h> unsigned int enc_2[8] = { 0x10BD3B47, 0x6155E0F9, 0x6AF7EBC5, 0x8D23435F, 0x1A091605, 0xD43D40EF, 0xB4B16A67, 0x6B3578A9 }; unsigned int data[8] = { 0xD58F4C91, 0x9A751121, 0xD1F7D6AC, 0x63575833, 0x2A313023, 0xF9750867, 0xB2A9EDA5, 0xD6A7CD37 }; unsigned int data1[8] = { 0x5DFAFC16, 0x3E3D5252, 0x03623808, 0x6B884FEA, 0xE99A75EB, 0xB741A335, 0x0C77D248, 0xE4908612 }; uint32_t DeryptoLoop(unsigned int num1, unsigned int num2, uint32_t sum, uint32_t index) { unsigned int key[4] = { 0x1234,0x2345,0x4567,0x6789 }; //unsigned int key[4] = { 4,3,2,2 }; uint32_t tmp1 = ((num1 >> 3) ^ 16 * num2) + (4 * num1 ^ (num2 >> 5)); uint32_t tmp2 = (key[(sum >> 2) & 3 ^ index] ^ num2) + (num1 ^ sum); return tmp1 ^ tmp2; } void decrypt2(unsigned dec_2[8]) { unsigned int state[8]; unsigned int DELTA = 0x6a99b4ac; unsigned int key[4] = { 0x1234,0x2345,0x4567,0x6789 }; int round = 0; for (int i = 0; i < 8; i++) { state[i] = enc_2[i]; } int tmpd = DELTA; do { state[7] -= DeryptoLoop(state[0], state[6], tmpd, 3); state[6] -= DeryptoLoop(state[7], state[5], tmpd, 2); state[5] -= DeryptoLoop(state[6], state[4], tmpd, 1); state[4] -= DeryptoLoop(state[5], state[3], tmpd, 0); state[3] -= DeryptoLoop(state[4], state[2], tmpd, 3); state[2] -= DeryptoLoop(state[3], state[1], tmpd, 2); state[1] -= DeryptoLoop(state[2], state[0], tmpd, 1); state[0] -= DeryptoLoop(state[1], state[7], tmpd, 0); tmpd += 0x61C88647; round += 1; } while (round < 12); for (int i = 0; i < 8; i++) { dec_2[i] = state[i]; } } int main(){ unsigned int dec_2[8]; decrypt2(dec_2); for (int i = 0; i < 8; i++) { for (int j = 0; j < 4; j++) { printf("%c", (dec_2[i] >> (j * 8)) & 0xFF); } } //puts(dec_2); //0x33323130, 0x37363534, 0x31303938, 0x35343332, 0x39383736, 0x33323130, 0x37363534, 0x31303938 return 0; }其中有几个要注意的地方，首先就是初始的delta需要动调一下，先动调跑到最后一轮拿到delta然后再拿来解密。然后就是key是异或0123位这样最后得到flag:7f943921724d63dc0ac9c6febf99fa88re2ida打开可以发现有个TlsCallback_0，main里面有一些怪东西，动调可以发现把输入的东西读入到了byte_41B2EC然后看TlsCallback_0，经典smc异或，然后新线程执行。动调之后可以看代码，那么直接断在这里然后看401040的东西，上面一坨拿代码段的字节加起来然后取一个byte，然后用这个和输入进行异或（这个代码段还加了几个花指令，我直接被骗，patch之后发现后面跑不出来.....），下面一堆不用看，直接看最下面v6是上面一堆操作得到的，和输入没有关系，然后异或，最后进行验证这个验证循环也比较有意思，他把密文直接放在代码段里面了，可以看到k+4199175,这个地址在代码段里面，过去把密文复制了就行我最终选择的方式是直接改内存，因为这一堆异或，都是可逆的。先直接把最上面的sleep上的dword改了，再改一下数组长度和输入，直接把输入的改成密文即可断在这里点进v7即可看到flag
babynim这题比初赛的简单多了我是没想到的...就是符号表也有，复现了初赛的LOADER之后这题没有难度属于是。ida打开，动态调试到主要逻辑，简单分析，这里验证flag格式分析可以知道star开头的函数进行了乘法，第一个，第二个参数是input与密文1，也就是一串反着的flag(浪费了我的时间。。。，然后第三个参数是结果。eq函数进行了比较，可以把最终的密文提取出来，在hello44里面这里eqeq函数就是对大数进行比较提取数据出来写个python脚本除一下即可得到flagprint(0x1566336316561EDB086701C71C71C7) print(chr(0xa7)) a=0x666C61677B63393461626261652D353263302D346461302D616666312D383839373730353839356337A7 b=0x4732510F4379F99925CF3B2C29EAFA1647B1E897A61BFD7C1D1807478D0E18A1AC9791D6DBB1BD74554FECECC16519CE12AC443DA1877CA8D9 print(b//a) ''' s='7c5985077988-1ffa-0ad4-0c25-eabba49c{galf' ss='' for i in reversed(s): ss+=i print(ss) print(len('c94abbae-52c0-4da0-aff1-8897705895c7')) '''
【CTF】Reverse Writeup deeprev+GameMaster等 强网杯 逆向学习
deeprev这题非常新颖，直接在elf里面的LOAD段写机器码，加载汇编并且跳转过去执行最开始其实是常规动调，注意到链接了so文件并且读取了so里面的secret到elf中，那么直接在elf中的secret上面下一个内存读写断点。（这边我给check也下了）然后运行，可以发现载入so库的时候就完成了加密，等到执行到main函数的时候check已经有值了。我自己是通过一直f7步入，ida提示eip跳转到非代码段，从而发现了LOAD段藏起来的机器码。仔细看一下ida的注释就可以发现这边复制了so的secret然后进一步动调加分析可以得到每一片段其实是这样的，每一行第二位是操作码，第一位是偏移地址，第三位是值。动调可以发现有很多重复的这样的片段，并且每一段都把第三行的第三位给第二行的偏移地址，简单理解R_X86_64_RELATIVE，操作码0x8是赋值。在进一步动调之后发现0xc3操作码是标志下一步跳转进LOAD段执行代码那么我们其实只需要把所有的机器码提取出来，再扔到机器码转汇编的网站上面解密就行了。直接复制LOAD段内容，提取机器码s='' with open("qwbre2data","r") as file: for line in file: s+=line[line.find("<"):line.find(">")+1] s+='\n' with open("qwbre2data1","w") as file1: file1.write(s) f=0 with open("qwbre2data1","r") as file2: for line in file2: if "0C3" in line: f=1 continue if f==1: temp=line[line.find(",")+1:] temp1=temp[temp.find(",")+2:temp.find("h")] if len(temp1)>8: if len(temp1)!=16 and len(temp1)>14: temp1=temp1[::-1] temp1+="0" temp1=temp1[::-1] reopcode=temp1 opcode='' for i in reversed(range(0,len(reopcode),2)): opcode+=reopcode[i] opcode+=reopcode[i+1] print(opcode) f=0 ''' 803425CC40800016 800425CC4080 803425CC40800017 800425CC40800001 803425CC40800010 800425CC40800002 803425CC40800012 800425CC40800003 803425CC40800010 800425CC40800004 803425CC40800011 800425CC40800005 803425CC40800012 800425CC40800006 803425CC40800013 800425CC40800007 803425CC40800014 800425CC40800008 803425CC40800015 800425CC40800009 803425CC40800016 800425CC4080000A 803425CC40800017 800425CC4080000B 803425CC40800018 800425CC4080000C 803425CC40800019 800425CC4080000D 803425CC40800024 800425CC4080000E 803425CC4080002C 800425CC4080000F 803425CC40800026 800425CC40800010 803425CC4080001E 800425CC40800011 803425CC4080001F 800425CC40800012 803425CC40800020 800425CC40800013 803425CC40800020 800425CC40800014 803425CC40800021 800425CC40800015 803425CC40800023 800425CC40800016 803425CC40800027 800425CC40800017 803425CC40800024 800425CC40800018 803425CC40800025 800425CC40800019 803425CC40800026 800425CC4080001A 803425CC40800027 800425CC4080001B 803425FC40800070 803425FC4080007C 803425FC40800073 803425FC40800078 803425FC4080006F 803425FC40800027 803425FC4080002A 803425FC4080002C 803425FC4080007F 803425FC40800035 803425FC4080002D 803425FC40800032 803425FC40800037 803425FC4080003B 803425FC40800022 803425FC40800059 803425FC40800053 803425FC4080008E 803425FC4080003D 803425FC4080002A 803425FC40800059 803425FC40800027 803425FC4080002D 803425FC40800029 803425FC40800034 803425FC4080002D 803425FC40800061 803425FC40800032 8034255C4180006C 8034255C418000A100 8034255C418000B100 8034255C418000E500 '''然后丢到网站里面shell-storm | Online Assembler and Disassembler最后是要改一下，多了0删掉，少了0补上。得到汇编xor byte ptr [0x8040cc], 0x16 add byte ptr [0x8040cc], 0 xor byte ptr [0x8040cc], 0x17 add byte ptr [0x8040cc], 1 xor byte ptr [0x8040cc], 0x10 add byte ptr [0x8040cc], 2 xor byte ptr [0x8040cc], 0x12 add byte ptr [0x8040cc], 3 xor byte ptr [0x8040cc], 0x10 add byte ptr [0x8040cc], 4 xor byte ptr [0x8040cc], 0x11 add byte ptr [0x8040cc], 5 xor byte ptr [0x8040cc], 0x12 add byte ptr [0x8040cc], 6 xor byte ptr [0x8040cc], 0x13 add byte ptr [0x8040cc], 7 xor byte ptr [0x8040cc], 0x14 add byte ptr [0x8040cc], 8 xor byte ptr [0x8040cc], 0x15 add byte ptr [0x8040cc], 9 xor byte ptr [0x8040cc], 0x16 add byte ptr [0x8040cc], 0xa xor byte ptr [0x8040cc], 0x17 add byte ptr [0x8040cc], 0xb xor byte ptr [0x8040cc], 0x18 add byte ptr [0x8040cc], 0xc xor byte ptr [0x8040cc], 0x19 add byte ptr [0x8040cc], 0xd xor byte ptr [0x8040cc], 0x24 add byte ptr [0x8040cc], 0xe xor byte ptr [0x8040cc], 0x2c add byte ptr [0x8040cc], 0xf xor byte ptr [0x8040cc], 0x26 add byte ptr [0x8040cc], 0x10 xor byte ptr [0x8040cc], 0x1e add byte ptr [0x8040cc], 0x11 xor byte ptr [0x8040cc], 0x1f add byte ptr [0x8040cc], 0x12 xor byte ptr [0x8040cc], 0x20 add byte ptr [0x8040cc], 0x13 xor byte ptr [0x8040cc], 0x20 add byte ptr [0x8040cc], 0x14 xor byte ptr [0x8040cc], 0x21 add byte ptr [0x8040cc], 0x15 xor byte ptr [0x8040cc], 0x23 add byte ptr [0x8040cc], 0x16 xor byte ptr [0x8040cc], 0x27 add byte ptr [0x8040cc], 0x17 xor byte ptr [0x8040cc], 0x24 add byte ptr [0x8040cc], 0x18 xor byte ptr [0x8040cc], 0x25 add byte ptr [0x8040cc], 0x19 xor byte ptr [0x8040cc], 0x26 add byte ptr [0x8040cc], 0x1a xor byte ptr [0x8040cc], 0x27 add byte ptr [0x8040cc], 0x1b xor byte ptr [0x8040fc], 0x70 xor byte ptr [0x8040fc], 0x7c xor byte ptr [0x8040fc], 0x73 xor byte ptr [0x8040fc], 0x78 xor byte ptr [0x8040fc], 0x6f xor byte ptr [0x8040fc], 0x27 xor byte ptr [0x8040fc], 0x2a xor byte ptr [0x8040fc], 0x2c xor byte ptr [0x8040fc], 0x7f xor byte ptr [0x8040fc], 0x35 xor byte ptr [0x8040fc], 0x2d xor byte ptr [0x8040fc], 0x32 xor byte ptr [0x8040fc], 0x37 xor byte ptr [0x8040fc], 0x3b xor byte ptr [0x8040fc], 0x22 xor byte ptr [0x8040fc], 0x59 xor byte ptr [0x8040fc], 0x53 xor byte ptr [0x8040fc], 0x8e xor byte ptr [0x8040fc], 0x3d xor byte ptr [0x8040fc], 0x2a xor byte ptr [0x8040fc], 0x59 xor byte ptr [0x8040fc], 0x27 xor byte ptr [0x8040fc], 0x2d xor byte ptr [0x8040fc], 0x29 xor byte ptr [0x8040fc], 0x34 xor byte ptr [0x8040fc], 0x2d xor byte ptr [0x8040fc], 0x61 xor byte ptr [0x8040fc], 0x32 xor byte ptr [0x80415c], 0x6c xor byte ptr [0x80415c], 0xa1 xor byte ptr [0x80415c], 0xb1 xor byte ptr [0x80415c], 0xe5然后手搓一个解密，会发现给的flag缺了四位，看到hint给了sha256那直接加个sha256爆破（不爆破可能就要写脚本patchso文件了）import hashlib print(chr(0x6c)) enc=[0x70,0x7c,0x73,0x78,0x6f,0x27,0x2a,0x2c,0x7f,0x35,0x2d,0x32,0x37,0x3b,0x22,0x59,0x53,0x8e,0x3d,0x2a,0x59,0x27,0x2d,0x29,0x34,0x2d,0x61,0x32,0x6c,0xa1,0xb1,0xe5] s=[0x16,0,0x17,1,0x10,2,0x12,3,0x10,4,0x11,5,0x12,6,0x13,7,0x14,8,0x15,9,0x16,0xa,0x17,0xb,0x18,0xc,0x19,0xd,0x24,0xe,0x2c,0xf,0x26,0x10,0x1e,0x11,0x1f,0x12,0x20,0x13,0x20,0x14,0x21,0x15,0x23,0x16,0x27,0x17,0x24,0x18,0x25,0x19,0x26,0x1a,0x27,0x1b] s1=[] s2=[] for i in range(0,len(s),2): s1.append(s[i]) s2.append(s[i+1]) flag='' for i in range(len(s1)): enc[i]-=i enc[i]^=s1[i] flag+=chr(enc[i]) print(flag) for i in range(127): for j in range(127): for p in range(127): flag='flag{366c950370fec47e34581a0' flag+=(chr(i)+chr(j)+chr(p)+"}") data_sha = hashlib.sha256(flag.encode('utf-8')).hexdigest()[0:16] if("f860464d767610bb"==data_sha): print(flag) break print(len(s1))GameMaster感觉这个还能看一看...dnspy打开直接源码，给了三个文件，dll和exe拖进去，然后可以看到有个很长的金手指方法，输入14位字符再按esc即可有几个金手指对数据文件进行了操作，第一个是异或，第二个是aes解密，第三个是反序列化 ，把反编译出来的三坨加密直接丢到vs里面跑一下，然后把文件保存一下，可以得到解密后的//internal class Program using System; using System.IO; using System.Runtime.Serialization.Formatters.Binary; using System.Security.Cryptography; namespace test { class Program { private static byte[] memory; private static byte[] m; static void Main(string[] args) { FileStream fileStream = File.OpenRead("D:\\CTF\\ctf比赛文件\\2022qwb\\gamemessage"); int num = (int)fileStream.Length; Program.memory = new byte[num]; fileStream.Position = 0L; fileStream.Read(Program.memory, 0, num); for (int i = 0; i < Program.memory.Length; i++) { byte[] array = Program.memory; int num1 = i; array[num1] ^= 34; } //Environment.SetEnvironmentVariable("AchivePoint1", game.Player.Balance.ToString()); byte[] key = new byte[] { 66, 114, 97, 105, 110, 115, 116, 111, 114, 109, 105, 110, 103, 33, 33, 33 }; ICryptoTransform cryptoTransform = new RijndaelManaged { Key = key, Mode = CipherMode.ECB, Padding = PaddingMode.Zeros }.CreateDecryptor(); Program.m = cryptoTransform.TransformFinalBlock(Program.memory, 0, Program.memory.Length); //Environment.SetEnvironmentVariable("AchivePoint2", game.Player.Balance.ToString()); //Environment.SetEnvironmentVariable("AchivePoint3", game.Player.Balance.ToString()); BinaryFormatter binaryFormatter = new BinaryFormatter(); MemoryStream serializationStream = new MemoryStream(Program.m); binaryFormatter.Deserialize(serializationStream); FileStream fsWrite = File.OpenWrite("D:\\CTF\\ctf比赛文件\\2022qwb\\gamemessage1"); int num2 = (int)Program.m.Length; fileStream.Position = 0L; fsWrite.Write(Program.m, 0, num2); } } }解出来这个文件就好做了，丢到010看一下，在中间发现了MZ头标志，提取出来发现还是.NET然后再丢到dnspy里面可以看到加密方法using System; using System.Linq; using System.Text; using System.Windows.Forms; namespace T1Class { // Token: 0x02000002 RID: 2 public class T1 { // Token: 0x06000001 RID: 1 RVA: 0x00002050 File Offset: 0x00000250 private static void Check1(ulong x, ulong y, ulong z, byte[] KeyStream) { int num = -1; for (int i = 0; i < 320; i++) { x = (((x >> 29 ^ x >> 28 ^ x >> 25 ^ x >> 23) & 1UL) | x << 1); y = (((y >> 30 ^ y >> 27) & 1UL) | y << 1); z = (((z >> 31 ^ z >> 30 ^ z >> 29 ^ z >> 28 ^ z >> 26 ^ z >> 24) & 1UL) | z << 1); bool flag = i % 8 == 0; if (flag) { num++; } KeyStream[num] = (byte)((long)((long)KeyStream[num] << 1) | (long)((ulong)((uint)((z >> 32 & 1UL & (x >> 30 & 1UL)) ^ (((z >> 32 & 1UL) ^ 1UL) & (y >> 31 & 1UL)))))); } } // Token: 0x06000002 RID: 2 RVA: 0x00002110 File Offset: 0x00000310 private static void ParseKey(ulong[] L, byte[] Key) { for (int i = 0; i < 3; i++) { for (int j = 0; j < 4; j++) { Key[i * 4 + j] = (byte)(L[i] >> j * 8 & 255UL); } } } // Token: 0x06000003 RID: 3 RVA: 0x0000215C File Offset: 0x0000035C public T1() { try { string environmentVariable = Environment.GetEnvironmentVariable("AchivePoint1"); string environmentVariable2 = Environment.GetEnvironmentVariable("AchivePoint2"); string environmentVariable3 = Environment.GetEnvironmentVariable("AchivePoint3"); bool flag = environmentVariable == null || environmentVariable2 == null || environmentVariable3 == null; if (!flag) { ulong num = ulong.Parse(environmentVariable); ulong num2 = ulong.Parse(environmentVariable2); ulong num3 = ulong.Parse(environmentVariable3); ulong[] array = new ulong[3]; byte[] array2 = new byte[40]; byte[] array3 = new byte[40]; byte[] array4 = new byte[12]; byte[] first = new byte[] { 101, 5, 80, 213, 163, 26, 59, 38, 19, 6, 173, 189, 198, 166, 140, 183, 42, 247, 223, 24, 106, 20, 145, 37, 24, 7, 22, 191, 110, 179, 227, 5, 62, 9, 13, 17, 65, 22, 37, 5 }; byte[] array5 = new byte[] { 60, 100, 36, 86, 51, 251, 167, 108, 116, 245, 207, 223, 40, 103, 34, 62, 22, 251, 227 }; array[0] = num; array[1] = num2; array[2] = num3; T1.Check1(array[0], array[1], array[2], array2); bool flag2 = first.SequenceEqual(array2); if (flag2) { T1.ParseKey(array, array4); for (int i = 0; i < array5.Length; i++) { array5[i] ^= array4[i % array4.Length]; } MessageBox.Show("flag{" + Encoding.Default.GetString(array5) + "}", "Congratulations!", MessageBoxButtons.OK); } } } catch (Exception) { } } } }发现只有一坨位移加密，那么直接拿去z3即可from z3 import * solver = Solver() byte1=[101,5,80,213,163,26,59,38,19,6,173,189,198,166,140,183,42,247,223,24,106,20,145,37,24,7,22,191,110,179,227,5,62,9,13,17,65,22,37,5] x = [BitVec("x[%d]" % i,64) for i in range(3)] KeyStream = [BitVec("KeyStream[%d]" % i,64) for i in range(len(byte1))] num = -1 for i in range(320): x[0] = (((x[0] >> 29 ^ x[0] >> 28 ^ x[0] >> 25 ^ x[0] >> 23) & 1) | x[0] << 1) x[1] = (((x[1] >> 30 ^ x[1] >> 27) & 1) | x[1] << 1) x[2] = (((x[2] >> 31 ^ x[2] >> 30 ^ x[2] >> 29 ^ x[2] >> 28 ^ x[2] >> 26 ^ x[2] >> 24) & 1) | x[2] << 1) flag = (i % 8 == 0) if (flag): num+=1 KeyStream[num] = ((KeyStream[num] << 1) | ((((x[2] >> 32 & 1 & (x[0] >> 30 & 1)) ^ (((x[2] >> 32 & 1) ^ 1) & (x[1] >> 31 & 1)))))) for i in range(len(byte1)): solver.add(KeyStream[i]==byte1[i]) if solver.check() == sat: m = solver.model() for d in m.decls(): print('%s == %s'% (d.name(),int(str(m[d])))) '''x[0] == 156324965 x[1] == 868387187 x[2] == 3131229747'''解出来三个值然后直接放到代码里面跑就行了，非常的简单 static void Main(string[] args) { ulong num = 156324965; ulong num2 = 868387187; ulong num3 = 3131229747; ulong[] array = new ulong[3]; byte[] array2 = new byte[40]; byte[] array3 = new byte[40]; byte[] array4 = new byte[12]; byte[] first = new byte[] { 101, 5, 80, 213, 163, 26, 59, 38, 19, 6, 173, 189, 198, 166, 140, 183, 42, 247, 223, 24, 106, 20, 145, 37, 24, 7, 22, 191, 110, 179, 227, 5, 62, 9, 13, 17, 65, 22, 37, 5 }; byte[] array5 = new byte[] { 60, 100, 36, 86, 51, 251, 167, 108, 116, 245, 207, 223, 40, 103, 34, 62, 22, 251, 227 }; array[0] = num; array[1] = num2; array[2] = num3; Program.Check1(array[0], array[1], array[2], array2); bool flag2 = first.SequenceEqual(array2); if (flag2) { Program.ParseKey(array, array4); for (int i = 0; i < array5.Length; i++) { array5[i] ^= array4[i % array4.Length]; } MessageBox.Show("flag{" + Encoding.Default.GetString(array5) + "}", "Congratulations!", MessageBoxButtons.OK); } } }改过之后的main，拿着dnspy反编译出来的源码直接放到vs里面跑一下就行上面这两题是比赛的时候穿的，后面的还没复现（（（（绝对不是懒之后复现下apk和子进程调试那题再更在这里面
IDA for RE插件推荐这篇可能会持续更新起因是某开发巨佬群里说让我出个IDA插件合集，那就直接开搞，正好可以水一篇博客涨涨人气（那不多bb直接进入正题LazyIDA链接：https://github.com/P4nda0s/LazyIDA简化逆向流程这个插件个人已经用成习惯了，属于是没有这个插件就逆不下去了（开个玩笑。功能可以直接看图，这里简单介绍一下我个人最常用几个的功能Convert，直接把ida里面数据转换成python list或者c array，而且可以自己选择数据类型，在提取密文的时候就非常的方便。Fill with NOPs，字面意思，直接把选中的区域全部填充为0x90（不过在其他架构上面这个没用）Paste Data，这个功能非常牛，可以直接在动调的时候把数据粘贴在内存里面，有时候我们解rc4或是异或这种对称加密，直接把密文粘贴到input的内存里面，再断点在解密之后的地方，就可以直接拿到flag。其他的功能可以看Github链接里面的readme，都挺好用的Keypatch链接：https://github.com/keystone-engine/keypatch修改汇编代码，Patch程序。这个插件，用ida的应该没人不装吧不过要注意一下的是，如果改完要运行程序的话，改完记得到 ida 的 Edit->Patch program->Apply patches to input file... 应用你的更改FindCrypt链接：https://github.com/polymorf/findcrypt-yara识别程序里面包含的加密类型这个插件通过识别一些加密函数的特征来识别加密类型，例如base64的表，tea加密的delta等等。当然他有时候可能会不太准，那就只能靠自己识别加密了。重要的还是多做题。Finger链接：https://github.com/aliyunav/Finger阿里巴巴出品的函数识别好像是用的阿里的数据库，通过一些函数特征自动识别函数，然后重命名并且把这个函数标记成绿色。有的时候可以省去很多事情例如强网这个easyre，识别出了fork和remove函数，不然还要临时去搜索一下。可以拖进ida的时候直接点一下 Recognize all functions（大佬能一眼看出来的别骂了D-810链接：https://gitlab.com/eshard/d810匹配公式，直接简化代码计算过程可能概况的不是很对，因为才发现这个插件，大概是直接识别代码里面的计算过程，然后转换成相同的更简单的式子，而且好像还能去掉ollvm，看上去很神奇的插件，还没有实际使用测试，不过具体的可以参考一下这个博客目前在用的确实也就这几个插件了，下次等发现新的插件再更新（（（
【CTF】Reverse LOADER pell方程 nim-lang 蓝帽杯 逆向学习
蓝帽杯初赛逆向LOADER复现分析首先一进来直接开幕雷击可以看到加载了一个程序在里面，点进unk可以发现MZ开头的数据，直接猜想里面有一个程序。当时第一反应是dump程序出来，但是发现dump出来的东西运行不了，于是直接放弃了。后来经过wjh大佬指点，可以直接动调。于是直接开调动调这个也可以讲一点，基本上程序进行输入输出的地方肯定是关键函数。这个题要找到关键函数的话，键盘上先多按f8，注意call哪个函数之后程序输出东西了，那么打个断点，然后重新开始程序再f9到这个地方f7进这个call，这样反复，肯定能快速找到主要函数。例如这边就找到main_0了，然后下面几个call其实点进去看一下就差不多知道了，其实主函数在这里（可以熟练用快捷键n去改一下这些函数名，方便识别。）那么调到这里之后，可以继续往下走，发现下面一个就是输入数据，这个时候可以点进函数返回的数据下去看一下，可以看到这其实是一个结构体，我们可以选择进入structures创建一个结构体。当然也可以不创建。这边这一块，点进数据看一下，可以发现其实是检测前五位是不是flag{同理在这边也可以发现是flag格式的检验，同时可以知道flag总长度是42利用同样的方法，可以很快的知道下面的循环在做的事情，也就是提取flag{}括号内前十八位数据放入v13同理也可以得到下面这个函数的作用，就是把字符串变成int值。（听说可以对照着字符串去nim-lang的源码搜，长知识了）接下来是几个关键的函数，同样可以通过对数据的观察，猜测函数做的事情。例如calculate这个，最开始我是不知道他是干嘛的，因为不像之前的str2int，这个出来之后是一堆奇怪的数字。但是能知道的就是他对v28和v27两个这两个变量（实际存放的是输入flag的前十八位的数字）做了什么操作然后给了v31。那么传入两个数字的话，应该是进行了一系列运算。那么可以先拿出v31的数据，与前十八位的数据进行对比。最后我们可以发现其实这个函数是拿前两个参数相乘，给到第三个参数里面。假如还是说不清楚是干嘛的，可以去源码搜一搜，可以发现是运算的函数，基本上猜一下也就出来了。同理，有好几个乘法，还有一个减法最后这里是一个check，动调可以在上面直接拿到密文9于是我们最后可以得出一个式子$$ inp1^2-11*(inp2^2)=9 $$同时数字需要为18位数也就是100000000000000000<inp1<999999999999999999解答我们把式子直接拿到网站上面可以解出来结果拼接起来发现会少一位，发现y只有17位，那么加个-或者前面加个0应该都行。补充观察到别的师傅的wp里面的x是有一个确切的范围的，应该是前面这一部分判定了x的范围。总结比赛的时候去看取证了，re没做，实际复现起来的话，应该是花了挺久的，可能有三四个小时（还是在看了一点wp，知道是解方程之后才完成的。）不过比赛的时候，可能不会这么顺利，因为会想着把东西dump出来，或者是卡在加减乘除的识别上面。还是做题少了