【问题标题】:How to find the in-memory address of a specific instruction in a DLL如何在 DLL 中找到特定指令的内存地址
【发布时间】:2013-07-27 00:01:36
【问题描述】:

如何找到特定指令的内存地址(用于漏洞利用写入)?

具体来说,我正在 Windows XP 上的user32.dll 中寻找call ebp 指令,没有我可以将EIP 指向的地址的Service Pack。我在目标上安装了Immunity DebuggerOllyDBG

【问题讨论】:

  • 嗯,DLL 是可重定位的(并且正在被重定位),因此您只能以可靠的方式找到相对于模块基地址的这样一个地址。
  • @Damon 对于所有现代操作系统都是如此,但在 Windows XP 中没有 ASLR,因此 DLL 的内存位置在操作系统版本和 SP 版本之间非常一致。
  • 我什至没有想到 ASLR,尽管那当然是另一个问题。我在想通常发生的正常 DLL 变基。所有 DLL 通常具有相同的基地址(尽管可以告诉链接器选择一个随机的基地址,但这会使地址空间更加糟糕......)并在加载时重新设置基地址。第一个 DLL 通常是 kernel32,在其原始地址加载。然后通常是 NLS,然后是所有其他 DLL(包括 user32),所有这些都被重新定位。顺便说一句,它们可能每次都在同一个地址结束,但谁知道呢。

标签: windows security dll exploit shellcode


【解决方案1】:

要查找指令,您需要确定代码、.text、节的开始和结束位置,然后加载 DLL 并进行线性搜索,直到找到指令。

这里我们有一个测试 DLL,它有两个 call ebp 指令:

// test.c
// gcc -Wall -shared test.c -o test.dll
#include <stdio.h>

__declspec(dllexport) void test(void) {
    asm("call *%ebp");
    puts("test");
    asm("call *%ebp");
}

编译并加载ollydbg中的DLL并点击CTRL+F搜索CALL EBP:

6BEC125A  |. FFD5            CALL EBP
6BEC125C  |. C70424 6430EC6> MOV DWORD PTR SS:[ESP],test.6BEC3064 ; |ASCII "test"
6BEC1263  |. E8 74060000     CALL <JMP.&msvcrt.puts>              ; \puts
6BEC1268  |. FFD5            CALL EBP

你看到第一条指令的地址在0x6bec125a,第二条在0x6bec1268call ebp的操作码是0xff 0xd5,记住这一点。

现在我们需要找到代码的边界,可以使用objdump和-h

> objdump --headers test.dll

test.dll:     file format pei-i386

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         00000984  6bec1000  6bec1000  00000600  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE, DATA

  1 .data         00000008  6bec2000  6bec2000  00001000  2**2
                  CONTENTS, ALLOC, LOAD, DATA

  2 .rdata        0000011c  6bec3000  6bec3000  00001200  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  ....

>

代码从VMA,虚拟内存地址,0x6bec1000开始,它的大小是0x984,所以它在0x6bec1000 + 0x984 = 0x6bec1984结束为:

0x6bec1000
....
what is between are the DLL instructions
....
0x6bec1984

我希望到目前为止很清楚。

如果我们想对call ebp 扫描器进行编码,我们需要进行流动:

  1. 读取PE信息,获取可执行节信息,通常为.text,以查找其相对地址及其虚拟大小。
  2. 使用LoadLibrary加载DLL,它将返回DLL的基地址。
  3. 代码段开头的虚拟地址为:DLL基地址+代码段virtualAddress,以DLL基地址+代码段virtualAddress+VirtualSize结束。
  4. 现在我们准备循环遍历代码并查找0xff 0xd5call ebp 的操作码,简单的线性搜索。

这是一个简单的实现:

// findopcode.c
// gcc -Wall findopcode.c -o findopcode

#include <windows.h>
#include <stdio.h>
#include <string.h>

int main(int argc, char **argv) {
    const char opcode[] = {0xff, 0xd5}; // The opcode of `call ebp'
    FILE *dllFile;
    HMODULE dllHandle;

    IMAGE_DOS_HEADER dosHeader;
    IMAGE_NT_HEADERS NtHeaders;
    IMAGE_SECTION_HEADER sectionHeader;

    unsigned int i;
    unsigned char *starAddr;
    unsigned char *endAddr;

    if( argc < 2 ) {
        printf("usage: %s [DLL]\n", argv[0]);
        return -1;
    }

    if( ( dllFile = fopen(argv[1], "rb") ) == NULL ) {
        perror("[!] Error");
        return -1;
    }

    // Read the basic PE headers
    fread(&dosHeader, sizeof(dosHeader), 1, dllFile);
    fseek(dllFile, dosHeader.e_lfanew, SEEK_SET);
    fread(&NtHeaders, sizeof(NtHeaders), 1, dllFile);

    // Search for the executable section, .text section.
    for( i = 0 ; i < NtHeaders.FileHeader.NumberOfSections ; i++ ) {
        fread(&sectionHeader, sizeof(sectionHeader), 1, dllFile);
        // If we found a section that contains executable code,
        // we found our code setion.
        if( (sectionHeader.Characteristics & IMAGE_SCN_CNT_CODE) != 0 ) {
            printf("[*] Code section: `%s'\n", sectionHeader.Name);
            break;
        }
    }

    fclose(dllFile);

    // Load the DLL to get it's base address
    if( (dllHandle = LoadLibraryA(argv[1])) == NULL ) {
        printf("[!] Error: loading the DLL, 0x%.8x\n", (unsigned int) GetLastError());
        return -1;
    }

    // The code start at : base address + code virtual address
    starAddr = (unsigned char *) dllHandle + sectionHeader.VirtualAddress;
    // It ends at : base address + code virtual address + virtual size
    endAddr = (unsigned char *) starAddr + sectionHeader.Misc.VirtualSize;

    printf("[*] Base address : 0x%.8x\n", (unsigned int) dllHandle);
    printf("[*] Start address: 0x%.8x\n", (unsigned int) starAddr);
    printf("[*] End address  : 0x%.8x\n", (unsigned int) endAddr);

    // Simple liner search, when ever we find `0xff 0xd5' we print that address
    for( endAddr -= sizeof(opcode) ; starAddr < endAddr ; starAddr++ ) {
        if( memcmp(&opcode, (void *) starAddr, sizeof(opcode)) == 0 ) {
            printf("[*] Found `call ebp` at: 0x%.8x\n", (unsigned int) starAddr);
        }
    }

    FreeLibrary(dllHandle);
    return 0;
}

编译它并使用该 DLL 对其进行测试:

> gcc -Wall findopcode.c -o findopcode
> findopcode.exe test.dll
[*] Code section: `.text'
[*] Base address : 0x6bec0000
[*] Start address: 0x6bec1000
[*] End address  : 0x6bec1984
[*] Found `call ebp` at: 0x6bec125a
[*] Found `call ebp` at: 0x6bec1268

>

效果不错,试试user32.dll

> findopcode.exe \Windows\System32\user32.dll
[*] Code section: `.text'
[*] Base address : 0x75680000
[*] Start address: 0x75681000
[*] End address  : 0x756e86ef
[*] Found `call ebp` at: 0x756b49b5

> 

我只在0x756b49b5 找到了一个call ebp。请注意,在使用memcmp 阅读之前,您需要检查您是否具有读取权限:使用IsBadReadPtr

        if( IsBadReadPtr(starAddr, sizeof(opcode)) == 0 &&
            memcmp(&opcode, (void *) starAddr, sizeof(opcode)) == 0 ) {

因此,如果您以某种奇怪的方式访问某个区域,该程序不会失败。

【讨论】:

  • 哇!优秀的答案。非常有帮助且内容丰富。非常感谢!
【解决方案2】:

另一种方法是使用metasploit framework 中的msfpescan

msfpescan -j ebp user32.dll

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-07-13
    • 2011-04-25
    • 1970-01-01
    • 1970-01-01
    • 2013-05-11
    • 1970-01-01
    • 2021-03-02
    相关资源
    最近更新 更多