Tuesday, 16 April 2019

Last Minute Write Up - WPI CTF 2019

Last Minute was a reversing challenge in WPI CTF 2019. This challenge was very interesting because it uses frame buffers to draw the actual flag if the environment and initial conditions are correct.

The provided binary is an ELF 64-bit binary.

The main functions performed by the binary are:

1. Uses the current timestamp to calculate a seed which is used to seed the random number generator.

The seed is calculated and used to seed the random number generator as shown below:

t = time()
seed = t/0x3c
srand(srand)

This means that the seed only changes every 60 seconds.

2. Opens the frame buffer device, /dev/fb0

3. Uses the ioctl, FBIOGET_VSCREENINFO (0x4600) to retrieve the fb_var_screeninfo structure. The bits_per_pixel field in this structure is set to 32.

4. Updates the structure using another ioctl, FBIOPUT_VSCREENINFO (0x4601).

5. Runs a loop which will update the accel_flags field in the vinfo structure.


6. Retrieves the vinfo structure.

7. Retrieves the finfo structure using the ioctl, FBIOGET_FSCREENINFO (0x4602).

This structure is defined as shown below:

struct fb_fix_screeninfo {
    char id[16];            /* identification string eg "TT Builtin" */
    unsigned long smem_start;    /* Start of frame buffer mem */
                    /* (physical address) */
    __u32 smem_len;            /* Length of frame buffer mem */
    __u32 type;            /* see FB_TYPE_*        */
    __u32 type_aux;            /* Interleave for interleaved Planes */
    __u32 visual;            /* see FB_VISUAL_*        */
    __u16 xpanstep;            /* zero if no hardware panning  */
    __u16 ypanstep;            /* zero if no hardware panning  */
    __u16 ywrapstep;        /* zero if no hardware ywrap    */
    __u32 line_length;        /* length of a line in bytes    */
    unsigned long mmio_start;    /* Start of Memory Mapped I/O   */
                    /* (physical address) */
    __u32 mmio_len;            /* Length of Memory Mapped I/O  */
    __u32 accel;            /* Indicate to driver which    */
                    /*  specific chip/card we have    */
    __u16 capabilities;        /* see FB_CAP_*            */
    __u16 reserved[2];        /* Reserved for future compatibility */
};

It fetches the line_length field of the finfo structure and prints it to stdout.

The line_length field needs to be equal to 0x1900 for it to continue the execution to display the flag as shown below:




if line_length <= 0x18ff:
    print "Ectomporh"
elif line_length != 0x1900:
    print "Endomorph"
else:
    print "Mesomorph"

    // perform the computations to display the flag

So, what is the line_length field?

In frame buffers, the line_length field represents the number of bytes in each line of the display. It is related to the X resolution field in the vinfo structure as shown below:

line_length = vinfo.x_res * (bits_per_pixel)/8

In our case, line_length should be equal to 0x1900 and bits_per_pixel is 32.

so, vinfo.x_res = 0x1900/4 = 1600

We can check the current resolution of the frame buffer device, /dev/fb0 using the command:

cat /sys/class/graphics/fb0/virtual_size

This displays both the resolution fields in (x,y) format.

If the X and Y resolution fields of the virtual_size are not equal to 1600 and 900 respectively, then the flag will not be displayed on the screen.

We can set the frame buffer resolution using the command:

fbset -fb /dev/fb0 1600 900 1600 900 32

Using the above command we have set all the fields of the frame buffer which are required to display the flag.

Now, lets explore the generation of seed using time(). The seed is very important here because it determines the random numbers generated by rand(). And these random numbers are useful because they are used to calculate the offsets at which the pixels will be drawn on the frame buffer.

The data to to be drawn to the frame buffer is stored in the .data section in the array called baboof[] as shown below:


Based on the challenge description, we find the timestamp when the CTF ends and it is: 1555286400.

Since we know that the seed changes every 60 seconds, we can generate all the possible seeds for the last 1 hour of the CTF.

This gives us the values:

['0x5cb3bb70', '0x5cb3bbac', '0x5cb3bbe8', '0x5cb3bc24', '0x5cb3bc60', '0x5cb3bc9c', '0x5cb3bcd8', '0x5cb3bd14', '0x5cb3bd50', '0x5cb3bd8c', '0x5cb3bdc8', '0x5cb3be04', '0x5cb3be40', '0x5cb3be7c', '0x5cb3beb8', '0x5cb3bef4', '0x5cb3bf30', '0x5cb3bf6c', '0x5cb3bfa8', '0x5cb3bfe4', '0x5cb3c020', '0x5cb3c05c', '0x5cb3c098', '0x5cb3c0d4', '0x5cb3c110', '0x5cb3c14c', '0x5cb3c188', '0x5cb3c1c4', '0x5cb3c200', '0x5cb3c23c', '0x5cb3c278', '0x5cb3c2b4', '0x5cb3c2f0', '0x5cb3c32c', '0x5cb3c368', '0x5cb3c3a4', '0x5cb3c3e0', '0x5cb3c41c', '0x5cb3c458', '0x5cb3c494', '0x5cb3c4d0', '0x5cb3c50c', '0x5cb3c548', '0x5cb3c584', '0x5cb3c5c0', '0x5cb3c5fc', '0x5cb3c638', '0x5cb3c674', '0x5cb3c6b0', '0x5cb3c6ec', '0x5cb3c728', '0x5cb3c764', '0x5cb3c7a0', '0x5cb3c7dc', '0x5cb3c818', '0x5cb3c854', '0x5cb3c890', '0x5cb3c8cc', '0x5cb3c908', '0x5cb3c944', '0x5cb3c980']

Now, we need to bruteforce the above timestamps and check the frame display output.

Since the binary is dynamically linked, we can leverage the LD_PRELOAD environment variable so that time() function always returns a predefined value for the binary being executed.

This can be done by writing a short function such as:

int time()
{
    return 0x5cb3c944;
}

Then compile it as a shared library:

gcc -shared -fPIC hook_time.c -o hook_time.so

Now, we can run the binary as shown below and confirm that the timestamp returned by time() is indeed how we configured it:

sudo LD_PRELOAD=$PWD/hook_time.so ltrace -o trace1.txt ./lm

This will run the binary using ltrace to log the API calls and LD_PRELOAD will be used to load our shared library, hook_time.so

As shown below, time() returns our configured timestamp:


Now, we can run the binary and it will display the flag as shown below:


flag: WPI{MINUTE_TO_WIN_IT}

c0d3inj3cT

No comments:

Post a Comment