A reverse engineering challenge I worked on for Google CTF Qualifiers 2020.
I played with cr0wn 🇬🇧, which came 16th and qualified for the next stage.
We were provided a virtual machine disk image and had to recover the root password.
Outline
- First look at disk image.
- Access the file system.
- Discover the authentication mechanism.
- Reverse engineering a kernel module.
- Discovering what
initramfs
contains and does. - Reverse engineering an AML file.
- Win.
Tl;dr
The given image has a pluggable authentication module that checks if a char device /dev/chck
reads 1 when root login is attempted.
The kernel module responsible for said device reads from ACPI, logic of which is within an ACPI Machine Language (AML) file.
Decompiling it shows that it is a PS/2 keyboard device, and the make/break codes form the flag.
First look
The archive contains a disk image and a script to run it in QEMU. GRUB menu reveals that it is Arch Linux, and you are greeted with a login prompt.
Binwalk suggests there’s an EXT file system at offset 0x100000
.
The file system
The file system can be mounted with:
mkdir -p mnt && sudo mount -o loop,offset=1048576 disk.img mnt
My first instinct was to check /etc/shadow
and the boot files, the former was a rabbit hole.
I also checked initramfs
to see if there are any files I might have missed, it contains ssdt.aml
, we’ll come back to this later.
Authentication mechanism
A feature that authenticated users in Linux was PAM.
So I checked out the modules in /etc/pam.d/system-auth
, and pam_chck.so
was mentioned.
pam_chck.so
looks for the root user, then returns 0 (success) if check_device() == 1
.
ulong pam_sm_authenticate(undefined8 param_1)
{
int r;
ulong ret;
char *user;
uint _r;
_r = pam_get_user(param_1,&user,"Username: ",&user);
if (_r == 0) {
r = strcmp(user,"root");
if (r == 0) {
fwrite("Password: ",1,10,stderr);
r = check_device();
if (r == 1) {
fprintf(stderr,"\n\nWelcome %s\n",user);
ret = 0;
}
else {
fwrite("Wrong username or password",1,0x1a,stderr);
ret = 6;
}
}
else {
ret = 6;
}
}
else {
ret = (ulong)_r;
}
return ret;
}
check_device()
is a simple function that reads 2 bytes from /dev/chck
and returns the result as an integer.
int check_device(void)
{
int ret;
char buf [2];
FILE *fd;
fd = fopen("/dev/chck","r");
fgets(buf,2,fd);
fclose(fd);
ret = atoi(buf);
return ret;
}
What exactly provides this device?
Chck kernel module
Devices in /dev/
are usually added through kernel modules, so I looked for .ko
files in the image.
Immediately, chck.ko
jumped out so it was given the decompiler treatment, indeed it handles /dev/chck
.
undefined8 chck_read(undefined8 param_1,undefined8 param_2,undefined8 param_3,undefined8 param_4)
{
int chck_r;
undefined8 r;
undefined8 extraout_RDX;
long in_GS_OFFSET;
undefined8 acpi_data;
char local_22 [2];
long canary;
__fentry__();
canary = *(long *)(in_GS_OFFSET + 0x28);
chck_r = acpi_evaluate_integer(chck_handle,"CHCK",0,&acpi_data);
if (chck_r == 0) {
snprintf(local_22,2,"%llu",acpi_data);
r = simple_read_from_buffer(param_2,extraout_RDX,param_4,local_22,2);
}
else {
printk("\x014Chck: cannot read from method CHCK");
r = 0xffffffffffffffff;
}
if (canary == *(long *)(in_GS_OFFSET + 0x28)) {
return r;
}
// WARNING: Subroutine does not return
__stack_chk_fail();
}
This function relays reads on /dev/chck
to ACPI’s CHCK device.
My guess was that this was handled early in the boot process — initramfs
.
AML file
initramfs
contains ssdt.aml
which can be decompiled with iasl
, producing ssdt.dsl.
The following helped me make sense of what is happening:
Here’s roughly what it does:
- It defines a device:
CHCK
with 2 operation regions atSystemIO
addresses0x60
and0x64
. - There’s a method, also called
CHCK
, that compares a bufferKBDB
withKBDA
, returning 1 if they match and 0 otherwise. KBDB
is populated fromSystemIO:0x60
.KBDA
contained a bunch of pre-defined bytes.
What kind of device is this? Googling acpi 0x60 0x64 yields this page.
So I made the assumption that KBDA
contained PS/2 scan codes.
That assumption was confirmed with this table. Each byte represents a make/break code, indicating a key-down/key-up event.
The decode script is trivial and is left as an exercise for the reader. Lol kidding, I did it by hand.
Result was the flag: CTF{acpi_machine_language}