x86_64汇编中LoadLibraryA加载user32.dll失败问题求助
x64汇编加载user32.dll触发访问违例问题
我编写了一段x86_64汇编代码,流程如下:
- 通过访问PEB获取kernel32.dll基址
- 解析kernel32.dll导出表,获取GetProcAddress函数地址
- 调用GetProcAddress获取LoadLibraryA函数地址
- 尝试用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
相关产品推荐
相关产品推荐

