在x86_64中不用call指令修改返回地址调用函数

修改返回地址调用函数hello

  1. 准备环境vs2022。

  2. start.asm

    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
    ;assembly.asm
    extrn hello : PROC

    .code

    funnormal PROC; 正常调用函数

    ; shadow space 32 bytes
    sub rsp, 40 ; 保持16字节对齐 + 预留 shadow space

    mov ecx, 123 ; RCX = 第一个参数(Windows x64 ABI)
    call hello ; 正常调用函数

    add rsp, 40
    xor eax, eax ; return 0
    ret

    funnormal ENDP


    vulnerable PROC;修改返回地址调用函数
    push rbp
    mov rbp, rsp

    ; return address stored at [rbp+8]
    ; we replace it with address of hello

    mov rax, QWORD PTR [rbp+8];
    mov rcx, 321 ; 设置第一个参数
    lea rdx, hello
    mov QWORD PTR [rbp+8], rdx

    pop rbp
    ret
    vulnerable ENDP

    main proc

    sub rsp, 40 ; 保持16字节对齐 + 预留 shadow space
    call vulnerable ; 正常调用函数
    add rsp, 40

    ret
    main endp
    end
  3. hello.asm

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    ;assembly.asm
    includelib ucrt.lib
    includelib legacy_stdio_definitions.lib
    extern printf:proc

    .data
    Format db '%d',0

    .code
    hello PROC
    ; RCX = int x (hello 的第一个参数)
    ; printf 可以用 C 版本,不必写 asm
    sub rsp,28h
    mov rdx,rcx
    lea rcx,Format
    call printf
    add rsp,28h
    ret
    hello ENDP
    END

修改返回地址调用函数CreateRemoteThread注入代码

  • MessageBoxA在重启前的每个64位程序中的地址相同,可以在本地址得到弹窗函数地址后覆盖shellcode中间预留的8个字节的占位符(比如全 0xAA)。
  1. 准备环境vs2022

  2. main.c

    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
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    #include <windows.h>
    #include <stdio.h>

    // 声明汇编函数
    // 注意:我们需要传入所有的 7 个参数
    extern HANDLE vulnerable_CRT(
    HANDLE hProcess, // RCX
    LPSECURITY_ATTRIBUTES lpAttr, // RDX
    SIZE_T dwStackSize, // R8
    LPTHREAD_START_ROUTINE lpAddr,// R9
    LPVOID lpParam, // [Stack]
    DWORD dwFlags, // [Stack]
    LPDWORD lpThreadId // [Stack]
    );
    void InjectCode(DWORD dwProcId, LPVOID mFunc)
    {
    HANDLE hProcess, hThread;
    LPVOID mFuncAddr;
    DWORD NumberOfByte;

    hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcId);
    mFuncAddr = VirtualAllocEx(hProcess, NULL, 1024, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    WriteProcessMemory(hProcess, mFuncAddr, mFunc, 1024, &NumberOfByte);

    // --- 唯独这里改为汇编跳转方式 ---
    hThread = vulnerable_CRT(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)mFuncAddr, NULL, 0, NULL);

    WaitForSingleObject(hThread, INFINITE);
    VirtualFreeEx(hProcess, mFuncAddr, 1024, MEM_RELEASE);
    CloseHandle(hThread);
    CloseHandle(hProcess);
    }
    void InjectCodec(DWORD dwProcId, LPVOID mFunc)
    {
    HANDLE hProcess, hThread;
    LPVOID mFuncAddr, ParamAddr;
    SIZE_T NumberOfByte;

    hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcId);
    mFuncAddr = VirtualAllocEx(hProcess, NULL, 1024, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    WriteProcessMemory(hProcess, mFuncAddr, mFunc, 1024, &NumberOfByte);
    hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)mFuncAddr, NULL, 0, &NumberOfByte);
    WaitForSingleObject(hThread, INFINITE);
    VirtualFreeEx(hProcess, mFuncAddr, 1024, MEM_RELEASE);
    CloseHandle(hThread);
    CloseHandle(hProcess);
    }
    // msg: "Hello", title: "Hi"
    // 经过严格计算偏移的 Shellcode
    unsigned char shellcode[] = {
    0x55, // push rbp
    0x48, 0x89, 0xE5, // mov rbp, rsp
    0x48, 0x83, 0xEC, 0x30, // sub rsp, 30h (足够大的影子空间)
    0x48, 0x83, 0xE4, 0xF0, // and rsp, -16 (对齐)

    0x48, 0x31, 0xC9, // xor rcx, rcx
    0x48, 0x8D, 0x15, 0x18, 0x00, 0x00, 0x00, // lea rdx, [rip+24] -> "Hello"
    0x4C, 0x8D, 0x05, 0x18, 0x00, 0x00, 0x00, // lea r8, [rip+24] -> "Hi"
    0x45, 0x31, 0xC9, // xor r9d, r9d

    0x48, 0xB8, // mov rax, 0xAAAAAAAAAAAAAAAA
    0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,

    0xFF, 0xD0, // call rax

    0x48, 0x89, 0xEC, // mov rsp, rbp (恢复原始栈指针)
    0x5D, // pop rbp
    0xC3, // ret

    'H', 'e', 'l', 'l', 'o', 0x00,
    'H', 'i', 0x00
    };
    void PrepareShellcode(unsigned char* sc, size_t scSize)
    {
    // 1. 获取当前系统真实的 MessageBoxA 地址
    // 注意:在所有 64 位进程中,这个地址在本次开机期间是固定的
    void* realAddr = (void*)GetProcAddress(LoadLibraryA("user32.dll"), "MessageBoxA");

    if (realAddr == NULL) {
    printf("Error: Could not find MessageBoxA. Is user32.dll loaded?\n");
    return;
    }

    printf("[+] Found MessageBoxA at: %p\n", realAddr);

    // 2. 在 shellcode 数组中寻找占位符 0xAAAAAAAAAAAAAAAA 并替换
    // 我们步进查找,确保不会越界
    BOOL found = FALSE;
    for (size_t i = 0; i < scSize - 8; i++)
    {
    // 检查连续的 8 个字节是否为我们的占位符
    if (*(unsigned __int64*)(&sc[i]) == 0xAAAAAAAAAAAAAAAA)
    {
    *(unsigned __int64*)(&sc[i]) = (unsigned __int64)realAddr;
    found = TRUE;
    printf("[+] Placeholder replaced at offset: %zu\n", i);
    break;
    }
    }

    if (!found) {
    printf("[-] Warning: Placeholder 0xAAAAAAAAAAAAAAAA not found in shellcode!\n");
    }
    }


    int main()
    {
    PrepareShellcode(shellcode, sizeof(shellcode)); // 别忘了先替换地址
    InjectCode(58344, shellcode);
    return 0;
    }
  3. vulnerable.asm

    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
    includelib ucrt.lib
    includelib legacy_stdio_definitions.lib
    extern CreateRemoteThread : proc

    .code
    vulnerable_CRT PROC
    ; 进入时:
    ; RCX, RDX, R8, R9 已经由 C 语言填好了前 4 个参数
    ; 此时栈顶 [rsp] 是返回到 InjectCode 的地址

    push rbp
    mov rbp, rsp

    ; --- 关键:手动处理第 5, 6, 7 个参数 ---
    ; C 语言调用本函数时,已经把 5, 6, 7 参数放到了它的影子空间上方
    ; 我们需要把它们挪到“发给 CreateRemoteThread 的影子空间”上方

    ; 1. 获取 C 语言传入的后三个参数(位于本函数的栈帧之上)
    mov rax, qword ptr [rbp + 30h] ; 参数 5 (lpParam)
    mov r10, qword ptr [rbp + 38h] ; 参数 6 (dwFlags)
    mov r11, qword ptr [rbp + 40h] ; 参数 7 (lpThreadId)

    ; 2. 将它们布置在返回地址 [rbp+8] 之后的位置
    ; 栈布局应该是:[返回地址] [影子空间32字节] [参数5] [参数6] [参数7]
    mov qword ptr [rbp + 28h], rax ; 第 5 参数放到 [rbp+40] 左右
    mov qword ptr [rbp + 30h], r10 ; 第 6 参数
    mov qword ptr [rbp + 38h], r11 ; 第 7 参数

    ; --- 劫持返回地址 ---
    lea rax, CreateRemoteThread
    mov qword ptr [rbp + 8], rax

    ; 恢复栈帧并跳转
    pop rbp
    ret
    vulnerable_CRT ENDP
    END