2017-05-26 17:08:11
阅读:613次
点赞(0)
收藏
来源: tophertimzen.com
作者:xgxgxggx
翻译: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)在堆栈上传递为
pushypushx
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 = uTyper8 = 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 globalstart2).设置堆栈
start: subrsp,28h;reservestackspaceforcalledfunctions andrsp,0fffffffffffffff0h;makesurestack16-bytealigned3).得到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_DATALdr链表位于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]找到PEBb.通过在PEB中偏移0x18进入LDR链表。
c.偏移0x20是InMemoryOrderModuleList。
d.InMemoryOrderModuleList中的第3个元素是Kernel32,0x30th offset是模块的基地址。
e.我们要调用ExitProcess,这实际上是来自ntdll.dll的RtlExitUserProcess。Ntdll.dll是InMemoryOrderModuleList中的第二个条目,我也将获取它的基地址并将其存储在r15中供以后使用。我发现这个方法比依靠Kernel32在ntdll中正确执行一个函数更容易和更可靠
从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通过这种方式编译出来的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
启动这个小程序,将16进制数据拷贝到左边文本框中,选择64位版本,点击Fire Shellcode后,汇编代码将出现在右侧文本框中。实现汇编功能主要为了让这个小程序功能更加完整,通过这个功能也可以反编译一些不知道功能的shellcode。
点击fire后,小程序将运行calc并注入一个线程执行shellcode。
成功!
后记
我希望这篇文章有助于帮助开发Win64 Shellcode,我刚开始写我在研究中学到的东西,希望以后能够继续坚持写。可以在这里下载我使用的一些程序,我将它压缩并放到这里:资源。更新2015年3月18日:我开源的我开发的shellcode Tester,将它放在github:shellcodeTester。
写于2014年12月4日
本文由 安全客 翻译,转载请注明“转自安全客”,并附上链接。
原文链接:https://www.tophertimzen.com/blog/windowsx64Shellcode/