The challenge is a crackme, it asks for an argument of 32 chars and gives it to
a check
function.
This check
function fork the program multiple times and redirect the stdout of
the child to /dev/null
.
do {
_Var1 = fork();
iVar3 += 1;
if (_Var1 == 0) {
iVar3 = 0;
uVar4 |= 1 << ((byte)iVar2 & 0x1f);
fd = open("/dev/null",1);
dup2(fd,1);
}
iVar2 += 1;
} while (iVar2 != 5);
For each fork, it create an index (uVar4
) and gives it to a function which
will test if the password[index] is correct.
is_correct(password[uVar4], uVar4);
One way to crack the password is to test the function is_correct
for each char
in each position.
The easiest way to do it without extracting the function is to load a library
with LD_PRELOAD
which will call the function.
The first problem we find is that the binary is a PIE, so we need to find where
it's loaded in memory. I didn't find a great way to do this so I'm using the
entries in /proc/self/maps
.
static unsigned long pie_base_address(void) {
FILE *maps = fopen("/proc/self/maps", "r");
while (1) {
unsigned long from;
int pgoff;
char *lib;
fscanf(maps, "%lx-%*lx %*4c %x %*x:%*x %*lu %ms", &from, &pgoff, &lib);
bool check = strstr(lib, "beginners_rev") && pgoff == 0;
free(lib);
if (check) {
return from;
}
}
abort();
}
Now we need to write the code to crack each character of the password.
static __attribute__((constructor)) void init(void) {
void *base_address = (void*)pie_base_address();
printf("[*] Base address is %p\n", base_address);
puts("[*] Cracking password");
bool (*is_correct)(int value, int offset) = base_address + 0x1280;
for (int offset = 0; offset < 32; offset++) {
for (int i = 0; i < 256; i++) {
if (is_correct(i, offset)) {
putchar(i);
}
}
}
putchar('\n');
exit(0);
}
Unfortunately, doing this will massively log errors about this function not able to being run inside a debugger.
Looking inside this function we can find the code responsible of telling us that.
if (in_stack_00000000 != 0x1031cf) {
fwrite("This function may not work properly with a debugger.",1,0x34,stderr);
}
This check if the return address is equals to 0x1031cf
or looking at the
assembly, if it is equals to check's address + 0x5f
.
So to lead the program into thinking its return address is what it want we will
patch the .text
section with our library. To do this we will use mprotect
to be able to write and we will replace the instruction at the return address
with a ret and instead of directly calling the function we will call this
gadget.
mprotect(base_address, 0x4000, PROT_READ | PROT_WRITE | PROT_EXEC);
*(char*)(base_address + 0x031cf) = 0xc3;
Adding that to our library and running the program don't show the warning message anymore and shows us the flag.