It's a SIGn

Aperi'CTF 2019 - Reverse (175 pts).

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.

main

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.

maindecomp

If the len check fails the according fail function is called. Otherwise two other function are called.

We can investigate the first one:

1Call

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.

1cmp

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.

SIGFPE

By navigating with IDA pro we can find the handler of the according signal.

SIGFPEhandler

Again a char is checked in this function.

SIGFPECmp

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 “====”

SIGCHLD

Flag

Flag : APRK{====poulet====}

Happy hacking !

Laluka