AUCTF writeup - cracker_barrel
In AUCTF 2020 under rev
section, the challenge cracker barrel had a binary given running on remote.
Let's rev it up.
recon ↩
o cracker_barrel
aaa
i
fd 6
file cracker_barrel
size 0x43e8
humansz 17.0K
mode r-x
format elf64
iorw false
blksz 0x0
block 0x100
type DYN (Shared object file)
arch x86
baddr 0x0
binsz 15399
bintype elf
bits 64
canary true
class ELF64
compiler GCC: (Ubuntu 9.2.1-9ubuntu2) 9.2.1 20191008
crypto false
endian little
havecode true
intrp /lib64/ld-linux-x86-64.so.2
laddr 0x0
lang c
linenum true
lsyms true
machine AMD x86-64 architecture
maxopsz 16
minopsz 1
nx true
os linux
pcalign 0
pic true
relocs true
relro full
rpath NONE
sanitiz false
static false
stripped false
subsys linux
va true
It's ELF64, I can run it locally too. Let's look into the functions.
aflm
entry0:
reloc.__libc_start_main
entry.fini0:
sym..plt.got
rip
sym.remove_newline:
sym.imp.strlen
sym.check_2:
sym.imp.strlen
sym.imp.malloc
sym.imp.strcmp
sym.__libc_csu_init:
sym._init
rax
sym.check_1:
sym.imp.strcmp
main:
sym.imp.setvbuf
sym.check
sym.print_flag
sym.imp.puts
sym.check:
sym.imp.puts
sym.imp.fgets
sym.remove_newline
sym.check_1
sym.check_2
sym.check_3
sym.imp.__stack_chk_fail
sym.check_3:
sym.imp.strlen
sym.imp.malloc
sym.imp.__stack_chk_fail
sym.print_flag:
sym.imp.fopen
sym.imp.puts
sym.imp.exit
sym.imp.fgets
sym.imp.printf
sym.imp.__stack_chk_fail
so we have check
function called by main
and inside it there are 3 functions check_1
, check_2
and check_3
s main
pdg
bool main(undefined8 argc, char **argv)
{
int32_t iVar1;
undefined8 in_R8;
undefined8 in_R9;
char **var_10h;
int64_t var_4h;
sym.imp.setvbuf(_reloc.stdout, 0, 2, 0, in_R8, in_R9, argv);
iVar1 = sym.check();
if (iVar1 == 0) {
sym.imp.puts("That\'s not it!");
} else {
sym.print_flag();
}
return iVar1 == 0;
}
Decompiling main
using r2ghidra-dec!
So, check
returns a boolean and if it's true we will get the flag.
s sym.check
pdg
undefined8 sym.check(void)
{
int64_t iVar1;
int32_t iVar2;
undefined8 uVar3;
int64_t in_FS_OFFSET;
int64_t var_2018h;
int64_t var_2010h;
int64_t var_8h;
iVar1 = *(int64_t *)(in_FS_OFFSET + 0x28);
sym.imp.puts("Give me a key!");
sym.imp.fgets(&var_2010h, 0x2000, _reloc.stdin);
sym.remove_newline((char *)&var_2010h);
iVar2 = sym.check_1((char *)&var_2010h);
if (iVar2 != 0) {
sym.imp.puts("You have passed the first test! Now I need another key!");
sym.imp.fgets(&var_2010h, 0x2000, _reloc.stdin);
sym.remove_newline((char *)&var_2010h);
iVar2 = sym.check_2((char *)&var_2010h);
if (iVar2 != 0) {
sym.imp.puts("Nice work! You\'ve passes the second test, we aren\'t done yet!");
sym.imp.fgets(&var_2010h, 0x2000, _reloc.stdin);
sym.remove_newline((char *)&var_2010h);
iVar2 = sym.check_3((char *)&var_2010h);
if (iVar2 != 0) {
sym.imp.puts("Congrats you finished! Here is your flag!");
uVar3 = 1;
goto code_r0x00001450;
}
}
}
uVar3 = 0;
code_r0x00001450:
if (iVar1 != *(int64_t *)(in_FS_OFFSET + 0x28)) {
// WARNING: Subroutine does not return
sym.imp.__stack_chk_fail();
}
return uVar3;
}
There you have, your cascading if
statements. Seems we need to dig deeper.
solving check_1
↩
s sym.check_1
pdg
undefined8 sym.check_1(char *arg1)
{
int32_t iVar1;
undefined8 uVar2;
char *s1;
char *s2;
char *var_8h;
iVar1 = sym.imp.strcmp(arg1, "starwars", "starwars");
if (iVar1 == 0) {
iVar1 = sym.imp.strcmp(arg1, "startrek", "startrek");
if (iVar1 == 0) {
uVar2 = 0;
} else {
uVar2 = 1;
}
} else {
uVar2 = 0;
}
return uVar2;
}
Alright, check_1
is simple, it compares input with starwars
, if it isn't equal then
it checks compares input with startrek
. Hence, first input is either starwars
or startrek
.
Moving to check_2
solving check_2
↩
s sym.check_2
pdg
bool sym.check_2(char *arg1)
{
int32_t iVar1;
int64_t iVar2;
char *s2;
int32_t var_18h;
char *var_10h;
char *s1;
iVar1 = sym.imp.strlen(arg1);
iVar2 = sym.imp.malloc((int64_t)(iVar1 + 1) << 3);
var_18h = 0;
while (var_18h < iVar1) {
*(char *)(iVar2 + var_18h) = "si siht egassem terces"[(iVar1 + -1) - var_18h];
var_18h = var_18h + 1;
}
iVar1 = sym.imp.strcmp(iVar2, arg1, arg1);
return iVar1 == 0;
}
Okay, check_2
has a string si siht egassem terces
which when read in reverse is secret message this is
.
And the while loop is doing exactly that, reversing the string and comparing it with the second input.
Hence, our second input should be secret message this is
.
Moving on to check_3
solving check_3
↩
s sym.check_3
pdg
bool sym.check_3(char *arg1)
{
int64_t iVar1;
bool bVar2;
int64_t iVar3;
uint64_t uVar4;
int64_t in_FS_OFFSET;
int64_t var_68h;
int64_t var_54h;
int32_t var_4ch;
int64_t var_48h;
int64_t var_40h;
int64_t var_18h;
iVar1 = *(int64_t *)(in_FS_OFFSET + 0x28);
var_40h._0_4_ = 0x7a;
var_40h._4_4_ = 0x21;
iVar3 = sym.imp.strlen(arg1);
iVar3 = sym.imp.malloc(iVar3 << 2);
var_54h._0_4_ = 0;
while (uVar4 = sym.imp.strlen(arg1), (uint64_t)(int64_t)(int32_t)var_54h < uVar4) {
*(uint32_t *)(iVar3 + (int64_t)(int32_t)var_54h * 4) = (int32_t)arg1[(int32_t)var_54h] + 2U ^ 0x14;
var_54h._0_4_ = (int32_t)var_54h + 1;
}
bVar2 = false;
var_4ch = 0;
while (uVar4 = sym.imp.strlen(arg1), (uint64_t)(int64_t)var_4ch < uVar4) {
if (*(int32_t *)(iVar3 + (int64_t)var_4ch * 4) != *(int32_t *)((int64_t)&var_40h + (int64_t)var_4ch * 4)) {
bVar2 = true;
}
var_4ch = var_4ch + 1;
}
if (iVar1 != *(int64_t *)(in_FS_OFFSET + 0x28)) {
// WARNING: Subroutine does not return
sym.imp.__stack_chk_fail();
}
return !bVar2;
}
This was rather interesting, I took me a while to understand while to understand what's going on in here. There is a bitwise XOR operation to a string 4 times the given input length (the malloc has the length of string left shift by 2).
This was until I found another solution, this was unintentional I suppose, judging by path we followed on reversing other 2 functions. The creator wants us to reverse this. But let's stick to the unintented solution.
Note the variable bVar2
, initially it's set to false
. And at the return of the function, it is !bVar2
which means in order for the function return true
, bVar2
should be false
which it is initially. The intention of the second while
is to compared the input with the crated string and it turns bVar2
to true whenever the comparison is false
. This is a trivial technique, find if 2 strings are equal? Match it character by character and declare it equal only if you didn't find any mismatch till the end.
This is where I guess out unintended solution summons. The while
loop runs as long as var_4ch < strlen(arg1)
where arg1
is the input. If the loop never ran we wouldn't change out bVar2
.
How to do that? Supply an empty string :sunglasses:
The condition turns false
in the first iteration itself and the while
loop is never executed.
The result ↩
So here is the solution, nc
the challenge, then supply these inputs in order.
starwars
secret message this is
- `` (just hit enter)
You will get the flag.