Summary: Makes calls to ioctl()
in an attempt to blink Morse code on the keyboard LEDs
Key: and0u0didnt0even0need0an0arduino
I’m not sure how many people solved this challenge, but I thought it was neat, so I wrote it up. Comments and corrections are welcome.
What We Have
A 64-bit Linux binary. You can download it here.
Goal
Presumably, find a key from the binary somehow!
Getting the key
Running the binary results in no output - it’s doing something, we just weren’t sure what, exactly. Time to strace!
$ strace ./three_eyed_fish-ddeeb1eb3270b8e2c2bedabd3492bab5e83d584c
execve("./three_eyed_fish-ddeeb1eb3270b8e2c2bedabd3492bab5e83d584c", ["./three_eyed_fish-ddeeb1eb3270b8"...], [/* 18 vars */]) = 0
brk(0) = 0x603000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7ff9566f9000
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=97919, ...}) = 0
mmap(NULL, 97919, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7ff9566e1000
close(3) = 0
open("/lib64/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\320%\2\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=1732824, ...}) = 0
mmap(NULL, 3845408, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7ff95612e000
mprotect(0x7ff9562cf000, 2097152, PROT_NONE) = 0
mmap(0x7ff9564cf000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1a1000) = 0x7ff9564cf000
mmap(0x7ff9564d5000, 15648, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7ff9564d5000
close(3) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7ff9566e0000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7ff9566df000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7ff9566de000
arch_prctl(ARCH_SET_FS, 0x7ff9566df700) = 0
mprotect(0x7ff9564cf000, 16384, PROT_READ) = 0
mprotect(0x7ff9566fa000, 4096, PROT_READ) = 0
munmap(0x7ff9566e1000, 97919) = 0
ptrace(PTRACE_TRACEME, 0, 0, 0) = -1 EPERM (Operation not permitted)
--- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0} ---
+++ killed by SIGSEGV +++
Segmentation fault
From the above, it’s pretty obvious that the binary is using the ptrace(PTRACE_TRACEME)
trick to see if it is being debugged.
Typically, ptrace()
is
used by some process in order to trace, or debug, another process. Using ptrace()
with PTRACE_TRACEME
, however, does the inverse — it is called by the debugee to
indicate that it is to be debugged by its parent. In this case, the call to ptrace(PTRACE_TRACEME)
is used as a quick, easy way to detect the presence of a debugger — the call will fail
(return something other than 0) if a debugger is already attached, allowing the binary to commit
Seppuku before we can get a look at it.
Fortunately, there is a well-known and easy way around this: using LD_PRELOAD
with
a mocked-up implementation of ptrace()
, as detailed
in this blog post
and probably elsewhere.
// fakeptrace.c
#include <stdio.h>
long ptrace(int x, int y, int z)
{
puts("== ptrace called ==");
return 0;
}
And then we invoke the three-eyed-fish executable with our stubbed-out ptrace()
:
$ gcc -shared -fPIC -o fakeptrace fakeptrace.c
$ strace -E LD_PRELOAD="./fakeptrace" ./three_eyed_fish
Doing this resulted in a ton of output. Many calls to usleep()
and ioctl()
— it appeared
that the binary was attempting to (and failing) to turn the keyboard LED’s on and off! I disclosed
my findings to my teammates, and the talented Mak Kolybabi suggested
that it might be Morse code. As it turns out, he was right!
If you read through the 649 lines (yes, I counted!) of strace
output and carefully convert it to Morse
code, you will end up with the key for the challenge: and0u0didnt0even0need0an0arduino