Weeks ago, I started reading the Practical Binary Analysis book from Dennis Andriesse. Totally recommended by the way.

This is a walkthrough the lvl2 challenge from Chapter 5.

If you’ve followed the book, or tried to solve Level 1 by yourself this is where you’re at:

binary@binary-VirtualBox:~/code/chapter5/level1$ export LD_LIBRARY_PATH=`pwd`

binary@binary-VirtualBox:~/code/chapter5/level1$ ./ctf

binary@binary-VirtualBox:~/code/chapter5/level1$ GUESSME=”Crackers Don’t Matter” ./ctf show_me_the_flag

checking ‘show_me_the_flag’

ok

flag = 84b34c124b2ba5ca224af8e33b077e9e

Once the flag is passed to the oracle, we get the next level to work with, and a hint could be helpful or not.

binary@binary-VirtualBox:~/code/chapter5$ ./oracle 84b34c124b2ba5ca224af8e33b077e9e

+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+

| Level 1 completed, unlocked lvl2 |

+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+

Run oracle with -h to show a hint

binary@binary-VirtualBox:~/code/chapter5$ ./oracle 84b34c124b2ba5ca224af8e33b077e9e -h

Combine the parts

Let’s start by quickly executing the binary:

binary@binary-VirtualBox:~/code/chapter5/level2$ ./lvl2

df

binary@binary-VirtualBox:~/code/chapter5/level2$ ./lvl2

c4

binary@binary-VirtualBox:~/code/chapter5/level2$ ./lvl2

03

binary@binary-VirtualBox:~/code/chapter5/level2$ ./lvl2

df

binary@binary-VirtualBox:~/code/chapter5/level2$ ./lvl2

88

binary@binary-VirtualBox:~/code/chapter5/level2$ ./lvl2

f8

binary@binary-VirtualBox:~/code/chapter5/level2$ ./lvl2

a5

Running the binary a couple of times showed what looks to be some random hex values being printed. If you prefer to first look at the binary prior to execute it, that’s ok. In my case, I knew it’s not dangerous and now we’ve seen the behaviour we can look and deep dive on what it’s doing.

binary@binary-VirtualBox:~/code/chapter5/level2$ readelf -h -n lvl2

ELF Header:

Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00

Class: ELF64

Data: 2's complement, little endian

Version: 1 (current)

OS/ABI: UNIX - System V

ABI Version: 0

Type: EXEC (Executable file)

Machine: Advanced Micro Devices X86-64

Version: 0x1

Entry point address: 0x400540

Start of program headers: 64 (bytes into file)

Start of section headers: 4624 (bytes into file)

Flags: 0x0

Size of this header: 64 (bytes)

Size of program headers: 56 (bytes)

Number of program headers: 9

Size of section headers: 64 (bytes)

Number of section headers: 29

Section header string table index: 28 Displaying notes found at file offset 0x00000254 with length 0x00000020:

Owner Data size Description

GNU 0x00000010 NT_GNU_ABI_TAG (ABI version tag)

OS: Linux, ABI: 2.6.32 Displaying notes found at file offset 0x00000274 with length 0x00000024:

Owner Data size Description

GNU 0x00000014 NT_GNU_BUILD_ID (unique build ID bitstring)

Build ID: 457d7940f6a73d6505db1f022071ee7368b67ce9 binary@binary-VirtualBox:~/code/chapter5/level2$ readelf --sections --wide lvl2

There are 29 section headers, starting at offset 0x1210: Section Headers:

[Nr] Name Type Address Off Size ES Flg Lk Inf Al

[ 0] NULL 0000000000000000 000000 000000 00 0 0 0

[ 1] .interp PROGBITS 0000000000400238 000238 00001c 00 A 0 0 1

[ 2] .note.ABI-tag NOTE 0000000000400254 000254 000020 00 A 0 0 4

[ 3] .note.gnu.build-id NOTE 0000000000400274 000274 000024 00 A 0 0 4

[ 4] .gnu.hash GNU_HASH 0000000000400298 000298 00001c 00 A 5 0 8

[ 5] .dynsym DYNSYM 00000000004002b8 0002b8 0000a8 18 A 6 1 8

[ 6] .dynstr STRTAB 0000000000400360 000360 000048 00 A 0 0 1

[ 7] .gnu.version VERSYM 00000000004003a8 0003a8 00000e 02 A 5 0 2

[ 8] .gnu.version_r VERNEED 00000000004003b8 0003b8 000020 00 A 6 1 8

[ 9] .rela.dyn RELA 00000000004003d8 0003d8 000018 18 A 5 0 8

[10] .rela.plt RELA 00000000004003f0 0003f0 000078 18 AI 5 24 8

[11] .init PROGBITS 0000000000400468 000468 00001a 00 AX 0 0 4

[12] .plt PROGBITS 0000000000400490 000490 000060 10 AX 0 0 16

[13] .plt.got PROGBITS 00000000004004f0 0004f0 000008 00 AX 0 0 8

[14] .text PROGBITS 0000000000400500 000500 0001b2 00 AX 0 0 16

[15] .fini PROGBITS 00000000004006b4 0006b4 000009 00 AX 0 0 4

[16] .rodata PROGBITS 00000000004006c0 0006c0 000034 00 A 0 0 4

[17] .eh_frame_hdr PROGBITS 00000000004006f4 0006f4 000034 00 A 0 0 4

[18] .eh_frame PROGBITS 0000000000400728 000728 0000ec 00 A 0 0 8

[19] .init_array INIT_ARRAY 0000000000600e10 000e10 000008 00 WA 0 0 8

[20] .fini_array FINI_ARRAY 0000000000600e18 000e18 000008 00 WA 0 0 8

[21] .jcr PROGBITS 0000000000600e20 000e20 000008 00 WA 0 0 8

[22] .dynamic DYNAMIC 0000000000600e28 000e28 0001d0 10 WA 6 0 8

[23] .got PROGBITS 0000000000600ff8 000ff8 000008 08 WA 0 0 8

[24] .got.plt PROGBITS 0000000000601000 001000 000040 08 WA 0 0 8

[25] .data PROGBITS 0000000000601040 001040 0000a0 00 WA 0 0 32

[26] .bss NOBITS 00000000006010e0 0010e0 000008 00 WA 0 0 1

[27] .comment PROGBITS 0000000000000000 0010e0 000034 01 MS 0 0 1

[28] .shstrtab STRTAB 0000000000000000 001114 0000fc 00 0 0 1

Key to Flags:

W (write), A (alloc), X (execute), M (merge), S (strings), l (large)

I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)

O (extra OS processing required) o (OS specific), p (processor specific)

A quick look at the file might not show anything useful up to this point, so we can try to take a look at the strings:

binary@binary-VirtualBox:~/code/chapter5/level2$ strings lvl2

/lib64/ld-linux-x86-64.so.2

E}y@

libc.so.6

srand

puts

time

__libc_start_main

__gmon_start__

GLIBC_2.2.5

AWAVA

AUATL

[]A\A]A^A_

;*3$"

GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.9) 5.4.0 20160609

...

.rela.plt

.init

.plt.got

.text

.fini

.rodata

.eh_frame_hdr

.eh_frame

.init_array

.fini_array

.jcr

.dynamic

.got.plt

.data

.bss

.comment

Strings output showed some interesting information, what if we use nm :

binary@binary-VirtualBox:~/code/chapter5/level2$ nm -D lvl2

w __gmon_start__

U __libc_start_main

U puts

U rand

U srand

U time

This can be promising down the line, but let’s see what we can trace:

binary@binary-VirtualBox:~/code/chapter5/level2$ strace ./lvl2

execve("./lvl2", ["./lvl2"], [/* 36 vars */]) = 0

brk(NULL) = 0x124d000

access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)

access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)

open("/home/binary/code/chapter5/level1/tls/x86_64/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)

stat("/home/binary/code/chapter5/level1/tls/x86_64", 0x7ffd6e8e1f20) = -1 ENOENT (No such file or directory)

open("/home/binary/code/chapter5/level1/tls/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)

stat("/home/binary/code/chapter5/level1/tls", 0x7ffd6e8e1f20) = -1 ENOENT (No such file or directory)

open("/home/binary/code/chapter5/level1/x86_64/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)

stat("/home/binary/code/chapter5/level1/x86_64", 0x7ffd6e8e1f20) = -1 ENOENT (No such file or directory)

open("/home/binary/code/chapter5/level1/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)

stat("/home/binary/code/chapter5/level1", {st_mode=S_IFDIR|0775, st_size=4096, ...}) = 0

open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3

fstat(3, {st_mode=S_IFREG|0644, st_size=98537, ...}) = 0

mmap(NULL, 98537, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f5c6f4dc000

close(3) = 0

access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)

open("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3

read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0P\t\2\0\0\0\0\0"..., 832) = 832

fstat(3, {st_mode=S_IFREG|0755, st_size=1868984, ...}) = 0

mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f5c6f4db000

mmap(NULL, 3971488, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f5c6ef06000

mprotect(0x7f5c6f0c6000, 2097152, PROT_NONE) = 0

mmap(0x7f5c6f2c6000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1c0000) = 0x7f5c6f2c6000

mmap(0x7f5c6f2cc000, 14752, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f5c6f2cc000

close(3) = 0

mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f5c6f4da000

mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f5c6f4d9000

arch_prctl(ARCH_SET_FS, 0x7f5c6f4da700) = 0

mprotect(0x7f5c6f2c6000, 16384, PROT_READ) = 0

mprotect(0x600000, 4096, PROT_READ) = 0

mprotect(0x7f5c6f4f5000, 4096, PROT_READ) = 0

munmap(0x7f5c6f4dc000, 98537) = 0

fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 6), ...}) = 0

brk(NULL) = 0x124d000

brk(0x126e000) = 0x126e000

write(1, "4f

", 34f

) = 3

exit_group(0) = ?

+++ exited with 0 +++

Now let’s look at what we can see with ltrace:

binary@binary-VirtualBox:~/code/chapter5/level2$ ltrace -i ./lvl2

[0x400569] __libc_start_main(0x400500, 1, 0x7fff410c4e18, 0x400640 <unfinished ...>

[0x40050b] time(0) = 1585476483

[0x400512] srand(0x5e807383, 0x7fff410c4e18, 0x7fff410c4e28, 0) = 0

[0x400517] rand(0x7f2146dfd620, 0x7fff410c4cfc, 0x7f2146dfd0a4, 0x7f2146dfd11c) = 0x52a72ef9

[0x400531] puts("f8"f8

) = 3

[0xffffffffffffffff] +++ exited (status 0) +++

With some of the memory addresses we can try to decompile the binary and take a closer look. Then fire it up in gdb and add a few breakpoints. When using objdump -M intel -d lvl2 we can take a look at the section where calls to time, srand, and rand. Let’s start at 0x400500 and look at the function:

0000000000400500 <.text>:

400500: 48 83 ec 08 sub rsp,0x8

400504: 31 ff xor edi,edi

400506: e8 c5 ff ff ff call 4004d0 <time@plt>

40050b: 89 c7 mov edi,eax

40050d: e8 ae ff ff ff call 4004c0 <srand@plt>

400512: e8 c9 ff ff ff call 4004e0 <rand@plt>

400517: 99 cdq

400518: c1 ea 1c shr edx,0x1c

40051b: 01 d0 add eax,edx

40051d: 83 e0 0f and eax,0xf

400520: 29 d0 sub eax,edx

400522: 48 98 cdqe

400524: 48 8b 3c c5 60 10 60 mov rdi,QWORD PTR [rax*8+0x601060]

40052b: 00

40052c: e8 6f ff ff ff call 4004a0 <puts@plt>

400531: 31 c0 xor eax,eax

400533: 48 83 c4 08 add rsp,0x8

400537: c3 ret

Tracing this back, looks like what is being passed to the puts function at 0x40052c does comes from mov rdi,QWORD PTR [rax*8+0x601060] . Now let’s put some breakpoints around there and look at what’s happening:

binary@binary-VirtualBox:~/code/chapter5/level2$ gdb lvl2

GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1

...

Type "apropos word" to search for commands related to "word"...

Reading symbols from lvl2...(no debugging symbols found)...done.

(gdb) set disassembly-flavor intel

(gdb) b *0x400524

Breakpoint 1 at 0x400524

(gdb) r

Starting program: /home/binary/code/chapter5/level2/lvl2 Breakpoint 1, 0x0000000000400524 in ?? ()

(gdb) x/i $rip

=> 0x400524: mov rdi,QWORD PTR [rax*8+0x601060]

(gdb) x/s *0x601060

0x4006c4: "03"

(gdb) print/d $rax

$1 = 4

(gdb) ni

0x000000000040052c in ?? ()

(gdb) x/5i $rip-0x5

0x400527: vmovups xmm12,XMMWORD PTR [rax+0x0]

=> 0x40052c: call 0x4004a0 <puts@plt>

0x400531: xor eax,eax

0x400533: add rsp,0x8

0x400537: ret

(gdb) x/5i $rip-0x10

0x40051c: rol BYTE PTR [rbx-0x2fd6f020],1

0x400522: cdqe

0x400524: mov rdi,QWORD PTR [rax*8+0x601060]

=> 0x40052c: call 0x4004a0 <puts@plt>

0x400531: xor eax,eax

(gdb) x/s $rdi

0x4006d0: "a5"

(gdb) x/16s *0x601060

0x4006c4: "03"

0x4006c7: "4f"

0x4006ca: "c4"

0x4006cd: "f6"

0x4006d0: "a5"

0x4006d3: "36"

0x4006d6: "f2"

0x4006d9: "bf"

0x4006dc: "74"

0x4006df: "f8"

0x4006e2: "d6"

0x4006e5: "d3"

0x4006e8: "81"

0x4006eb: "6c"

0x4006ee: "df"

0x4006f1: "88"

When we put a breakpoint and execute the binary up to 0x400524, we can then disassemble the line, look at the address that it’s pointing and peek at the content.

Value we found is “03”, if we point to the address that it will resolve we get “a5”. So it does looks like there is an array with the values that the binary will print. With x/16s *0x601060 we can print the content of what looks like an array and the output does looks like the flag we need.

binary@binary-VirtualBox:~/code/chapter5$ ./oracle 034fc4f6a536f2bf74f8d6d3816cdf88

+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+

| Level 2 completed, unlocked lvl3 |

+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+

Run oracle with -h to show a hint

Now we got the binary for the next level, next, let’s take a look at lvl3…