Being a cheapass student, I couldn't resist to try to win a free entrance to Area41 Security Conference (don't mix with Alcoholics Anonymous Area 41 in Nebraska) after spotting @gandro23 retweet about the conference and the challenge.
The challenge starts after downloading and extracting a binary. A quick look
into it with file
gives us a hint that the binary is an ELF file compiled for
x86_64 Linux machine with stripped symbols.
$ file challenge_area41
challenge_area41: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, stripped
After running it we get a nice banner and a prompt asking for $$$ to pass the toll:
$ ./challenge_area41
=================================================================================
/$$$$$$ /$$ /$$ /$$
/$$__ $$ | $$ | $$ /$$$$
| $$ \ $$ /$$$$$$ /$$$$$$ /$$$$$$ | $$ | $$|_ $$
| $$$$$$$$ /$$__ $$ /$$__ $$ |____ $$ | $$$$$$$$ | $$
| $$__ $$| $$ \__/| $$$$$$$$ /$$$$$$$ |_____ $$ | $$
| $$ | $$| $$ | $$_____/ /$$__ $$ | $$ | $$
| $$ | $$| $$ | $$$$$$$| $$$$$$$ | $$ /$$$$$$
|__/ |__/|__/ \_______/ \_______/ |__/|______/
/$$$$$$ /$$ /$$ /$$
/$$__ $$| $$ | $$| $$
| $$ \__/| $$$$$$$ /$$$$$$ | $$| $$ /$$$$$$ /$$$$$$$ /$$$$$$ /$$$$$$
| $$ | $$__ $$ |____ $$| $$| $$ /$$__ $$| $$__ $$ /$$__ $$ /$$__ $$
| $$ | $$ \ $$ /$$$$$$$| $$| $$| $$$$$$$$| $$ \ $$| $$ \ $$| $$$$$$$$
| $$ $$| $$ | $$ /$$__ $$| $$| $$| $$_____/| $$ | $$| $$ | $$| $$_____/
| $$$$$$/| $$ | $$| $$$$$$$| $$| $$| $$$$$$$| $$ | $$| $$$$$$$| $$$$$$$
\______/ |__/ |__/ \_______/|__/|__/ \_______/|__/ |__/ \____ $$ \_______/
/$$ \ $$
| $$$$$$/
\______/
=================================================================================
Welcome to the Area41 challenge! If you attended HashDays, it will be easy for you...
-> The next part of the challenge is tolled. No change given!
-> Only CHF accepted. How much are you going to pay?
-> 42
BOOOOO
Hehe, no easy money with a random amount. Let's try to disassemble it:
$ objdump -d challenge_area41
challenge_area41: file format elf64-x86-64
.text
section seems to not exist. Listing out all printable characters
of the binary reveals a few interesting ones:
$ strings challenge_area41
WTF!
...
$Info: This file is packed with the WTF executable packer #wtf $
$Id: WTF 3.91 The WTF Team. All WTFs Reserved. $
...
The binary is packed, but what this WTF
packer is ? Googling doesn't return
any link to WTF
packer or any helpful info. Looking more carefully into
strings
output we find UPX!u
which gives the hint that the binary was
packed with upx, but the "UPX" occurrence in the binary was replaced by
"WTF". Therefore, decompressing with upx
fails hard:
$ upx -d challenge_area41
Ultimate Packer for eXecutables
Copyright (C) 1996 - 2013
UPX 3.91 Markus Oberhumer, Laszlo Molnar & John Reiser Sep 30th 2013
File size Ratio Format Name
-------------------- ------ ----------- -----------
upx: challenge_area41: NotPackedException: not packed by UPX
Unpacked 0 files.
The problem might be that upx
can't find UPX_MAGIC_LE32
which is replaced
by "WTF!" and raises the exception. Let's bring the "UPX!" back and try to
decompress:
$ sed -i 's/WTF\!/UPX\!/g' challenge_area41
$ upx -d challenge_area41
Ultimate Packer for eXecutables
Copyright (C) 1996 - 2013
UPX 3.91 Markus Oberhumer, Laszlo Molnar & John Reiser Sep 30th 2013
File size Ratio Format Name
-------------------- ------ ----------- -----------
39558 <- 14960 37.82% linux/ElfAMD challenge_area41
Unpacked 1 file.
Worked. Now we can disassemble the binary:
$ objdump -s challenge_area41 > challenge_area41.asm
The function main contains a check for the amount we provide:
406f57: e8 64 96 ff ff callq 4005c0 <getchar@plt>
406f5c: 88 45 fb mov %al,-0x5(%rbp)
406f5f: 0f be 45 fb movsbl -0x5(%rbp),%eax
406f63: 83 e8 30 sub $0x30,%eax
406f66: 89 45 fc mov %eax,-0x4(%rbp)
406f69: 83 7d fc 05 cmpl $0x5,-0x4(%rbp)
406f6d: 75 07 jne 406f76 <main+0x76>
406f6f: e8 db fe ff ff callq 406e4f <good_toll>
406f74: eb 05 jmp 406f7b <main+0x7b>
406f76: e8 f2 99 ff ff callq 40096d <wrong_toll>
The function int getchar(void)
returns a value in eax
register, but only
8 bits are taken from the value and written back to eax
and
sign is extended if needed. Then it is subtracted by 0x30 which
converts a char to integer (0x30 = '0' in ASCII) and finally the converted
value is compared to 0x05. So, it expects us to give 5 CHF for the pass to
enter good_toll
function which mostly interests us.
After paying 5 CHF nothing useful happens:
Welcome to the Area41 challenge! If you attended HashDays, it will be easy for you...
-> The next part of the challenge is tolled. No change given!
-> Only CHF accepted. How much are you going to pay?
-> 5
Nevermind, I changed my mind
Disassembling good_toll
explains a lot:
0000000000406e4f <good_toll>:
406e4f: 55 push %rbp
406e50: 48 89 e5 mov %rsp,%rbp
406e53: 48 83 ec 10 sub $0x10,%rsp
406e57: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%rbp)
406e5e: 83 7d fc 00 cmpl $0x0,-0x4(%rbp)
406e62: 74 16 je 406e7a <good_toll+0x2b>
406e64: b8 00 00 00 00 mov $0x0,%eax
406e69: e8 c1 a1 ff ff callq 40102f <init_data_good>
406e6e: b8 00 00 00 00 mov $0x0,%eax
406e73: e8 7d 99 ff ff callq 4007f5 <show_data_matrix>
406e78: eb 1e jmp 406e98 <good_toll+0x49>
406e7a: 48 8b 05 ff 13 20 00 mov 0x2013ff(%rip),%rax # 608280 <stdout@@GLIBC_2.2.5>
406e81: 48 89 c1 mov %rax,%rcx
406e84: ba 1e 00 00 00 mov $0x1e,%edx
406e89: be 01 00 00 00 mov $0x1,%esi
406e8e: bf b8 79 40 00 mov $0x4079b8,%edi
406e93: e8 58 97 ff ff callq 4005f0 <fwrite@plt>
406e98: c9 leaveq
406e99: c3 retq
Spot the obfuscation which makes a jump in the function to surpass
init_data_good
:
406e57: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%rbp)
406e5e: 83 7d fc 00 cmpl $0x0,-0x4(%rbp)
406e62: 74 16 je 406e7a <good_toll+0x2b>
Zero is always equal to zero. Bummer, but gdb
comes to the rescue:
$ gdb ./challenge_area41
(gdb) b *0x406e62
Breakpoint 1 at 0x406e62
(gdb) r
...
-> The next part of the challenge is tolled. No change given!
-> Only CHF accepted. How much are you going to pay?
-> 5
Breakpoint 1, 0x0000000000406e62 in good_toll ()
(gdb) p $eflags
$1 = [ PF ZF IF ]
(gdb) set ($eflags) = 0x206
(gdb) p $eflags
$2 = [ PF IF ]
What we did is we reset the ZF before executing je 406e7a <good_toll+0x2b>
to
avoid the conditional jump.
Now comes the interesting part... Hope you still remember my gdb
session which
I've started in Level 03. Let's continue it to see what happens:
(gdb) c
Continuing.
(Remember HashDays challenges...)
----------------------------------------------------------------------------------------------------
| 0 0 0 0 0 0 0 1 1 0 1 0 0 1 0 1 1 0 1 0 1 0 1 0 1 1 1 1 1 1 0 0 1 1 1 1 0 1 1 1 0 1 0 0 0 0 0 0 0 |
| 0 1 1 1 1 1 0 1 0 0 0 1 0 1 1 1 1 1 0 1 1 0 0 0 0 1 0 0 0 1 1 1 0 0 1 1 0 1 0 0 0 1 0 1 1 1 1 1 0 |
| 0 1 0 0 0 1 0 1 1 1 1 0 1 0 1 0 0 0 0 1 1 1 1 1 1 0 0 0 0 1 0 1 0 1 1 1 1 1 1 0 0 1 0 1 0 0 0 1 0 |
| 0 1 0 0 0 1 0 1 0 0 0 1 1 1 1 0 1 1 0 1 0 1 0 0 0 0 0 1 1 0 0 1 1 1 1 0 0 0 1 0 1 1 0 1 0 0 0 1 0 |
| 0 1 0 0 0 1 0 1 1 0 0 0 0 0 1 1 0 1 0 1 1 0 0 0 0 0 0 0 0 1 0 0 0 1 1 0 0 0 1 1 1 1 0 1 0 0 0 1 0 |
| 0 1 1 1 1 1 0 1 0 0 1 1 0 1 0 0 0 1 1 0 0 1 0 1 1 1 0 0 1 1 1 1 0 0 0 1 1 0 0 1 1 1 0 1 1 1 1 1 0 |
| 0 0 0 0 0 0 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 0 0 0 0 0 0 |
| 1 1 1 1 1 1 1 1 1 0 0 1 0 1 0 1 0 1 0 0 1 1 0 1 1 1 0 1 0 1 0 1 0 1 0 1 1 0 1 0 1 1 1 1 1 1 1 1 1 |
| 0 0 0 0 0 1 0 0 0 1 0 1 1 0 1 1 0 0 0 1 1 0 0 0 0 0 0 1 1 1 0 0 1 0 1 1 0 1 0 0 0 0 1 0 1 0 1 0 1 |
| 1 1 1 1 1 1 1 1 0 0 1 0 0 0 0 1 1 1 1 0 1 0 1 0 0 1 1 0 0 0 0 1 0 1 1 1 1 0 1 1 1 0 0 0 0 0 0 0 1 |
| 0 0 0 1 0 0 0 1 1 1 1 0 0 0 0 0 1 0 1 0 1 0 1 0 0 1 0 1 1 0 1 0 1 0 0 0 1 0 0 0 1 1 1 0 0 0 0 1 0 |
| 1 0 0 0 1 1 1 1 0 0 0 1 1 1 0 0 1 1 1 1 0 1 1 1 1 1 1 1 0 1 0 1 0 0 0 0 1 1 0 0 1 0 0 0 0 1 1 1 0 |
| 1 1 0 1 0 1 0 1 1 0 1 0 0 0 0 1 1 0 1 0 0 0 0 0 0 1 1 1 1 1 0 0 1 0 1 0 0 1 0 0 0 1 0 0 1 0 0 0 0 |
| 1 0 1 0 0 1 1 0 0 0 0 0 1 1 1 0 1 1 0 0 1 0 1 0 1 0 1 0 0 0 0 0 0 0 1 1 0 0 1 0 1 0 0 0 1 1 1 1 1 |
| 0 0 0 1 1 0 0 1 1 1 0 1 1 1 1 1 1 0 0 1 1 0 0 0 1 1 0 1 0 0 0 0 0 0 0 1 0 1 0 0 1 1 1 1 1 0 1 0 0 |
| 1 1 1 0 0 0 1 1 0 1 0 0 0 0 1 0 1 0 1 1 1 1 1 1 1 1 1 1 0 1 0 1 0 0 0 1 1 0 1 0 0 1 1 1 0 0 1 1 0 |
| 0 0 0 0 0 1 0 0 1 1 1 0 0 1 1 0 0 1 0 1 0 0 0 0 0 1 1 1 1 1 0 0 1 0 1 1 0 0 0 1 0 1 0 0 1 1 0 0 0 |
| 1 0 1 1 1 0 1 1 0 0 1 0 1 1 0 0 1 1 1 0 1 0 1 0 1 0 1 0 0 0 0 1 1 1 1 0 0 0 1 1 1 0 0 1 0 0 0 1 1 |
| 1 0 0 0 0 0 0 1 0 1 1 1 0 1 1 0 0 1 0 1 0 1 1 0 1 1 0 0 1 0 1 0 1 1 0 0 1 1 0 0 0 0 1 1 0 0 0 0 0 |
| 1 1 0 1 0 0 1 1 1 0 0 0 0 0 1 0 1 0 0 1 0 1 1 1 1 0 1 0 0 0 0 1 0 0 1 1 1 0 1 1 0 1 1 1 0 1 1 1 1 |
| 0 0 1 1 0 0 0 1 1 1 1 1 0 1 1 0 1 1 0 0 0 1 0 0 0 1 1 1 1 1 1 0 1 0 1 1 0 1 0 1 0 0 1 0 1 0 0 1 0 |
| 0 1 1 1 1 1 1 1 0 1 1 0 1 0 1 1 0 0 0 0 1 0 0 1 0 0 1 0 1 0 0 0 1 1 1 0 1 0 1 1 1 0 0 0 0 0 1 0 1 |
| 0 0 1 0 0 0 0 0 0 0 1 0 0 1 1 0 0 0 1 0 1 1 0 0 0 0 0 0 1 0 1 1 1 0 0 1 1 0 0 1 0 0 0 0 0 1 1 0 0 |
| 0 0 1 1 0 1 1 1 0 1 0 0 1 1 1 1 0 1 1 0 1 1 0 1 1 1 0 1 0 1 1 1 0 1 1 1 1 1 1 0 0 1 1 1 0 0 1 0 1 |
| 0 1 0 0 0 1 0 1 0 0 1 0 1 0 1 1 0 0 0 0 0 0 0 1 0 1 0 1 1 1 0 0 1 1 0 0 0 0 1 1 0 1 0 1 0 0 0 0 0 |
| 1 0 0 0 0 1 1 1 0 1 0 0 1 1 0 1 1 0 1 0 1 0 0 1 1 1 0 0 0 0 0 1 1 1 1 0 1 0 1 0 0 1 1 1 0 1 1 1 1 |
| 1 1 1 1 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0 0 1 0 0 0 0 0 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 |
| 1 0 1 1 0 0 1 0 0 1 0 1 0 1 0 0 0 1 1 1 0 1 1 0 1 1 0 0 0 0 0 0 0 0 0 1 1 1 1 1 0 0 1 0 1 1 1 0 1 |
| 1 0 0 1 1 1 0 1 0 1 0 1 0 0 0 1 1 0 1 0 0 0 1 1 1 0 1 0 1 0 1 0 1 1 0 0 0 0 0 0 1 0 0 1 0 1 0 0 0 |
| 1 1 1 0 1 1 1 1 1 1 0 0 0 1 1 0 1 1 0 0 1 0 0 0 0 1 1 1 1 0 0 1 0 1 1 1 1 0 1 1 0 1 1 0 1 0 1 0 1 |
| 1 0 0 0 1 0 0 0 1 1 0 1 1 1 1 1 1 1 1 0 0 1 1 1 0 0 1 0 1 0 1 1 1 0 0 0 0 1 0 0 0 0 0 1 1 0 1 0 0 |
| 0 1 0 1 1 1 1 0 0 0 0 1 1 1 0 0 1 1 1 1 0 0 1 1 1 1 0 0 0 0 1 1 0 0 0 1 1 1 1 1 0 1 1 1 0 1 1 1 1 |
| 1 1 1 1 1 0 0 0 1 0 1 0 0 0 1 1 0 0 0 1 0 0 1 1 1 1 0 1 1 0 1 1 1 1 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 |
| 1 0 0 0 0 0 1 0 1 1 0 0 1 0 0 1 1 0 1 0 1 0 1 0 0 1 0 1 1 0 0 0 1 1 1 1 0 0 1 1 0 1 1 0 1 0 1 0 1 |
| 1 0 0 1 0 1 0 0 0 1 0 1 0 1 1 0 1 1 0 1 1 1 0 1 0 0 1 0 0 1 1 1 1 1 0 0 1 1 0 0 0 1 0 0 0 1 1 0 0 |
| 0 1 1 0 0 1 1 1 1 0 0 0 0 0 1 0 1 0 0 1 1 0 1 1 0 1 0 0 0 1 0 1 0 1 0 0 1 1 0 1 0 1 1 0 1 1 1 0 1 |
| 0 0 0 0 0 1 0 0 0 0 1 1 0 1 1 0 1 1 0 1 0 0 1 0 1 0 0 1 1 0 1 0 1 0 0 0 0 1 1 0 1 1 1 0 0 1 0 1 0 |
| 0 1 1 0 0 1 1 0 0 0 1 0 1 0 1 1 0 0 0 0 1 0 1 0 0 1 1 0 1 0 0 0 1 1 1 0 1 1 1 0 1 0 0 0 1 0 0 1 0 |
| 1 0 1 1 1 0 0 0 0 1 0 1 1 0 1 0 0 1 0 0 0 1 0 1 0 0 1 0 0 1 1 1 1 0 0 0 0 1 0 0 1 0 0 0 1 1 0 0 0 |
| 1 0 0 0 1 1 1 1 1 0 0 0 1 0 0 1 0 1 1 0 0 0 0 1 0 0 0 1 0 0 1 0 0 0 0 1 1 0 0 0 0 1 1 1 1 1 1 0 0 |
| 0 0 0 1 1 1 0 0 1 0 1 1 1 0 1 0 0 0 0 1 0 0 0 0 0 0 0 1 1 0 0 0 1 1 1 0 0 1 1 0 0 0 0 0 0 1 0 0 1 |
| 1 1 1 1 1 1 1 1 0 0 1 1 0 1 0 0 1 0 1 1 1 1 0 1 1 1 0 1 0 0 0 0 0 1 1 1 0 1 1 0 0 1 1 1 0 0 0 0 0 |
| 0 0 0 0 0 0 0 1 0 1 1 0 0 0 0 0 1 0 1 1 0 0 0 1 0 1 0 0 0 1 1 1 0 0 0 1 1 1 0 0 0 1 0 1 0 0 1 0 0 |
| 0 1 1 1 1 1 0 1 1 0 1 0 0 1 0 1 0 1 1 1 1 0 0 1 1 1 0 1 0 1 0 0 0 1 1 1 1 1 1 0 0 1 1 1 0 1 1 1 0 |
| 0 1 0 0 0 1 0 1 0 0 0 0 0 0 0 1 1 0 1 1 0 1 0 0 0 0 0 1 1 0 0 0 1 0 1 0 0 1 1 0 0 0 0 0 0 1 0 1 0 |
| 0 1 0 0 0 1 0 1 0 1 1 1 0 1 1 0 1 1 0 0 1 0 0 1 1 1 0 0 1 1 0 1 1 1 1 1 0 0 1 0 0 1 0 1 0 1 1 1 1 |
| 0 1 0 0 0 1 0 1 0 1 0 0 0 1 1 1 1 0 0 1 0 0 1 1 0 1 0 0 1 1 1 0 1 0 0 1 1 0 0 0 1 0 0 0 1 1 1 1 1 |
| 0 1 1 1 1 1 0 1 0 1 1 1 0 0 1 0 1 0 0 1 1 1 1 1 0 1 1 0 0 1 1 1 0 1 1 1 1 1 1 0 1 1 0 1 1 1 1 1 0 |
| 0 0 0 0 0 0 0 1 0 0 1 0 1 0 1 0 0 1 0 0 0 1 1 1 1 1 0 1 1 1 0 0 1 0 1 0 0 1 1 0 0 1 1 0 1 1 0 0 0 |
----------------------------------------------------------------------------------------------------
Bad programmers worry about the code. Good programmers worry about data structures and their relationships.
[Inferior 1 (process 2393) exited normally]
There we go. Seems like a puzzle of 49x49 bits size. I've tried to group bits
to get some meaningful codes (like ASCII), but it didn't work.
Copying into vim
and doing :%s/1/ /g
helped to spot three squares in the
corners. Hehe, does it ring a bell? If not, I recommend you to look around a
mess on your desk, might be useful, really :D
Anyway, if you want to get spoiled, follow the link. The answer is here.
Decoding the code with zbarimg
gives us the following:
Fwtjjfqxtgwatkv! Gux zfsh xgvk yeh knddqbqok! Vwsa xa nro dlx log ay qr
[email protected], zr yjq bwau Swbd41 kuqxjohvih vnpfwaql hlgm. Lhwiydkq dfi
pxomhkyfrvy djj xoegbk bbokupw!
Ehrm, seems that non-alphabetic chars are already in place, so we need to
decipher alpha ones.
First guess Swbd41 -> Area41
. Doing replacement w -> r | b -> e | d -> a
in the text doesn't reveal anything, hence it doesn't seem to be a simple
substitution cipher.
The very first word looks like Congratulations!
. Checking the distance
between maybe decrypted and encrypted, gives us:
$ python -c 'print(list(map(lambda x: ord(x[0]) - ord(x[1]), zip("Congratulations", "Fwtjjfqxtgwatkv"))))
[-3, -8, -6, -3, 8, -5, 3, -3, -8, -6, -3, 8, -5, 3, -3]
Do you see the key? Looks like it is [-3, -8, -6, -3, 8, -5, 3]
. Applying it
to the ciphertext returns:
Congratulations?ou�avepassthechallengeSnduYhow_oudidittoctf�b}omto�et_o[r[reacon�erence~isco[ntcodeFed|ackandsuggestionsare{l]a_s]elcom
Close enough, but there are some non-printable chars and Area
is deciphered
wrongly. 83 (S
) + 8 is not equal to 65 (A
) as it supposed to be and it is
outside A
(65)..Z
(90) range...
Wait! But 65 + ((83 + 8) - 90) - 1 = 65
. I hope you got the idea:
#!/usr/bin/env python
enc = "Fwtjjfqxtgwatkv! Gux zfsh xgvk yeh knddqbqok! Vwsa xa nro dlx log " \
"ay qr [email protected], zr yjq bwau Swbd41 kuqxjohvih vnpfwaql hlgm. " \
"Lhwiydkq dfi pxomhkyfrvy djj xoegbk bbokupw!"
key = [-3, -8, -6, -3, 8, -5, 3]
i = 0
for c in enc:
if c.isalpha():
(low, high) = (ord('a'), ord('z')) if c.islower() else \
(ord('A'), ord('Z'))
tmp = ord(c) + key[i]
if tmp > high:
dec = low + (tmp - high) - 1
elif tmp < low:
dec = high - ((low - tmp) - 1)
else:
dec = tmp
print(chr(dec), end='')
i = (i+1) % len(key)
else:
print(c, end='')
print('')
Finally the answer:
Congratulations! You have pass the challenge! Send us how you did it to
[email protected], to get your Area41 conference discount code.
Feedback and suggestions are always welcome!
GG! And of course, kudos to:
$ objdump -d challenge_area41 | grep author -A4
0000000000406ef0 <author>:
406ef0: 55 push %rbp
406ef1: 48 89 e5 mov %rsp,%rbp
406ef4: bf d8 79 40 00 mov $0x4079d8,%edi
406ef9: e8 92 96 ff ff callq 400590 <puts@plt>
$ objdump -s challenge_area41 | grep 4079d8 -A2
4079d8 4c657665 6c207772 69747465 6e206279 Level written by
4079e8 204a6176 69657220 28406a61 76757469 Javier (@javuti
4079f8 6e290020 202d3e20 00 n). -> .