Post

Narnia0

Narnia0

In this post I’m going to share my write-up for the first level of the Narnia series of challenges from OvertheWire. This post is not just a CTF write-up, but an excuse to learn about computers and security. A basic knowledge of the Linux operating system, programming (Especially in C) and the Assembly Language is assumed. Enjoy! :)

All files related to the challenges are listed in the /narnia/ directory on the server. There is one narniaX.c file for each source code, and one narniaX for each executable, for every level of number X.
First we examine some required concepts, and then we analyze the source code of the this level line by line.

Initial Inspection


file & ls

Almost always the first commands to use are find and ls -l. Let’s see what initial details the narnia0 binary gives out:

1
file narnia0
1
narnia0: setuid ELF 32-bit LSB executable, ...

From the above we learn that narnia0 is a 32-bit executable Linux binary. So far a lot of info!

1
ls -l narnia0
1
-r-sr-x--- 1 narnia1 narnia0 7456 Aug 26  2019 narnia0

ls shows that we(as a member of the group narnia0) have reading and execution access to the binary. So we can not change(write to) it! Also a setuid bit is set, meaning that when we execute narnia0, it will be executed with narnia1 user access. Important detail!

CCC (C Calling Convention)


In order to understand the assembly code of the program we need to familiarize ourselves with the concept of C calling convention. What does it mean? For the subroutines in the program to be executed, a convention is to be followed, a set of rules. It’s divided into two sets; rules which the caller should obey them, and rules which the subroutine itself (i.e calle) must follow. We look at them nice and brief:

Notes:

  • The ESP register always points to the top of the stack.
  • Stack grows down! Stack grows from up to down, meaning every new value which is entered with the push command, lies down in a lower address, so the next entering value would be beneath it. Look up here for more details.
  • Conventions are different in 32-bit and 64-bit architectures. We consider the 32-bit version in this post, since we have a 32-bit binary.

Caller’s Convention

In each convention there are two phases which are mirrors to each other. One undoing what the other did:

  • Prologue
StageWhat to do?Explanation
1Save the registers!Save the contents of registers EAX, ECX, EDX If you want to preserve them. You can restore them after the function call. Now push them on the stack!
2Pass the parameters!Push the parameters of the subroutine onto the stack, in the reversed order (stack grows down).
3Call!Call the subroutine using call instruction. This will place the return address on top of the parameters on the stack.


  • Epilogue
StageWhat to do?Explanation
1Remove the parametersRemove the parameters from the stack, restoring it to the previous state. This is done by adding the proper value (Number of bytes used by the parameters)to the ESP.
E.g: If they were three integers (4 bytes each): addl $12, %esp
2Check the EAX!The return value of the subroutine can be found in the EAX register.
3Restore the Regs!Restore the registers by popping them off the stack.

Callee’s Convention

  • Prologue
StageWhat to do?Explanation
1Push EBPPush the EBP register on the stack. This way EBP is a point of reference to other values near it like parameters and local variables. This way we also preserve the contents of EBP.
2movl %esp , %ebpMove ESP’s content into EBP, so we have the reference address to work with. Now EBP points to the value of Old EBP.
3Make space for the variables!Allocate local variables by making space on the stack. This is done by subtracting the required value from ESP register. E.g for allocating 3 integers (4 bytes each) we do this: subl $12, %esp
4Save the Registers! (again?!)Save the callee’s registers including EBX, EDI and ESI. These are the ones needed to be saved by the function.
5Return in EAXWhen the function is done, return the exit value in EAX.


  • Epilogue
StageWhat to do?Explanation
1Restore the Regs!Restore the saved registers by popping them off the stack.
2Deallocate those vars!Deallocate the space made for variables by the moving base pointer’s value into the stack pointer: movl %ebp, %esp.
Smart ha?! It works because EBP has the initial value of ESP before allocating variables’ space.
3Restore the poor EBP!Finally undo the first act, restore the base pointer by popping the old EBP off the stack.
4Ret!Return to the caller by executing the ret instruction. This instruction will find and remove the return address off the stack.

Here we can see how EBP plays the referencing role so well:

EBP Usage

Wrapping it up, we have two important references:

  • EBP: Having the address of the old ESP, points to the old EBP.
  • ESP: Points to the top of the stack.

This was a summary. if you would like to read in more details and examples, here’s the full text.

Buffer Overflow


So let’ do some real work!

Here is narnia0.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <stdio.h>
#include <stdlib.h>

int main(){
	long val=0x41414141;
	char buf[20];

	printf("Correct val's value from 0x41414141 -> 0xdeadbeef!\n");
	printf("Here is your chance: ");
	scanf("%24s",&buf);

	printf("buf: %s\n",buf);
	printf("val: 0x%08x\n",val);

	if(val==0xdeadbeef){
		setreuid(geteuid(),geteuid());
		system("/bin/sh");
	}
	
	else{
		printf("WAY OFF!!!!\n");
		exit(1);
	}

	    return 0;
}

What does it do? It wants us to change the value of val from 0x41414141 to 0xdeadbeef. If we succeed, the program will give us a shell with the narnia1 user access. Sounds good!
Keep in mind that deadbeef is actually a hex value.

Let’s look up the lines each:

Line by Line

  1. First it defines two variables: val which has a hex value, and buf which is an array of characters with a length of 20.
  2. Prints out messages. Asks us nicely to change the value of val.
  3. Reads input. The %s character means it’s reading a string, and 24 indicates the input’s length. It’ll put the string in the variable buf. You can see the full instructions for scanf here.
  4. Prints out messages. Shows the contents of val and buf.
  5. Checks if we did the change. if so, delivers a shell with narnia1 user access.
  6. If not, shows a message and exits.
  7. The End!

Exploiting

  • The program asks us to enter a string of 24 bytes length.
  • As we saw in calling convention, when the main function executes, it allocates some space in the stack for the local variables, here val and buf.
  • The point is, buf lies bellow var in the stack, meaning the address of the buf variable is lower than val. It has been specified that buf is a 20 bytes size string, but scanf is putting 24 bytes in it, so the remaining 4 bytes will be written into val.
  • All we have to do is to choose the first 20 bytes as whatever as we want and the next four bytes: 0xdeadbeef.

This vulnerability, which lets us write to some variable exploiting an overflow of a buffer, is called: Buffer Overflow (da!).

UID

Each user in Unix is associated with a unique number called User ID.
There are three important types of UID defined for a process. Let’s have a look at them:

  • Real User ID: For a process it’s simply the UID of the user who has started it.

  • Effective User ID: Normally it’s same as Real UID, but can be changed to enable a non-privileged user to access what can be accessed by the privileged user.
    Take a look at passwd:
    -rwsr-xr-x 1 root root 68208 May 28 2020 /usr/bin/passwd
    The setuid flag is set. So when a normal user runs the binary, the EUID becomes 0 (root’s UID) and RUID remains the same.

  • Saved User ID: When a process is running with a privileged access, meaning EUID is changed, and it want’s to do some under-privileged job, the value of EUID is saved to saved user id (suid) and sets the EUID to the non-privileged UID.

Consider again this line in the code:

1
setreuid(geteuid(),geteuid());

It’ll simply set the values of EUID and RUID of the calling process to it’s Effective User ID, so the process will have the narnia1 user access.

Endianness

  • Endianness is the order to store a sequence of bytes in the computer memory. It is also used in networks for transmitting bytes over a channel.

  • There are two types: big-endian and little-edian. In a big-endian system the most significant byte is stored in the smallest memory address and the least significant byte a the largest. Little-endian is reverse.

  • Big-endianness is mostly used in network protocols and little-endianness in processor architectures (x86, Arm and …) and their associated memory.

So how does little-endian work and benefit us?
In order to store 0xdeadbeef in a memory location, we have to put it in this form: 0xefbeadde.
Why? Because this way 0xde will go into the first byte, 0xad the second, 0xbe third and 0xef in the last byte. This happens because our processor stores the least significant byte in the least significant address, in the memory.

In conclusion our desired string would be looking like this:

1
12345678901234567890efbeadde

The first 20 bytes filled with whatever we want, and the 4 remaining ones with 0xdeadbeef in the little-endian compatible format.

Subshell

This is the magical command for getting the shell:

1
(printf "12345678901234567890\xef\xbe\xad\xde"; cat - ) | ./narnia0
  • By putting our command in parentheses we’re executing it in a subshell. This way we have a parallel shell beside the one program is running in.

  • We use printf to wrap our string up in the proper format. We don’t want to give deadbeef as a string, but as a hex value, a binary.
  • After that we run cat with a dash mark. So when the shell (system(“/bin/sh”)) opens it will constantly get input and deliver to the new born shell.

Congrats! You got the shell, now you just need to use cat for viewing the narnia1 user password from /etc/narnia_pass/ directory.

This post is licensed under CC BY 4.0 by the author.