这道题来自西湖论剑,关键代码是splay平衡树,俺不会
从栈里可以看见格式是%25s
随便输入之后看ret返回之后的代码
外层逻辑很清晰
只要经过4022B7 call 004020C2
之后 al!=0
就行了
进入之后呢入眼就是一个strlen函数 想走到这里看看能不能知道所需求的长度
然而并不能 这里只是根据长度来进行下面的循环而已
再往下跟一下可以知道是将输入的每一位与0x4C和0x52相比 这两位分别是’L’和’R’
然后再往下走就直接跳到上面的循环并且计数器加一,说明输入只能是’L’和’R’
粗略分析中间的代码,并且经过调试,可以看出是将每一位’L’或’R’传入call中,然后检查经过4个call之后的eax返回值,如果返回0会直接return
直接return肯定是不能的
但是分析中间各个的call,即使是用上了IDA的反编译来分析,也并没有理清程序的逻辑
后来想着 这个只需要输入’L’和’R’ 长度为25位
就尝试着在4个call下面那一句判断下断点,
每次多加一位,不是L就是R,都不是就回到上一位,
不过这种方法实在是难受
于是我把这4个地方都hook了一下
这样的结果是如果返回0就打印当前的flag索引值然后退出
然后用了管道进行输入输出交互 代码如下
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
| #pragma warning(disable:4996) #include <iostream> #include <windows.h> #define BUFSIZE 4096
int main() { HANDLE hReadPipe1, hWritePipe1, hReadPipe2, hWritePipe2; SECURITY_ATTRIBUTES sa{ sizeof(sa),NULL,true }; CreatePipe(&hReadPipe1, &hWritePipe1, &sa, 0); CreatePipe(&hReadPipe2, &hWritePipe2, &sa, 0);
STARTUPINFO si = {}; si.cb = sizeof(STARTUPINFO); GetStartupInfoW(&si); si.wShowWindow = SW_HIDE; si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES; si.hStdInput = hReadPipe2;; si.hStdError = si.hStdOutput = hWritePipe1;
std::string flag; char cbBuf[4096] = {}; PROCESS_INFORMATION pi = {}; DWORD dwRead, dwAvail, dwWrite; WCHAR pCommandLine[] = L"C:\\Users\\Administrator\\Desktop\\Cellular_patch.exe"; while (flag.length() != 25) { int temp{}; pi = {}; flag.push_back('R'); CreateProcess(NULL, pCommandLine, NULL, NULL, TRUE, NULL, NULL, NULL, &si, &pi);
flag.push_back('\r'); flag.push_back('\n');
WriteFile(hWritePipe2, flag.c_str(), flag.length() + 1, &dwWrite, NULL); Sleep(10); flag.pop_back(); flag.pop_back();
dwRead = 0; if (PeekNamedPipe(hReadPipe1, cbBuf, BUFSIZE, &dwRead, &dwAvail, 0) && dwRead) { ReadFile(hReadPipe1, cbBuf, BUFSIZE, &dwRead, NULL); sscanf(cbBuf, "Path:%d", &temp); } else temp = flag.length() - 1;
CloseHandle(pi.hProcess); CloseHandle(pi.hThread);
if (temp) { int res{ 0 }; while (1) { if (flag[temp - res] != 'L') { flag[temp - res] = 'L'; break; } else res++; } } else if (flag.length() == 25) { printf("flag{md5(%s)}", flag.c_str()); break; } } CloseHandle(hReadPipe1); CloseHandle(hWritePipe1); CloseHandle(hReadPipe2); CloseHandle(hWritePipe2); getchar(); }
|
还要注意的是要把程序结束后system(“pause”)nop掉 不然看不见输出
运行得到最后的结果是
不过这个题目的话 还有一个师傅的一个爆破思路 我觉得很有创造性,学到许多
所以也在这里写写
首先写了一个main函数,是他那个主函数的main函数
然后把输入改成了循环 从1位到25位RL的全排列
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
| #include <iostream>
typedef void* (__cdecl* PFNHONEYCOMB)(void* thiz);
typedef void(__cdecl* PFNINCDEPT)(void* thiz); typedef bool(__cdecl* PFNCHECKFLAG)(void* thiz, char* szBuff);
int main() { PFNHONEYCOMB pfnHoneycomb = (PFNHONEYCOMB)0x402202; PFNINCDEPT pfnIncDept = (PFNINCDEPT) 0x4012F0; PFNCHECKFLAG pfnCheckFlag = (PFNCHECKFLAG)0x4020C2; char szThiz[8] = {0}; char szFlag[32] = { 0 };
for (int i = 1; i <= 25; i++) { for (int j = 0; j < (1 << i); j++) { for (int k = 0; k <i; k++) { szFlag[k] = 'L'; } szFlag[i] = 0;
int tmp = j; int bit = 0; while (tmp > 0) { if (tmp % 2 == 1) { szFlag[bit] = 'R'; } bit++; tmp /= 2; }
for (auto& a : szThiz) { a = 0; }
pfnHoneycomb(szThiz);
for (int x = 0; x <= 4; ++x) { pfnIncDept(szThiz); }
bool bResult = pfnCheckFlag(szThiz, szFlag); if (bResult) { goto LB_END; }
}
}
LB_END: printf("%s\r\n", szFlag); return 0;
}
|
然后在反汇编软件中 复制这个程序的main函数的字节码
再在进入Cellular的主函数后 分配一段空白内存
在空白内存中粘贴字节码 然后修改EIP到这个地方 于是这个程序就会执行上面的代码了
于是这个程序就自己问自己这些flag是不是对的
是就输出flag
还要注意的是printf的地方要改一下 连带””%s\r\n”也要在后面加一下
但是这个程序他本身是有问题的,用一些特定的输入会跑蹦这个代码
如果再把上面那个printf取消注释 就可以看到代码在这个地方出现了异常
这如果要解决还要去修复他原来的程序,实在麻烦就没有继续往下做了
但是这个思路确实是强啊