【逆向】在程序空白区添加Shellcode

目录

 硬编码

内存对齐和文件对齐

节表

实战


滴水逆向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_第1张图片

我们的目的是在代码空白区段添加一个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
因此我们用计算器算一下
【逆向】在程序空白区添加Shellcode_第2张图片

刚好就是对应着FF FF ED 62,小端序转换就是62 ED FF FF ,和我们看到的对应的机器码是一样的

内存对齐和文件对齐

由于教程使用的飞鸽.exe的 SectionAlignment和FileAlignment 大小是一样的,导致很多人没有理解到添加ShellCode的精髓,所以推荐大家用文件和内存对齐不一致的程序来练手,就比如我上传的文件

【逆向】在程序空白区添加Shellcode_第3张图片

 我上传的这俩对齐大小就不一样,非常适合拿来练手

由于这俩对齐不一样,因此我们要多算一些东西,比如在在编写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:包含附加信息。
*/

 这是节表的结构。
【逆向】在程序空白区添加Shellcode_第4张图片

这是文件和内存对齐不一致时的情况

由于存在对齐,因此在例如.text和.data段之间,可能会留下足够长的空隙让我们写入shellcode,其实在哪个段写都无所谓,重要的是添加的代码要正确,也就是shellcode地址要正确,OEP要正确,这些都是要经过计算的。

实战

【逆向】在程序空白区添加Shellcode_第5张图片

 这是节表的信息

可以看到在文件中,.text结束的地址是Raw Size +Raw Offset=0x1400,放到WinHex里看看
【逆向】在程序空白区添加Shellcode_第6张图片

还是可以看到有相当长的空闲区可以让我们写的,那么我们就从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的具体地址

【逆向】在程序空白区添加Shellcode_第7张图片

比如我用X32dbg找到的MessageBox地址就是0x77219B00

OK,开始改

【逆向】在程序空白区添加Shellcode_第8张图片

 先打个模板。
首先我们要计算的是Call ,也就是计算E8后面的机器码是什么
根据前面说到的公式
X=真正要跳转的地址 - E8要要跳转的地址的下一条指令的地址

真正要跳转的地址为   0x77219B00
Call下一条指令的地址是0x12E0+0x8(4个Push 0) + 0x5(Call的长度)=0x12ED

正如我们之前所说的那样,E8 E9后面跟着的地址的计算,一切都是要以内存的地址来计算
因此在文件中的0X12ED在内存是多少呢?

【逆向】在程序空白区添加Shellcode_第9张图片

首先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

【逆向】在程序空白区添加Shellcode_第10张图片

【逆向】在程序空白区添加Shellcode_第11张图片 另存为看看

【逆向】在程序空白区添加Shellcode_第12张图片

成功弹出窗口! 

你可能感兴趣的:(滴水逆向,单片机,嵌入式硬件)