Home CTF Challenge Walkthrough from Practical Binary Analysis
Post
Cancel

CTF Challenge Walkthrough from Practical Binary Analysis

Practical Binary Analysis Book authored by Dennis Andriesse covers all major binary analysis topics in an accessible way, from binary formats, disassembly, and basic analysis to advanced techniques like binary instrumentation, taint analysis, and symbolic execution.

This blog covers the concepts and exercises from Chapter 5 of the book, focusing on basic binary analysis in Linux. The author uses a CTF challenge to illustrate the tools and techniques used in binary analysis.

Content

Level 1 is well explained in the chapter, so I will start from Level 2, which is unlocked using the flag from Level 1.

Challenge

Complete the new CTF challenge unlocked by the oracle program! You can complete the entire challenge using only the tools discussed in this chapter and what you learned in Chapter 2. After completing the challenge, don’t forget to give the flag you found to the oracle to unlock the next challenge.

Level 2

Hint: Combine the parts

Checking the file type of a command using the file utility.

1
2
$ file lvl2
lvl2: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=457d7940f6a73d6505db1f022071ee7368b67ce9, stripped

The nm utility, which lists symbols from object files, reveals that it employs functions such as rand and srand.

Running the program occasionally produces varying outputs, displaying random bytes each time. I utilized the sleep command to demonstrate the different outputs, as the seed for the srand function would be the same for all processes if executed simultaneously.

Utilize ltrace to trace the library function calls, revealing that it calls rand to generate a random number and puts to print a byte.

Use GDB to set a breakpoint at the puts call in order to analyze the surrounding instructions.

In GDB, a random number is generated using the rand function. This number is then utilized to index an array and retrieve a value, which appears to represent the flag. The flag needs to be passed to the oracle binary in order to unlock the next challenge.

Level 3

Hint: Fix four broken things

Checking the file type of a command using the file utility.

1
2
$ file lvl3
lvl3: ELF 64-bit LSB executable, Motorola Coldfire, version 1 (Novell Modesto), can't read elf program headers at 4022250974, for GNU/Linux 2.6.32, BuildID[sha1]=b6c0e8d914c6433e661b2cac794108671bdcaa06, stripped

The output contains several elements that appear inconsistent with an ELF 64-bit executable:

  • Motorola Coldfire: The Motorola Coldfire is a family of microprocessors developed by Motorola (now Freescale Semiconductor, a part of NXP). It is commonly used in embedded systems.
  • Version 1 (Novell Modesto): This likely refers to the OS/ABI, which should be ELFOSABI_NONE (UNIX System V ABI) instead of ELFOSABI_MODESTO.
  • Can’t read ELF program headers at 4022250974”: This is an error message indicating that the ‘file’ command encountelightgreen an issue while attempting to read the ELF program headers at a specific offset (4022250974). This could suggest corruption or an inconsistency in the file.

For viewing the ELF header, utilize the readelf utility.

To rectify the discrepancies:

  • Change the value of the EI_OSABI field in the e_ident array from 0xb(ELFOSABI_MODESTO) to 0x0(ELFOSABI_NONE).
  • Modify the e_machine field in the ELF header from 0x34(EM_COLDFIRE) to 0x3e(EM_X86_64).
  • Adjust the value of e_phoff in the ELF header from 0xdeadbeef to 0x40. This ensures that the program header comes after the ELF header, considering that the size of the ELF header is 64 bytes, as specified in the ELF header.

For making these changes, utilize a hex editor.

After changes :

The file output now displays everything correctly, and the files execute successfully.

The flag was printed, but passing it to the oracle revealed that it’s incorrect, indicating that there are still inconsistencies.

CHecking the section headers showed that the section type of .text section is set to 0x8(SHT_NOBITS) instead of 0x1(SHT_PROGBITS).

The address of the sh_type for the .text section can be calculated as the sum of the offset to the start of section headers and the size of section headers before the .text section.

addr = e_shoff + e_shentsize * number of sections before .text

Please correct this and try the flag again.

Level 4

Hint: Watch closely while I run

Checking the file type of a command using the file utility.

1
2
$ file lvl4
lvl4: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=f8785d89a1f11e7b413c08c6176ad1ed7b95ca08, stripped

It shows that lvl4 is a stripped 64-bit ELF executable for the x86-64 architecture, dynamically linked with shared libraries, intended to run on Linux kernel 2.6.32 or newer, and has a specific build identifier.

The strings utility shows that the file contains several strings, such as “FLAG” and “XaDht-+1432=/as4?0129mklqt!@cnz^”, which seem like an encrypted flag.

The ltrace utility reveals that an environment variable named “FLAG” is set with the flag.

Level 5

Hints:

  • Secrets hidden in code unused
  • The method of redirection is the key
  • Static rather than dynamically

Checking the file type of a command using the file utility.

1
2
$ file lvl5
lvl5: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=1c4f1d4d245a8e252b77c38c9c1ba936f70d8245, stripped

The strings utility reveals some interesting strings suggesting that this binary may include a decryption mechanism involving a key and a decrypted flag.

Upon execution, it only displays the strings we saw in the output of the strings utility.

readelf indicates that the entry point of the binary is at 0x400520.

ltrace shows that 0x400500 is passed as the first argument to __libc_start_main, indicating that the main function starts at 0x400500. Then, at 0x40050e, it calls the puts function to print the string “nothing to see here.”

The disassembly in objdump shows that the function at 0x400500 calls the puts function to print the string located at 0x400797.

Examining the .rodata section with objdump reveals that the string “nothing to see here” is located at address 0x400797.

The remaining code in the .text section does not get executed. For example, the code starting at 0x400620 appears to load an encrypted string onto the stack, and the code at 0x4006a0 XORs it with the value at address 0x400540.

We can try changing the argument passed to __libc_start_main from 0x400500 to 0x400620, ensuring that the loading and XORing instructions get executed.

The flag above is invalid. Upon closer inspection, both the actual address passed to __libc_start_main and the key printed are the same value, 0x400500. The key is retrieved from the instruction 40053d: 48 c7 c7 00 05 40 00 mov rdi,0x400500. What if we edit these bytes to use the new function address (0x400620) instead?

After making this change, the binary now prints the flag upon execution.

Level 6

Hint: Find out what I expect, then trace me for a hint

Checking the file type of a command using the file utility.

1
2
$ file lvl6
lvl6: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=5702d22547fea17be6ca988688df47b9c6525e05, stripped

The strings utility reveals a string indicating that this program might be expecting an argument.

So, pass a dummy argument and use ltrace to trace the library calls.

The ltrace output shows that the program compares the dummy input with “get_data_addr” and prints prime numbers up to 100. Now, try using “get_data_addr” as the argument. This sets an environment variable named DATA_ADDR with the value 0x4006c1.

If we examine the data instructions around that address, they appear to be gibberish.

4006c1: 2e 29 c6                cs sub esi,eax
4006c4: 4a 0f 03 a6 ee 2a 30    rex.WX lsl rsp,WORD PTR [rsi+0x7f302aee]
4006cb: 7f
4006cc: ec                      in     al,dx
4006cd: c8 c3 ff 42             enter  0xffc3,0x42

Combining these bytes reveals the flag.

This post is licensed under CC BY 4.0 by the author.