目录
滴水逆向03-17
- #include
-
- LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
-
- int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
- {
- // 定义窗口类
- WNDCLASS wc = { 0 };
- wc.lpfnWndProc = WndProc;
- wc.hInstance = hInstance;
- wc.lpszClassName = L"MyWindowClass";
- wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
-
- // 注册窗口类
- if (!RegisterClass(&wc))
- {
- MessageBox(NULL, L"窗口注册失败!", L"错误", MB_ICONERROR);
- return 0;
- }
-
- // 创建窗口
- HWND hwnd = CreateWindow(
- L"MyWindowClass", // 窗口类名
- L"Hello, Windows!", // 窗口标题
- WS_OVERLAPPEDWINDOW, // 窗口样式
- CW_USEDEFAULT, CW_USEDEFAULT, // 窗口位置
- 400, 200, // 窗口大小
- NULL, // 父窗口
- NULL, // 菜单句柄
- hInstance, // 实例句柄
- NULL // 附加参数
- );
-
- if (!hwnd)
- {
- MessageBox(NULL, L"窗口创建失败!", L"错误", MB_ICONERROR);
- return 0;
- }
-
- // 显示窗口
- ShowWindow(hwnd, iCmdShow);
- UpdateWindow(hwnd);
-
- // 进入消息循环
- MSG msg;
- while (GetMessage(&msg, NULL, 0, 0))
- {
- TranslateMessage(&msg);
- DispatchMessage(&msg);
- }
-
- return msg.wParam;
- }
-
- LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
- {
- switch (msg)
- {
- case WM_PAINT:
- {
- PAINTSTRUCT ps;
- HDC hdc = BeginPaint(hwnd, &ps);
- TextOut(hdc, 10, 10, L"Hello, Windows!", 15);
- EndPaint(hwnd, &ps);
- }
- return 0;
-
- case WM_CLOSE:
- DestroyWindow(hwnd);
- return 0;
-
- case WM_DESTROY:
- PostQuitMessage(0);
- return 0;
- }
-
- return DefWindowProc(hwnd, msg, wParam, lParam);
- }
链接:https://pan.baidu.com/s/1CfdGhw4S-iHOlYu-jb8bvg?pwd=l0ug
提取码:l0ug
这是我编写好的,尽量和我的一致吧
随便写一个简单的GUI程序。,功能就是简单地弹出一个窗口
我们的目的是在代码空白区段添加一个shellcode,添加一个MessageBox函数
开始:
E8:Call
E9: Jmp
举个例子
- call 0x77E5425F
-
- E8 13 88 E1 76
-
- jmp 0x2345678
-
- E9 2B 2B 00 00
但是我们分析可以发现,无论是E8还是E9,后面的地址貌似都不是直接小端序转化过来的地址
真正要跳转的地址=E8这条指令的下一条指令的地址 + X X=真正要跳转的地址 - E8要要跳转的地址的下一条指令的地址
这里用具体例子分析
- #include
- using namespace std;
-
- void func()
- {
- cout << "666" << endl;
- }
-
- int main()
- {
- func();
- return 0;
- }
-
- int main()
- {
- 00DA25C0 55 push ebp
- 00DA25C1 8B EC mov ebp,esp
- 00DA25C3 81 EC C0 00 00 00 sub esp,0C0h
- 00DA25C9 53 push ebx
- 00DA25CA 56 push esi
- 00DA25CB 57 push edi
- 00DA25CC 8B FD mov edi,ebp
- 00DA25CE 33 C9 xor ecx,ecx
- 00DA25D0 B8 CC CC CC CC mov eax,0CCCCCCCCh
- 00DA25D5 F3 AB rep stos dword ptr es:[edi]
- 00DA25D7 B9 29 F0 DA 00 mov ecx,offset _8810881B_stack@cpp (0DAF029h)
- 00DA25DC E8 A3 ED FF FF call @__CheckForDebuggerJustMyCode@4 (0DA1384h)
- func();
- 00DA25E1 E8 62 ED FF FF call func (0DA1348h)
- return 0;
- 00DA25E6 33 C0 xor eax,eax
- }
- 00DA25E8 5F pop edi
- 00DA25E9 5E pop esi
- 00DA25EA 5B pop ebx
- 00DA25EB 81 C4 C0 00 00 00 add esp,0C0h
- 00DA25F1 3B EC cmp ebp,esp
- 00DA25F3 E8 97 EC FF FF call __RTC_CheckEsp (0DA128Fh)
对照着汇编可以看到call func 的机器码对应的是E8 62 ED FF FF
之前说的
真正要跳转的地址=E8这条指令的下一条指令的地址 + X
X=真正要跳转的地址 - E8要要跳转的地址的下一条指令的地址
我们来实际计算一下
真正要跳转的地址是0XDA1348
Call结束的下一个地址是0xDA25E6
因此我们用计算器算一下
刚好就是对应着FF FF ED 62,小端序转换就是62 ED FF FF ,和我们看到的对应的机器码是一样的
由于教程使用的飞鸽.exe的 SectionAlignment和FileAlignment 大小是一样的,导致很多人没有理解到添加ShellCode的精髓,所以推荐大家用文件和内存对齐不一致的程序来练手,就比如我上传的文件

我上传的这俩对齐大小就不一样,非常适合拿来练手
由于这俩对齐不一样,因此我们要多算一些东西,比如在在编写Shellcode的时候,使用Call,后面跟着计算出来的地址应该是以在内存中的地址算出的,而不是文件中的
以及后面E9 Jmp后面跟着的地址也是一样,都是按照拉伸后的ImageBuffer来算的
还有就是OEP,也就是 AddressOfEntryPoint ,这个入口点的计算是在内存的入口点地址直接减去ImageBase算出来的,也就是说这个偏移是内存的偏移而不是文件中的偏移!
比如我们在图片看到的OEP是0x1474,并不意味着程序入口点在文件中的位置是0x1474,它的真正意思是程序的入口点在 内存 中的地址是 ImageBase+ OEP,所以程序在内存的入口点是0x401474. (这一点很重要,我被带沟里了,后续也会用到)
- typedef struct _IMAGE_SECTION_HEADER {
- 0x00 BYTE Name[IMAGE_SIZEOF_SHORT_NAME];
- union {
- 0x08 DWORD PhysicalAddress;
- 0x08 DWORD VirtualSize;
- } Misc;
- 0x0c DWORD VirtualAddress;
- 0x10 DWORD SizeOfRawData;
- 0x14 DWORD PointerToRawData;
- 0x18 DWORD PointerToRelocations;
- 0x1c DWORD PointerToLinenumbers;
- 0x20 WORD NumberOfRelocations;
- 0x22 WORD NumberOfLinenumbers;
- 0x24 DWORD Characteristics;
- };
-
- /*
- - Name:段名,是一个8字节的`ASCII`字符串,不足8字节用0补齐。
- - VirtualSize:虚拟大小,标识节在**内存**中占用的大小,请勿与`PhysicalSize`(物理大小)混淆。(对其前得大小)
- 这Misc联合体 双字 是该节在没有对其前的真实尺寸,该值可以不准确,(在内存中拉伸后的实际大小)
- - VirtualAddress:虚拟地址,标识**节**在**内存中**对应段头的地址,与实际加载的位置有关。(**节**在内存的偏移地址,加上ImageBase才是在内存的真正地址)
- - SizeOfRawData:物理大小,节在**PE文件**中该段的占用大小,不足以文件对齐单位则会进行填充。(对齐后的长度)
- - PointerToRawData:物理地址,标识该段在**文件中**的偏移位置。
- - PointerToRelocations:重定向表的偏移位置。
- - PointerToLinenumbers:行号表的偏移位置。
- - NumberOfRelocations:重定向表数量。
- - NumberOfLinenumbers:行号表数量。
- - Characteristics:标识该段的各种属性信息,包括下列常用属性:
- - IMAGE_SCN_MEM_READ:可读;
- - IMAGE_SCN_MEM_WRITE:可写;
- - IMAGE_SCN_MEM_EXECUTE:可执行;
- - IMAGE_SCN_CNT_CODE:代码段;
- - IMAGE_SCN_CNT_INITIALIZED_DATA:已初始化数据段;
- - IMAGE_SCN_CNT_UNINITIALIZED_DATA:未初始化数据段;
- - IMAGE_SCN_LNK_INFO:包含附加信息。
- */
这是节表的结构。
这是文件和内存对齐不一致时的情况
由于存在对齐,因此在例如.text和.data段之间,可能会留下足够长的空隙让我们写入shellcode,其实在哪个段写都无所谓,重要的是添加的代码要正确,也就是shellcode地址要正确,OEP要正确,这些都是要经过计算的。

这是节表的信息
可以看到在文件中,.text结束的地址是Raw Size +Raw Offset=0x1400,放到WinHex里看看
还是可以看到有相当长的空闲区可以让我们写的,那么我们就从0X12E0开始添加我们的Shellcode吧。
我们要添加的代码就是
- MessageBox(0, 0, 0, 0);
- 000F1A8D 8B F4 mov esi,esp
- 000F1A8F 6A 00 push 0
- 000F1A91 6A 00 push 0
- 000F1A93 6A 00 push 0
- 000F1A95 6A 00 push 0
- 000F1A97 FF 15 F4 B0 0F 00 call dword ptr [__imp__MessageBoxW@16 (0FB0F4h)]
当然MessageBox毕竟是动态链接库里的函数,因此我们需要打开OD或者X32dbg,定位一下MessageBox的具体地址

比如我用X32dbg找到的MessageBox地址就是0x77219B00
OK,开始改

先打个模板。
首先我们要计算的是Call ,也就是计算E8后面的机器码是什么
根据前面说到的公式
X=真正要跳转的地址 - E8要要跳转的地址的下一条指令的地址
真正要跳转的地址为 0x77219B00
Call下一条指令的地址是0x12E0+0x8(4个Push 0) + 0x5(Call的长度)=0x12ED
正如我们之前所说的那样,E8 E9后面跟着的地址的计算,一切都是要以内存的地址来计算
因此在文件中的0X12ED在内存是多少呢?

首先0X12ED在.text段的偏移是 0x12ED- 0x400(.text在文件的偏移)=EED
.text段在内存的起始地址是0x1000,加上偏移就是0x1EED,再加上ImageBase就是0X401EED
所以经过计算
X=真正要跳转的地址 - E8要要跳转的地址的下一条指令的地址
即 0x77219B00 - 0X401EED = 0x76E17C13
换成小端序就是 13 7C E1 76
接下来就是跳回到原来的入口点地址0x1474
0x1474在内存的地址就是0x401474
X=真正要跳转的地址 - E9要要跳转的地址的下一条指令的地址
E9要要跳转的地址的下一条指令的地址(文件中)=0X12ED+0X5=0X12F2
0x12F2在内存中的地址就是(参考之前的算法)0x1EF2,加上ImageBase就是401EF2
因此0X401474-0x401EF2=0xFFFF F582
换成小端序就是 82 F5 FF FF
最后一件事就是修改OEP了,文件中的0x12E0在内存中对应的地址是0x401EE0,所以OEP应该改为1EE0

另存为看看

成功弹出窗口!