TECHOMFEST Quals
Team: ぎゅっと。
Rank: 4 / 54
kenari
Binary Exploitation
116 pts
21
ezfs
Binary Exploitation
504 pts
5
ezpu
Binary Exploitation
1000 pts
1
kenari
Description
Author: bakwanmalank
mirip warmup kemarin cuma nambah burung kenari
(maaf ada sedikit kesalahan teknis😔)
connect: nc ctf.ukmpcc.org 11001
Analysis
we're given a single elf binary named kenari
with protection as follows
the program itself is very small, only consisting of the following functions
Exploitation
because the GOT is writeable, its possible to just overwrite the GOT with the win function with a single format string payload
here's the exploit being ran againts the remote server
here's the full exploit script:
Flag: TCF{m4nuk_kenari_cit_cit_cuiiiiittt_!!}
ezfs
Description
Author: rui
this might be the dumbest pwn that i've ever made
connect: nc ctf.ukmpcc.org 10002
Analysis
we're given the following files as attachments
here's the content of the patch file
we can get the summary of the patched function from this documentation
and more detail of the implementation from the source code
in summary, the patch removes the security checks upon creating new files, nodes, block devices etc
another relevant information is that the flag is not stored within the filesystem as can be seen from the qemu launch script
to further understand about how the flag is loaded/mounted (not sure what the correct word is), we can read the documentation:
-drive option[,option[,option[,...]]]
Define a new drive. This includes creating a block driver node (the backend) as well as a guest device, and is mostly a shortcut for defining the corresponding
-blockdev
and-device
options.
-drive
accepts all options that are accepted by-blockdev
. In addition, it knows the following options:[... SNIP]
format=format
Specify which disk format will be used rather than detecting the format. Can be used to specify format=raw to avoid interpreting an untrusted format header.
this means the flag is loaded as a simple, byte-for-byte representation of the disk's data. Since flag.txt
is specified with format=raw
, QEMU treats the file as a raw block device, allowing the guest kernel to access it as if it were a physical drive.
Exploitation
I'm not an expert in kernel shenanigans (hopefully yet), so I'll hunt for the low hanging fruit by asking chatGPT
and surprisingly it works, here's the exploit being ran againts the remote server
here's the full exploit script:
Flag: TCF{what_kind_of_pwn_is_this???}
ezpu
Description
Author: rui
new cpu instruction??? nah man its just backdoor
connect: nc ctf.ukmpcc.org 10001
Analysis
we're given the following files as attachment:
here's the content of the patch file
the well_its_a_backdoor
function introduces a custom behavior in QEMU that manipulates the emulated CPU's state based on specific conditions. the patched behavior will trigger when any guest code executes the 0x3f
opcode in 64-bit mode (CODE64
)
depends on the value of R11
when the instruction is executed, different branching is taken:
R11 == 0x6969696969696969
executes a memory read operation:
Source:
R_R13
Destination:
R_R12
Access Mode:
MO_LEUQ
indicates a 64-bit load in little-endian format
R11 == 0xdeadbeeffeebdaed
executes a memory write operation:
Source:
R_R12
Destination:
R_R13
Access Mode:
MO_LEUQ
indicates a 64-bit store in little-endian format
this essentially means we have whole memory access to write and read, a powerful primitive.
next this is the launch script
Exploitation
Defeating KASLR
though we have full access to the kernel address, it would be deemed useless if we don't know any addresses due to the fact that KASLR is enabled.
however KASLR doesn't apply everywhere, quote:
KASLR is nice, but doesn’t apply everywhere. You can manipulate either the IDT (before the
cpu_entry_area
at0xfffffe0000000000
) or LDT (after [modify_ldt
]() at0xffff880000000000
), which are mapped read-only at fixed addresses.
cpu_entry_area
specifically will contain addresses to kernel's .text
Privilege Escalation
I initially tried to overwrite modprobe but it fails, as the author would confirm that modprobe is indeed disabled
so we're left with data only privilege escalation. one technique is to overwrite the current
of type task_struct
's creds to be init_cred
(root).
this technique would be significantly harder/unreliable to do if the RANDOMIZE_LAYOUT
config is enabled, which seems not to be the case here.
essentially, the kernel stores a global variable init_task
of type task_struct
which is the starting task that initializes the booting process.
within it, exist the init_cred
of the initiating task (which have root privileges) and the next
field which forms a linked list upon other creds that will be spawned during the system's lifetime.
we can obtain the those global variable addresses by reading /proc/kallsyms
there's also the comm
field, which the name of the running process that the task's own. we can set this value by calling the prctl
syscall as follows:
as we have arbitrary read, we will use this as our egg to hunt. as we traverse the list to find our current
task, we will compare the value of the comm
field of each traversed task to determine if the task belongs to the current process.
next, the tricky part is to find the offset to next
as it will be different across systems (depending on the configurations and versions).
I note two ways of doing this
GDB observation and assumptions
this method will be less reliable compared to the latter but worth to note nonetheless. the idea came from kylebot (amazing pwner) that they shared in pwn.college's discord
first, we'll leak the init_task
and use the tele
command within gdb to inspect its structure
as can be seen, we observed two fields at offset 0x478 and 0x480 that have endless lists of pointers, one of these is the next
pointer as hinted by kyle as
(they should be so long that gef does not know they are loops)
at this time, we could also examine the result of the telescope and look for the string swapper
. this will be our offset to comm
which is 0x730 in this case
init_task
is named swapper by default
for_each_process
macro
this method in my opinion is more reliable and would be my go to for future occasions. the idea came from the writeup below
if we want to search for the offset of the
tasks
field inside thetask_struct
(which is the pointer to the next task), we can search for references toinit_task.tasks
and end up looking at the macrofor_each_process
which is called byclear_tasks_mm_cpumask
. The latter calls the first, which references the needed field
I suggest you to read it as it explained it better than I could do, but in essence this what would you need to do
first disassmble clear_tasks_mm_cpumask
look for the instruction which compares rax to some kernel address [1]
, the compare is actually made between the init_task
and its next task. in other words the address of the next of init_task
.
this way we can get the offset by subtracting &init_task
to the compared value
which is the same offset we got from the previous method.
Creds offset
examining the task_struct
structure definition, it can be seen that the two cred that we need to overwrite (*cred
and *real_cred
) is very close to comm
.
so we'll just have to look for adjacent pointers somewhere close to it, and telescope
it to verify that it contains 0x3e8 (UID 1000) for user ctf
. in this case, the offset to both cred are 0x718 and 0x720 respectively
finally, last step is to overwrite both of it with init_cred
to elevate our privilege to root and spawn a shell.
here's the exploit being ran againts the remote server
here's the full exploit script:
Flag: TCF{siapa_sangka_cuma_modal.byte_0x3f_bisa_dapet_AAR_AAW}
Last updated