You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

x86_64汇编中LoadLibraryA加载user32.dll失败问题求助

x64汇编加载user32.dll触发访问违例问题

我编写了一段x86_64汇编代码,流程如下:

  1. 通过访问PEB获取kernel32.dll基址
  2. 解析kernel32.dll导出表,获取GetProcAddress函数地址
  3. 调用GetProcAddress获取LoadLibraryA函数地址
  4. 尝试用LoadLibraryA加载user32.dll模块

我严格遵循Windows x64调用约定和ABI,但加载user32.dll时出现异常:用x64dbg调试时,调用LoadLibraryA后会触发gdifull32.dll的TLS Callback断点,继续执行则抛出EXCEPTION_ACCESS_VIOLATION异常。不过用同样逻辑加载ws2_32.dll完全正常。Stack Overflow上有类似问题是因栈未16字节对齐导致,但我认为这不是我的问题。

汇编代码片段

section .text
global _start
_start:
    ; ###### TABLE ######

; [rdi] = kernel32.dll
; [rdi + 0x8] = GetProcAddress
; [rdi + 0x10] = LoadLibraryA
; [rdi + 0x18] = ws2_32.dll
; [rdi + 0x20] = user32.dll
; [rdi + 0x28] = GetConsoleWindow
; [rdi + 0x30] = ShowWindow
; [rdi + 0x38] = WSAStartup
; [rdi + 0x40] = WSASocketA
; [rdi + 0x48] = connect
; [rdi + 0x50] = CreateProcessA
; [rdi + 0x58] = ExitProcess
; [rdi + 0x60] = socket fd

; ###### TABLE ######

; find the kernel32.dll base address
xor rdx, rdx
mov rax, [gs: rdx + 0x60] ; EAX = PEB
mov rax, [rax + 0x18] ; EAX = PEB->Ldr
mov rsi, [rax + 0x20] ; ESI = PEB->Ldr.InMemoryOrderModuleList
lodsq
xchg rax, rsi
lodsq
mov rbx, [rax + 0x20] ; kernel32 dllbase address, sizeof(_LIST_ENTRY) = 0x10 + 0x20 = 0x30 (0x30 offset for dllbase in _LDR_DATA_TABLE_ENTRY)


; find the export table of kernel32.dll
mov edx, dword [rbx + 0x3c] ; IMAGE_DOS_HEADER->e_lfanew (!! is type LONG !!)
add rdx, rbx ; e_lfanew + dllbase = IMAGE_NT_HEADERS
mov edx, dword [rdx + 0x88] ; export data directory (!! is type DWORD !!)
add rdx, rbx ; offset + dllbase = address export table
mov esi, dword [rdx + 0x20] ; offset addressOfNames
add rsi, rbx ; address of addressOfNames
mov rcx, 0xffffffffffffffff ; RCX = -1

; get the GetProcAddress function name
Get_FunctionName:
inc rcx ; index
lodsd ; load offset of the function name into rax
add rax, rbx ; get address of the function name
cmp dword [rax], 0x50746547 ; GetP
jnz Get_FunctionName
cmp dword [rax + 0x4], 0x41636f72 ; rocA
jnz Get_FunctionName
cmp dword [rax + 0x8], 0x65726464 ; ddre
jnz Get_FunctionName

; now rax contains the address to the string 'GetProcAddress'


; find the address of GetProcAddress
mov esi, dword [rdx + 0x24] ; RSI = Offset of addressOfNamesOrdinal
add rsi, rbx ; RSI = Address of addressOfNamesOrdinal
mov cx, [rsi + rcx * 2] ; CX = ordinal ( we multiply by 2 because addressOfnamesOrdinal is a list of WORDS )
mov esi, dword [rdx + 0x1c] ; RSI = offset of addressOfFunctions
add rsi, rbx ; RSI = address of addressOfFunctions
mov edx, dword [rsi + rcx * 4] ; RDX = offset of GetProcAddress address 
add rdx, rbx ; RDX = addres of GetProcAddress

sub rsp, 0x70 ; make space in the stack. Important to be aware of the 16 byte alignment
lea rdi, [rsp] ; RDI = Resolved addresses table
mov [rdi], rbx ; kernel32.dll
mov [rdi+0x8], rdx ; GetProcAddress

sub rsp, 0x10
mov r10, 0x7262694c64616f4c
mov qword [rsp], r10 ; LoadLibr
mov r10, 0x0000000041797261
mov qword [rsp+8], r10 ; aryA
sub rsp, 0x28 ; Reserve 32 bytes shadow space
mov rax, rdx
lea rdx, [rsp+0x28] ; LoadLibraryA string
mov rcx, [rdi] ; base address of kernel32
call rax ; Call GetProcAddress
add rsp, 0x38 ; restore stack, shadow space (32 bytes) + "LoadLibraryA"

; now rax contains the address of LoadLibraryA
mov [rdi + 0x10], rax

; Load user32.dll using LoadLibraryA
sub rsp, 0x10
mov r10, 0x642E323372657375
mov qword [rsp], r10
mov r10, 0x0000000000006C6C
mov qword [rsp+0x8], r10
sub rsp, 0x28 ; shadow space
lea rcx, [rsp+0x28] ; string user32.dll
mov rax, [rdi + 0x10] ; address of LoadLibraryA
call rax
add rsp, 0x38

; now rax contains the address of user32.dll
mov [rdi + 0x20], rax

编译命令

x86_64-w64-mingw32-gcc customshell_x64.obj -o shell_x86_64.exe -nostdlib -Wl,--entry=_start

排查建议

  • 栈对齐二次验证:Windows x64要求调用函数前RSP必须是16字节的倍数。在x64dbg中,在调用LoadLibraryA的call rax行下断点,查看RSP值是否满足RSP % 16 == 0。注意call指令会压入8字节返回地址,调用前的RSP必须16对齐才能保证函数内部栈结构合法。
  • 定位访问违例具体位置:在x64dbg中开启“异常捕获”(Debug -> Exceptions),勾选Access Violation,触发异常时查看当前指令和寄存器状态,确认是代码执行错误还是数据访问错误。如果是数据访问,检查[rdi]指向的地址表是否被栈操作破坏。
  • 验证依赖模块初始化状态:user32.dll依赖gdi32.dll等模块,TLS回调异常说明依赖模块初始化失败。可以在x64dbg中查看加载user32.dll时的模块加载顺序,检查gdifull32.dll的基址是否合法,或者其TLS回调函数是否执行异常。
  • 确认函数地址正确性:对比[rdi+0x8](GetProcAddress)和[rdi+0x10](LoadLibraryA)的值与kernel32.dll导出表中的实际地址是否一致,避免解析导出表时出现偏移计算错误。
  • 检查字符串完整性:虽然代码构造的"user32.dll"字符串带有终止符,但可以在调用LoadLibraryA前查看栈上的字符串内容,确认未被栈操作覆盖。

内容的提问来源于stack exchange,提问作者omar dans castro

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 02:27:33