marcof + jinblack
on May 23, 2016
17 minute read ·
Trump top tweets and money simulation machine! Do you have enough to build a wall???
The only file provided is this server script written in python listening for connections on port 9765. After a quick look we understand the script is accepting a pair of arguments used to compile a C source file and later forking in the fresh compiled binary. To download the source file the script also provides a handy read_tweet() function easily exploitable to our favour:
Pretty easy to see that asking for tweet ../pound.c would end up in leaking the real challenge source code.
Lets give a look to the core function of the python script:
This accepts two inputs with len <= 3 in order to compile pound.c (using clang) loading them in L1 and L2. Here is key to notice the script doesn’t check whether the input is numerical or not.
Now we analyzed pound.c source code and checked how we could use this consideration to our advantege. Without getting into much detail the program sets up a structure called global_s
containig information about two foreign states and allows us to transfer citizens’ gold by propagating it from the bottom to the top of the array (or viceversa) or randomly swapping it trough states (refer to source code).
L1 and L2 are used to define the citizens’ number of each state and we can force the assignment of l1_len and l2_len in something like: const int l1_len = 3+2;, const int l2_len = 1;1;, const int l1_len = 3*2;, const int l2_len = 3%2; etc, just by passing this arguments to the python server. This doesn’t seem very usefull but let’s keep looking..
The propagate_backwar(int k) and propagate_forward(int k) functions are revealing:
As we can see the propagation is done simultaneously until L1 is reached ( main() imposes s2_citizens >= s1_citizens ) and then continues for state_2 till lenght_diff is reached. Good, having something like 9;1 in L2 and 2 in L1 will force our program to believe lenght_diff = 9; and later execute 1 - 2; instruction (which is useless but still valid), making the second for loop overflow s2_citizens array. With this solid vulnerability the exploit starts developing around the possible use we could make of char *announcement;. If we could overflow global structure (saved in .bss) and reach this pointer arbitrary read/write is basically achieved trough the use of functions print_states()(for reading) andcreate_announcement ()(for writing):
Since both char s1_name[STATE_SIZE_LEN]; and char s2_name[STATE_SIZE_LEN]; varibales separates us from reaching the announcement pointer, we need to find something pretty bigger than 9 to reach it. Good enough we can use global variable const int N = 1024; to do the trick. Did our math and found two possible assignments for L1 and L2 : 258 , N;k. With this in mind we developed the idea of the two basic primitives as follows:
With this two basic primitives the attack strategy is pretty easy:
We leak at free_in_got to get free_in_libc_at_runtime
We calculate the offset from libc to obtain system_in_libc_at_runtime
We write system_in_libc_at_runtim at free_in_got
We create an announcement containing /bin/sh\x00
We call remove_announcement(), this will trigger system(/bin/sh\x00)
Ok, we got the basic idea, now we have to apply it. We still face a couple issues:
We need the libc used by the remote binary to calculate system_in_libc_at_runtime
We need the exact binary compiled by the remote host to know free_in_got, since our version of clang could compile a different one (we found out this was the case).
Leaking libc is not a big deal, we still have the read_tweet() function on our side. We can use it on something like ../../../../lib/i386-linux-gnu/libc.so.6 ( we already had it leaked from a previus pwning challenge but verified this worked aswell). To leak the binary the procedure was slightly more difficult since the file name was choosen according to:
We made a custom script to download it:
That’s it, the exploit will do the job! To get a better understanding on how it works and how something more of the pseudocode written above was needed I’m gonna explain some steps in detail, also refer to comments in the file for a deeper understanding:
Here the function takes in input the free_in_got address and before propagating it up to char *announcement adds 100 to later move this into int announcement_length. Then leaks the free_in_libc_at_runtime address, calculates system_in_lib_at_runtime using the offset obtained from the leaked libc ad writes it into free_in_got
Free pointer step:
After a little debugging we found out that just “dragging” amounts into announcement could cause some problem since values would get summed up in an unwanted way, so we defined a procedure to “drag back” the value into the “legit space” (int s2_citizens[l2_len]) and purge it away by reinitilizing everything to 0.
Similar to the first one, puts a valid bss address into announcement pointer and writes /bin/sh into it.
Calling option 4 (void create_announcement()) and passing a bigger size than 100 makes the program free the old announcement and trigger the call to system("/bin/sh").
Brainhugger (RuCTF 2019) The brainhugger challenge at RuCTF 2019 was a simple REST API written in Go, which allowed users to register and run code written in Brainfuck. Exploiting this...