Aperi’CTF 2019 - It’s a SIGn
Challenge details
Event | Challenge | Category | Points | Solves |
---|---|---|---|---|
Aperi’CTF 2019 | It’s a SIGn | Reverse | 175 | 6 |
Signals are much fun! I… Guess? Succeed at getting this crackme’s password and never ever again you’ll miss a SIGn! ;)
Fichier : crackme - md5sum: d98fcd5180bc5cdaee93f52f6d51ff96
TL;DR
Some crazy code flow achieved through signal handling. This can be solved statically by reading the asm/code or dynamically. But the best is still to use both… And a brain. A brain helps a lot! =]
Methodology
file crackme
# crackme: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=878b6ec14197294327f731a515947e197a3d70ee, stripped
First of all we can easily see that the binary is a 64-bit ELF executable , it is stripped and PIE.
We can disable ASLR for the duration of the chall, as root do :
echo 0 > /proc/sys/kernel/randomize_va_space
cat /proc/sys/kernel/randomize_va_space
# Must return 0
By using strings we can also guess that the password is hidden in a long already-initialized string.
First test, we can see that the challenge is asking for the Flag. If the flag is not correct a link to a cool video is returned!
./crackme
# Psst! What's the pass?
# -> ASDF
# input -> ASDF
#
# Anddd you failed!
# -> https://www.youtube.com/watch?v=RaiNp5JxrxU
With ltrace we can also see that some interesting functions are used such as printf, fgets, puts. In addition a couple of signal handlers are defined at the begining of the process. Maybe nanomites ?
ltrace ./crackme
# signal(SIGFPE, 0x8001406) = 0
# signal(SIGILL, 0x80014a8) = 0
# signal(SIGSEGV, 0x800154a) = 0
# signal(SIGUSR1, 0x80015ec) = 0
# signal(SIGBUS, 0x800168e) = 0
# signal(SIGCHLD, 0x8001730) = 0
# puts("Psst! What's the pass?"Psst! What's the pass?
# ) = 23
# printf(" -> ") = 6
# fflush(0x7fffff3fa600 -> ) = 0
# fgets(ASDF
# "ASDF\n", 64, 0x7fffff3f98c0) = 0x8005580
# printf("input -> %s\n", "ASDF\n"input -> ASDF
#
# ) = 15
# puts("Anddd you failed!"Anddd you failed!
# ) = 18
# puts(" -> https://www.youtube.com/watc"... -> https://www.youtube.com/watch?v=RaiNp5JxrxU
# ) = 48
# exit(0 <no return ...>
# +++ exited (status 0) +++
Let’s open it in IDA pro.
Finding the “main” function of the binary is straightforward. Indeed by finding the basic block that call the first puts we can go back to the prologue and deduce that we are probably in the “main” function.
Let’s decompile this function.
We can now see that the function is copying the INPUT from user into a 64 bits buffer in the .bss segment.
We can also see that the len of the flag needs to be 14.
If the len check fails the according fail function is called. Otherwise two other function are called.
We can investigate the first one:
Two things should be remembered here; first this function checks the first 4 characters of the password, then, there’s probably a “cmp” instruction in this basic block.
Bingo so we can leak the char via GDB ! :)
gdb-peda$ b * 0x8001377
# Breakpoint 1 at 0x8001377
gdb-peda$ r
# Starting program: ./crackme
# Psst! What's the pass?
# -> ABCDEFRTGHYUJI
Then, you can see the char in the RAX register.
# [----------------------------------registers-----------------------------------]
# RAX: 0x3d ('=')
# RBX: 0x0
# RCX: 0x80040a0 # ("U0dGNGVEQnlJR2x3YzNWdElHbG1JR1Z3YjJOb0lHeHBiblY0SU"...)
# RDX: 0x41 ('A')
# RSI: 0x8006010 ("\nnput -> ", 'A' <repeats 14 times>, "\n")
# RDI: 0x0
# RBP: 0x7ffffffee2a0 --> 0x7ffffffee2d0 --> 0x8001800 --> 0x89495741fa1e0ff3
# RSP: 0x7ffffffee290 --> 0x0
# RIP: 0x8001377 --> 0xffff91e80574c238
# R8 : 0x8005580 ('A' <repeats 14 times>)
# R9 : 0x19
# R10: 0x73 ('s')
# R11: 0x73 ('s')
# R12: 0x80010e0 --> 0x8949ed31fa1e0ff3
# R13: 0x7ffffffee3b0 --> 0x1
# R14: 0x0
# R15: 0x0
# EFLAGS: 0x293 (CARRY parity ADJUST zero SIGN trap INTERRUPT direction overflow)
# [-------------------------------------code-------------------------------------]
Knowing that this operation is looped 4 times, the flag start with “====”.
Then, the function that checks the first characters crashes and triggers an SIGFPE signal.
By navigating with IDA pro we can find the handler of the according signal.
Again a char is checked in this function.
At this point, we know that the binary will probably trigger many signal and check char by char the password. Thanks to this assumption, we’ll just disassemble all the binary and grep all the cmp instruction between al and dl.
objdump -d crackme | grep cmp
# 111e: 48 39 f8 cmp %rdi,%rax
# 1184: 80 3d cd 43 00 00 00 cmpb $0x0,0x43cd(%rip) # 5558 <stdin@@GLIBC_2.2.5+0x8>
# 118e: 48 83 3d 62 2e 00 00 cmpq $0x0,0x2e62(%rip) # 3ff8 <fork@plt+0x2f28>
# 11e8: 83 7d ec 01 cmpl $0x1,-0x14(%rbp)
# 128c: 83 7d fc 0e cmpl $0xe,-0x4(%rbp)
# 1377: 38 c2 cmp %al,%dl
# 1384: 83 7d fc 03 cmpl $0x3,-0x4(%rbp)
# 1497: 38 c2 cmp %al,%dl
# 1539: 38 c2 cmp %al,%dl
# 15db: 38 c2 cmp %al,%dl
# 167d: 38 c2 cmp %al,%dl
# 171f: 38 c2 cmp %al,%dl
# 17c1: 38 c2 cmp %al,%dl
# 1851: 48 39 dd cmp %rbx,%rbp
Great, let’s put a breakpoint and all of these cmp instructions and handle all the signals in gdb.
We can use this script:
b * 0x8001377
b * 0x8001497
b * 0x8001539
b * 0x80015db
b * 0x800167d
b * 0x800171f
b * 0x80017c1
handle all pass
r
Perfect, now every time we break we can check of the value in rax, make sure it equals edx and leak the flag at the same time:
commands
set $rdx=$rax
p/s $rax
end
The previous gdb commands allow us to leak the majority of the chars contained in flag.
The last signal to be triggered is the SIGCHLD. The latter calls the sub_1337 function that, again, checks that the four last chars are “====”
Flag
Flag : APRK{====poulet====}
Happy hacking !