Mar 26, 2019 • jedevc

Preloader

Challenge type

reversing

Description

get the flag using this one weird linker trick!

ssh george@challenge.whatthectf.afnom.net -p 4011

password: themanintheyellowhat

Challenge solve

We can ssh into the machine, and have a quick look round.

$ ls -l
total 24
---x--x--x 1 fifi fifi 16576 Mar 20 09:57 preloader
-rw-rw-r-- 1 fifi fifi   244 Mar 20 09:57 preloader.c
$ file preloader
preloader: executable, regular file, no read permission

That’s interesting - a binary that can be executed but can’t be read, so we can’t easily extract information from it.

Let’s look at the (censored) source code then:

#include <stdio.h>

int main() {
	// you have to go to the binary to get your flag!
	volatile char *flag = "<censored>";

	puts("Hello!");
	printf("Here's your flag:\n");
	// printf("%s\n", flag);
	printf("Hope you got that.\n");

	return 0;
}

So, the flag should have been printed out - but it actually won’t be, because it’s commented out! So we somehow need to trick the program into printing it out.

When we run the program, we can confirm:

$ ./preloader
Hello!
Here's your flag:
Hope you got that.

Looking at the challenge description, and a bit of clever googling, we can eventually find out about the LD_PRELOAD trick.

This trick will let us hook into a function call and dump the contents of the stack. We can choose any function that is called by main but, the compiler may have optimized the calls to printf to calls to puts as they contain no format strings. So it makes most sense to try hooking into puts.

#include <stdio.h>

int puts(const char *s) {
	printf("%s-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s-");
	printf("hooked-%s\n", s);

	return 0;
}

We redefine puts to first dump the contents of the strings on the stack, and then to print the actual data with the “hooked” prefix. Since we know the flag is stored somewhere on the stack, this should dump it.

$ gcc solution.c -shared -o solution.so
$ LD_PRELOAD=solution.so ~/preloader
���--���-��AWI��AVI��AUA��ATL�%L,---(null)-Hello!-�1���U-H�=�--AFNOM{Seek_And_Ye_Shall_Find}-hooked-Hello!
���--���-��AWI��AVI��AUA��ATL�%L,---(null)-Hello!-�1���U-H�=�--AFNOM{Seek_And_Ye_Shall_Find}-hooked-Hello!
Segmentation fault (core dumped)

And there’s our flag, just sitting there!

Aside

Using this method of hooking into a function, we could read from any location in memory and dump the assembly code. This means that it is impossible for a binary to be only executable without being in some way readable.

Another technique for solving this challenge was using ptrace to read from arbitrary memory addresses.