Tag Archives: practical binary analysis

Practical Binary Analysis – CTF Walkthrough – Level 3, 4

Hello,

In this article I’ll present you my solution on the Chapter 5 CTF from the book Practical Binary Analysis.

For this binary, the hint is to fix four broken things.

Running file gives us the following response:

binary@binary-VirtualBox:~/ctf$ file ./lvl3 
./lvl3: ERROR: ELF 64-bit LSB executable, Motorola Coldfire, version 1 (Novell Modesto) error reading (Invalid argument)

And the readelf command gives us:

binary@binary-VirtualBox:~/ctf$ readelf -h ./lvl3 
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 0b 00 00 00 00 00 00 00 00 
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            Novell - Modesto
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Motorola Coldfire
  Version:                           0x1
  Entry point address:               0x4005d0
  Start of program headers:          4022250974 (bytes into file)
  Start of section headers:          4480 (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
readelf: Error: Reading 0x1f8 bytes extends past end of file for program headers

At this moment, it was clear that the ELF header is broken, in order to fix it I opened up Wikipedia and the elf specification.

As I went through each field manually, with Binary Ninj. As I was checking the offset of the current byte, at 0x07, Wikipedia says: It is often set to 0 regardless of the target platform. I’ve changed it to 0x00. (Note: I think this field was probably ok as it is)

At offset 0x12, the value Specifies target instruction set architecture and is currently invalid. From googling, I found an article titled: Novell's Next Generation OS Will Natively Support Intel's Future IA-64 Architecture so I set the value to 0x3E.

binjahex.jpg

At offset 0x20 we have the e_phoff which Points to the start of the program header table. It usually follows the file header immediately, making the offset 0x34 or 0x40 for 32- and 64-bit ELF executables, respectively. The value de ad be ef is clearly invalid. I replaced the value with 40 00 00 00.

At this moment I thought I fixed the binary and ran it, it ran and it gave me an invalid flag.

If you run the following command:

binary@binary-VirtualBox:/media/sf_Dropzone$ readelf -S ./lvl3 | grep .text
  [14] .text             NOBITS           0000000000400550  00000550

You’ll see that the .text section is marked as 0x8 - NOBITS and it should be 0x1 - PROGBITS. To make the change I’ve used Binary Ninja as a hex editor, opening the binary in raw mode.

From the readelf command:

  Start of section headers:          4480 (bytes into file)

The start of the section header is 4480 bytes. A section header has the length of 0x40 bytes. 4480 to hex -> 0x1180. 0x40 * 14 + 0x1180 = 0x1500.

At offset 0x1504 we change the type from SHT_NOBITS to SHT_PROGBITS.

After we run the binary we get the valid flag:

3a5c381e40d2fffd95ba4452a0fb4a40  ./lvl3


After finishing level 3 I wanted to go to sleep and instead I thought of running ltrace, strace on the binary and I got this:

binary@binary-VirtualBox:~/ctf$ ltrace ./lvl4
__libc_start_main(0x4004a0, 1, 0x7ffd6fb460e8, 0x400650 <unfinished ...>
setenv("FLAG", "656cf8aecb76113a4dece1688c61d0e7"..., 1)             = 0
+++ exited (status 0) +++

Didn’t expect this, very nice tho.

Thanks for reading!

Introduction to Angr

I always wanted to play around with a binary analysis framework but most of the time I was turned off by how difficult it was to install and use it. Just recently I’ve thought to give angr a try and now I want to share my experience with you! I will present you a two scripts that solve two challenges, if you wish to dig deeper and learn Angr then you should visit it’s official documentation.

angr is a python framework for analyzing binaries. It combines both static and dynamic symbolic (“concolic”) analysis, making it applicable to a variety of tasks.

For me the easiest way to install Angr and get it working on the first try was to download Kali linux, install it in VirtualBox (make sure you have at least 12 GB space for the disk) and execute: pip install angr
From here you can setup your Python dev environment in Kali as you please.

For the first challenge we have the following source code:

//written by bla
#include <stdio.h>
#include <string.h>
#include <unistd.h>



int main(int argc, char **argv)
{

        int count = atoi(argv[1]);
        int buf[10];

        if(count >= 10 ) 
                return 1;

        //printf("%lx\n", (size_t)(count * sizeof(int)));
        memcpy(buf, argv[2], count * sizeof(int));
        if(count == 0x574f4c46) {
        printf("WIN!\n");
                //execl("/bin/sh", "sh" ,NULL);
    } else
                printf("Not today son\n");


        return 0;
}



Challenge source: https://io.netgarage.org/ level-7

The goal is to find two arguments to give to the program in order to overflow buf into count and display WIN. We can attempt to solve this with trial and error, debugging, do some computation or we can make Angr solve it for us with the following Python script.

import angr
import claripy

def resolve_win(state):
    # if the bytes of "WIN" are found in stdout it returns true
    return  b"WIN" in state.posix.dumps(1)

if __name__ == '__main__':
    print("starting.")

    # Declare project, load the binary
    proj = angr.Project('./lab-13/0-tutorial/level07')

    # Create a 32-bit symbolic bitvector named "password"
    arg1 = claripy.BVS('sym_arg', 8 * 11)  # maximum 11 * 8 bits
    arg2 = claripy.BVS('sym_arg', 8 * 44)  # maximum 44 * 8 bits

    # We construct an entry_state passing the two arguments
    st = proj.factory.entry_state(args=['./level07', arg1, arg2])
    # he st.libc.max_strtol_len tweak tells the atoi/strtol symbolic representation to
    # resolve strings that are of at most 11 bytes length (the default is 10)
    st.libc.max_strtol_len = 11

    # Now we will create what in angr terms is called a simulation manager.
    # https://docs.angr.io/core-concepts/pathgroups
    pg = proj.factory.simgr(st)

    # This can be read as: explore looking for the path p for which the current state
    # p.state contains the string "WIN" in its standard output (p.state.posix.dumps(1),
    # where 1 is the file descriptor for stdout).
    pg.explore(find=resolve_win)

    print("solution found")
    s = pg.found[0]
    print(s.posix.dumps(1)) # dump stdout

    # Print and eval the fist argument
    print("Arg1: ", s.solver.eval(arg1, cast_to=bytes))
    # Print and eval the second argument
    print("Arg2: ", s.solver.eval(arg2, cast_to=bytes))


Running the script will give us the solution for this binary, if the binary would change slightly (the count) we can still run the script and get a solution.


The next challenge is easier, the binary is called multiple-styles and it can be downloaded from here: https://github.com/trailofbits/manticore/tree/master/examples/linux/binaries

By looking at it’s disassembly output:

multiple-styles disassembly

We can see that the program does the following things:

  1. Calls read which reads the ‘password’ from stdin into a buffer.
  2. Loads the string “myvnvsuowsxs}ynk” into a buffer.
  3. Loops through the buffer byte by byte adds 10 00400a27 add dword [rbp-0x54 {var_5c_2} {var_5c_1}], 0xa to it and compares it with the previously loaded string.
  4. If they match it will jump to 0x00400a6c and print “you got it!”

At this point we can google for online caesar cipher, paste the string that got loaded and decipher it with an offset of -10, but we’re going to let angr decipher the password for us.

import angr
import claripy

if __name__ == '__main__':
    print("starting")
    proj = angr.Project("./multiple-styles", auto_load_libs=False)

    # Create a 32-bit symbolic bitvector named "password"
    password = claripy.BVS('password', 20*8)

    # We construct a blank_state with the address of main and we pass password to stdin
    st = proj.factory.blank_state(addr=0x004009ae, stdin=password)

    # We create a simulation manager
    pg = proj.factory.simulation_manager(st)

    # We tell angr to look for 0x00400a6c which is the starting address of the green block
    # that prints "you got it!" while telling him to avoid the address 0x00400a40
    pg.explore(find=(0x00400a6c), avoid=(0x00400a40))

    print("solution found")
    # We grab the solution.
    s = pg.found[0]

    # We can print the contents of stdin - 0:
    print("Flag: ", s.posix.dumps(0))

    # We can also get the password from our symbolic bitvector
    print("Pass: ", s.solver.eval(password, cast_to=bytes))

While writing the scripts I’ve used angr version `8.19.7.25. Please consult Angr’s official documentation if you wish to learn more!

Thank you for reading! 😀

References:

Practical Binary Analysis | No Starch Press

I started reading this book in november and it took me about two weeks to finish it. You should be a bit comfortable Linux and programming if you plan to give it a try. Here are my thoughts about it.

What I like the most about this book is that it explains the subject in a straightforward and concise way! The author is a very knowledgeable security researcher and his work is state of the art! 

The book helped me fill a lot of gaps about how binary analysis is done, code obfuscation, linear disassemblers, recursive disassemblers, intermediate languages and lots of tools and libraries. It also helped me learn things that I didn’t know they exist, like: code injection, binary instrumentation, dynamic taint analysis and symbolic execution analysis.

Each chapter sets the foundation for the next one and at the end of each chapter you’re invited to solve the exercises which help you enforce and understand the information. The code and examples can be found on the book’s website.

There weren’t many books that I’ve found the appendixes very useful. This one is an exception! There’s one appendix that guides you on further reading and one that discusses the disassemblers and tools used in the book. Being a novice in the field I just love when I get recommendation from an expert like Dennis Andriesse.

I enjoyed the book a lot and I hope you will too! 🙂