Skip to content

Instantly share code, notes, and snippets.

@HanEmile
Last active November 15, 2021 19:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save HanEmile/79d0bb0620260d555c803fd64abc4644 to your computer and use it in GitHub Desktop.
Save HanEmile/79d0bb0620260d555c803fd64abc4644 to your computer and use it in GitHub Desktop.
Labortage CTF 2021 writeup pwn license-one (ghidra edition)

Labortage CTF 2021 writeup pwn license-one (ghidra edition)

A slightly longer writeup to the license-one challenge containing a lot of basic ghidra foo:

step 0: install ghidra:

https://ghidra-sre.org/

step 1: create a project in ghidra

When opening ghidra, the first thing popping up might be the "Tip of the Day". You can close that:

image

You should then be confronted by the "project" window:

image

In here, you can manage your ghidra project and the binaries belonging to it. For creating a project, either click on the "File" tab on the top right and select New Project... or Press [CTRL]+[N].

image

The popup above is ment to allow you to create a "shared project", allowing multiple people to reverse the same binaries and share their knowledge, but that won't be needed here, continue by pressing "Next".

image

Here, you can provide a folder to store the project in (1) and a name for the project (2). When done, press "Finish" (3).

image

You should now have a folder (1) in your ghidra project view representing your project.

step 2: add a binary to the project

For adding a file (binary, in our case the challenge pwn-license-one), first of all, select the project, then click on "File" and select "Import File...":

image

Navigate to the files you want to import, then click on "Select File To Import".

image

From here, you can edit a view things if needed (in our case, ghidra has correctly identified everything, so we don't have to change stuff). If you'r still interested what this all means:

  1. What kind of binary is imported (ELF, Raw Binay (aka: "we don't know"))
  2. What language we're daling with (x86, arm, mips, riscv, ...)
  3. where to store foo
  4. that what the name of the binary that is imported is

You can go on and press Ok (5) to proceed.

image

This last image is quite large, but don't worry, it's just a big overview on all the information ghidra has got on the binary. Just press ok.

image

Now that you've imported the binary, it shows up in the project view.

step 3: open the binary

Make sure not to miss the amazing dragon animation appearing when you double click the binary to open it. (Now's the point where you can double click the binary to open it).

A few things happen: you've got a few ghidra-related windows open now: the project manager (you can move that away), the main ghidra view (we'll get to that in a second) and a little popup telling you that the binary has not been analyzed yet:

image

After clicking "ok", ghidra presents you yet another window:

image

You could tune some settings here, but the defaults are ok for now, press "analyze".

There is a small progress bar at the bottom right you probably won't even see, as ghidra is probably done analyzing this tiny binary in a second or so.

Now, let's get into the "actual" ghidra foo: the main window...

step 4: small tour of ghidras interface

The ghidra interface is ugly. That's just a thing we have to live with for now (although the radare cutter folks have integrated the decompiler somehow).

The "main" ghidra view looks a bit like this:

image

here a small introduction into what these individual panels contain:

  1. The "Program Trees" containing information on the binary allowing some easy navigation in the binary. Wan't to view the .got? press on .got
  2. The "Symbol Tree" containing information regarding the symbols, functions, and other more abstract informations. (We'll use this later on).
  3. The "Data Type Manager" in which you can define own types, such as structs.
  4. The "main" dissassembly view, more info on that further down in a minute.
  5. The "decompiler" view, currently empty, we'll also fix that shortly.
  6. Console foo for scripting, not if interest for now
  7. The Navbar with a lot of icons of which some contain hints if you hover on them, not really interesting for now.

Now, for fixing the decompiler view, we need tell ghidra what we want to view. For doing this, you can open the "Functions" folder in the "Symbol Tree" (and adjust the size of the panel a bit):

image

As you can see, ghidra has extracted symbols from the binary and displays them here. There are a few functions that might sound familliar, a good place to start in most cases is the main function, so click on it. From here on we'll start reversing.

step 5: reversing the binary

Clicking on the "main" function in the symbols view makes tow changes in the UI:

image

The dissassembler view in the dissassembler (big panel in the middle) jumps to the address of the main function displaying it and the decompiler view (right panel) displays the decompiled view.

Now you might think: well if I've got the decompiled code, why bother with the dissassembly? Well, it's not so easy: the decompiled code isn't perfect and it can be quite practical or even essential to view the disassembly, but for now, the decompiled code should be enough.

Let's look into what ghidra has generated for us:

image

  1. The return type of the main function could not be determined, so it is currently set to "undefined"
  2. The name of the decompiled function ("main")
  3. The parameters (none)
  4. some variables
  5. some code (we'll go through this in detail)
  6. ghidra even found a condition and corectly displays it
  7. some code we don't want to reach
  8. a return value

So in the end, we've actually got some quite nice code, let's go through it and see how much we can actually understand and how we can make this nicer.

  1
  2 undefined8 main(void)
  3
  4 {
  5   size_t sVar1;
  6   ulong uVar2;
  7   char local_228 [520];
  8   int local_20;
  9   int local_1c;
 10
 11   printf("Please enter your Shell-login key: ");
 12   fflush(stdout);
 13   __isoc99_scanf(&DAT_0040202c,local_228);
 14   printf("Checking: %s\n",local_228);
 15   local_1c = 0;
 16   local_20 = 0;
 17   while( true ) {
 18     uVar2 = SEXT48(local_20);
 19     sVar1 = strlen(local_228);
 20     if (sVar1 <= uVar2) break;
 21     local_1c = local_1c + local_228[local_20];
 22     local_20 = local_20 + 1;
 23   }
 24   if (local_1c == 0x539) {
 25     puts("\nYou entered a valid key. Here you go...");
 26     system("/bin/sh");
 27   }
 28   else {
 29     puts("\nNice try...");
 30   }
 31   return 0;
 32 }
 5   size_t sVar1;
 6   ulong uVar2;
 7   char local_228 [520];
 8   int local_20;
 9   int local_1c;

These are a few variables used, we can't say to much about then and will have to kind of extract what they are for out of the context of the following code.

11   printf("Please enter your Shell-login key: ");
12   fflush(stdout);
13   __isoc99_scanf(&DAT_0040202c,local_228);
14   printf("Checking: %s\n",local_228);
15   local_1c = 0;
16   local_20 = 0;

This is some code used purely for printing some info for the user (line 11), getting input (line 13), reprinting the input from the user (line 14) and defining some variables, possibly used in the next lines of code.

As we know that the variable currently called local_228 is used to store the user input, we can select it in ghidra using a left-click and press l (lowecase L) to rename it (to for example user_input). Ghidra will automatically rename all instances of this variable. Line 7 which previously contained char local_228 [520]; now contains char user_input [520];, giving us a better understanding of what this is: a 520 byte big char array containing the user input.

We can go on: you might want to rename &DAT_0040202c to something more readable, for example user_in_fstring.

17   while( true ) {
18     uVar2 = SEXT48(local_20);
19     sVar1 = strlen(user_input);
20     if (sVar1 <= uVar2) break;
21     local_1c = local_1c + user_input[local_20];
22     local_20 = local_20 + 1;
23   }

This while loop contains a lot of stuff we don't unterstand yet. In line 19 sVar1 is set to the len of our user input (see how renaming variables can be practical? In the end, reversing can be a lot like cleaning up messy code). We can rename sVar1 to something better like len_user_in, giving us a better understanding of what is happening:

17   while( true ) {
18     uVar2 = SEXT48(local_20);
19     len_user_in = strlen(user_input);
20     if (len_user_in <= uVar2) break;
21     local_1c = local_1c + user_input[local_20];
22     local_20 = local_20 + 1;
23   }

Having done this, we can see in line 20, there is a condition in the while loop. Conditions in while loops often have to do with an iterator being incremented and thus some end-condition being necessary in order to stop the while loop from running indefinetly. In this case, the value in line 22 is incremented by 1. This is pretty obviously the loop index, so let's rename local_20 to i:

17   while( true ) {
18     uVar2 = SEXT48(local_20);
19     len_user_in = strlen(user_input);
20     if (len_user_in <= uVar2) break;
21     local_1c = local_1c + user_input[i];
22     i = i + 1;
23   }

Now we're getting closer to understanding what is happening in this loop. the value local_1c seems to be a variable holding the sum of all values stored in user_input, as in each round of the loop, we add the ith value of user_input to local_1c. Let's rename local_1c to sum:

17   while( true ) {
18     uVar2 = SEXT48(local_20);
19     len_user_in = strlen(user_input);
20     if (len_user_in <= uVar2) break;
21     sum = sum + user_input[i];
22     i = i + 1;
23   }

Now that we've renamed a few variables, let's look at the whole main function again: image

Line 24 contains the condition determining of we get a shell (line 26) or just a message (line 29).

The condition in quite simple: the sum has to be 0x539. Now you might ask yourself: how can the user input be summed up? Well, characters are represented as numbers, in this case using ASCII.

A nice thing to view is the ascii manpage ($ man ascii) if you're on linux

Calculating the needed values can be done like this:

>>> 1337 // ord('z')
10
>>> chr(1337 - (10 * ord('z')))
'u'

So we can enter zzzzzzzzzzu as a valid key to receive a shell

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment