library_book Note: signal 11 doesn’t mean broken. library_book
It was embaracing that we were the only team who solved this challenge! Even some formidable teams didn’t solve this one. Here is what we did. First, we run the challenge and saw what we got.
➜ file library_book library_book: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, stripped ➜ chmod +x library_book ➜ ./library_book  26662 segmentation fault (core dumped) ./library_book ➜
We relaized that it gave a segmentation fault. The only hope we got to overcome this challenge is mainly static analysis.
➜r2 library_book [f000:fff0]> i type bios file library_book fd 6 size 0x4f97c iorw false blksz 0x0 mode -r-- block 0x100 format bios havecode true pic false canary false nx false crypto false va true bintype bios class 1.0 arch x86 bits 16 machine pc os any minopsz 1 maxopsz 16 pcalign 0 subsys unknown endian little stripped false static true linenum false lsyms false relocs false binsz 326012 [f000:fff0]>
Things started to get more confusing. It will make sense why it segfaulted if it was bios. After a quick look at the strings using
izz command, we found these strings which meant that indeed it is not a bios file.
$Info: This file is packed with the BSI executable packer http://BSI.sf.net $\n $Id: BSI 3.91 Copyright (C) 1996-2013 the BSI Team. All Rights Reserved. $\n GCC: (Ubuntu 5.4.0-6u\e1~16
After checking the website, we realized that BSI is not a binary packer at all! Actually the first string looks extremly similar to the one made by UPX packer, so we tried replacing all instances of
UPX then unpacked it using UPX.
[f000:fff0]> e search.from=0x0 [f000:fff0]> e search.to = 0xffffffff [f000:fff0]> oo+ File library_book reopened in read-write mode [f000:fff0]> s 0xb4 [0000:00b4]> w UPX @@/ BSI Searching 3 bytes from 0x00000000 to 0xffffffff: 42 53 49 Searching 3 bytes in [0x0-0xffffffff] hits: 7 0x000000b4 hit1_0 . $(jBSI! . 0x0004ecb2 hit1_1 .packed with the BSI executable pack. 0x0004eccf hit1_2 .e packer http://BSI.sf.net $$Id: . 0x0004ece2 hit1_3 ..sf.net $$Id: BSI 3.91 Copyright . 0x0004ed07 hit1_4 .) 1996-2013 the BSI Team. All Right. 0x0004f950 hit1_5 .Y_$IBSI!BSI!. 0x0004f958 hit1_6 .$IUPX!BSI!_h. [f000:ffdc]> q ➜ upx -d library_book 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 -------------------- ------ ----------- ----------- 838610 <- 326012 38.88% linux/ElfAMD library_book Unpacked 1 file. ➜
oo+ is used to enable writing in the binary file, thus patching it.
w is used to write a string at the current seek, and
s is used to change the current seek.
-d switch in
upx command is used to unpack binaries packed with upx. The good news is that upx unpacked the binary file successfually, and it recognized it as ELF file for AMD processors. The only problem is that the executable still give signal 11.
➜ ./library_book  16790 segmentation fault (core dumped) ./library_book ➜ file library_book library_book: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.32, BuildID[sha1]=32e423425aca449a1b5e60722880c9128042f935, stripped ➜ r2 library_book Warning: Cannot initialize dynamic strings [0x00400890]> i type EXEC (Executable file) file library_book fd 6 size 0xcc418 iorw false blksz 0x0 mode -r-- block 0x100 format elf64 havecode true pic false canary false nx true crypto false va true bintype elf class ELF64 lang c arch x86 bits 64 machine AMD x86-64 architecture os linux minopsz 1 maxopsz 16 pcalign 0 subsys linux endian little stripped true static true linenum false lsyms false relocs false rpath NONE binsz 834645 [0x00400890]>
The second step that we took is to auto analyze the whole code, it took a long time. Then, try some FLIRT signatures against the binary hoping that one will work. We got ours from push0ebp’s repo on github, and we tried all of the ubuntu’s ones all together using the
zF command in r2!
After that we checked the function main to see what is in there.
So, it seems that the main function is simple as calling
puchar in a loop then calling
putchar one last more time. What made us confident that flirt guessed putchar correctly is that the second
putchar looked like this:
1 2 3 4 0x004009fb bf0a000000 mov edi, 0xa 0x00400a00 e8cbf00000 call flirt.putchar ;; int putchar(int c) 0x00400a05 b800000000 mov eax, 0 0x00400a0a c9 leave
if we assumed that this putchar doesn’t use standard linux calling convention AKA cdecl and argument is put into register
edi, then this
putchar prints newline, and that makes sense here to print new line after finishing.
The loop part does some calculations that we were lazy to follow up with specially when it came to sign extensions and all that headache.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 0x004009bd 837dfc18 cmp dword [rbp - local_4h], 0x18 ; [0x18:4]=0x400890 entry0 0x004009c1 7f38 jg 0x4009fb ; 0x004009c3 8b45fc mov eax, dword [rbp - local_4h] 0x004009c6 4898 cdqe 0x004009c8 8b0485a0906c mov eax, dword [rax*4 + 0x6c90a0] ; [0x6c90a0:4]=0x2303526 0x004009cf 25ffffff00 and eax, 0xffffff 0x004009d4 89c2 mov edx, eax 0x004009d6 8b45fc mov eax, dword [rbp - local_4h] 0x004009d9 4898 cdqe 0x004009db 8b0485a0906c mov eax, dword [rax*4 + 0x6c90a0] ; [0x6c90a0:4]=0x2303526 0x004009e2 c1e818 shr eax, 0x18 0x004009e5 89c1 mov ecx, eax 0x004009e7 d3fa sar edx, cl 0x004009e9 89d0 mov eax, edx 0x004009eb 0fb6c0 movzx eax, al 0x004009ee 89c7 mov edi, eax 0x004009f0 e8dbf00000 call flirt.putchar ;; int putchar(int c) 0x004009f5 8345fc01 add dword [rbp - local_4h], 1 0x004009f9 ebc2 jmp 0x4009bd ; ; JMP XREF from 0x004009c1 (main)
Like the other call to
putchar, argument seems to be passed via
Here is the strategy to solve this challenge.
We overwrote both calls to
putchar with nops so no function get called to avoid any segmentation fault. From entry0, go directly to main function, set break point at
0x004009f0 and see what goes in edi.
1 2 3 4 5 6 7 8 9 10 11 import r2pipe dbg = r2pipe.open("library_book", ["-d"]) dbg.cmd("dr rip=0x004009ae") print "[+] rip is set to", dbg.cmd("dr rip") dbg.cmd("db 0x004009ee") flag = "" for _ in xrange(0x19): dbg.cmd("dc") c = chr(int(dbg.cmd("dr eax"), 16)) flag = flag + c print flag
This code snippet directly prints the flag