CTF, ELF binaries and magic bytes

Just a short blog on a fun CTF challenge I did related to ELF binaries.


I had a spare weekend so I particiapated in a CTF competition conducted by my college’s CS club. In the event, one challenge really stood apart and hence I’m writing about it.

The challenge #

On the website, a ‘PNG’ file was provided initially, called test.png . This file was the only clue to the flag.

My approach #

Given the fact that only a file was provided as a clue, and that it wouldn’t open using an image viewer. It was obvious what I needed to do first, and that was the classic file command.

$ file test.png
test.png: data

That is not how a PNG file looks like, so I went ahead with cat.

$ cat test.png
   ���-�==HP�-�==����DDP�td8 8 8 <<Q�tdR�td�-�==/lib64/ld-linux-x86-64.so.2GNU Z��_���09�DfWͫ�w�GNU��e�m? [ j"printf__cxa_finalize__libc_start_mainlibc.so.6GLIBC_2.2.5_ITM_deregisterTMCloneTable__gmon_start___ITM_registerTMCloneTable)ui   3�0��((@�?�?�?�?�?@H�H��/H��t��H���5�/�%�/@�%�/h������%�/f�1�I��^3H�=��f/�DH�=�/H��/H9�tH�>/H��t �����H�=y/H�5r/H)�H��H��?H��H�H��tH�/H����fD���=9/u/UH�=�.H��t
                                               $X��� FJ
X                                                      �?;*3$"DP��\-���]A�C
D|x���]B�I�E �E(�D0�H8�G@j8A0A(B BB�����0�)
 @P�    ������op���o���o\���o�=6(@GCC: (Debian 10.2.1-6) 10.2.1 20210110��0�p   �

@P 8 x �===�?@ @0@��
                      ��!�70@C�=j0v�=������|!����=��=��=�8 �@�
                                                               f  @,0@
3Fd @q �(@� ��]�8@jP+�0@�5]�0@� �"crtstuff.cderegister_tm_clones__do_global_dtors_auxcompleted.0__do_global_dtors_aux_fini_array_entryframe_dummy__frame_dummy_init_array_entrytest.c__FRAME_END____init_array_end_DYNAMIC__init_array_start__GNU_EH_FRAME_HDR_GLOBAL_OFFSET_TABLE___libc_csu_fini_ITM_deregisterTMCloneTable_edataprintf@GLIBC_2.2.5__libc_start_main@GLIBC_2.2.5__data_start__gmon_start____dso_handle_IO_stdin_used__libc_csu_init__bss_startmain__TMC_END___ITM_registerTMCloneTable__cxa_finalize@GLIBC_2.2.5.symtab.strtab.shstrtab.interp.note.gnu.build-id.note.ABI-tag.gnu.hash.dynsym.dynstr.gnu.version.gnu.version_r.rela.dyn.rela.plt.init.plt.got.text.fini.rodata.eh_frame_hdr.eh_frame.init_array.fini_array.dynamic.got.plt.data.bss.comment�#��$6�� D��No
                                                                                                                                                      0V���^���o\\k���oppz���BP��   �PP��        �  �8 8 <�x ������=�-��?��@� @ 0@0�000'X0-      X6�V8%

The things which jump out at me from the above output are the following:

  • (@GCC: (Debian 10.2.1-6) 10.2.1
  • =/lib64/ld-linux-x86-64.so.2

They are something you normally won’t find in a PNG file. Instead, they’re found in ELF binaries. To verify this, I compared the output of cat with the ls binary. The output is a bit verbose so I’ll skip it here.

Now, we know that test.png is actually a binary instead of an image, we can go ahead and execute it.

$ chmod +x test.png
$ ./test.png
zsh: exec format error: ./test.png

Hmm, guess it won’t be that easy. Since file was failing to detect test.png as an ELF earlier, the error probably lies somewhere in the header bytes of the ELF binary.

$ xxd test.png | head -n 1
00000000: 8950 4e47 0201 0100 0000 0000 0000 0000  .PNG............
$ xxd /bin/ls | head -n 1
00000000: 7f45 4c46 0201 0100 0000 0000 0000 0000  .ELF............

We can see that the first 4 bytes in test.png have been tampered with. Therefore, we can fix the header bytes by correctly indicating that test.png is actually and ELF binary.

I used vim alongwith xxd for editing the test.png in hex.

  1. Open test.png as a binary: vi -b test.png

  2. Show the hexdump of the buffer: :%!xxd

  3. Fix the magic bytes

  4. Reverse the hexdump into the buffer: :%!xxd -r

  5. Save contents of buffer as test: :w test

After fixing the ELF magic bytes, we can see that file properly detects the file type for test and that we can execute it to get the flag.

$ file test
test: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=205a03bd925f839ef43039d5446657cdabca7785, for GNU/Linux 3.2.0, not stripped
$ chmod +x test
$ ./test

Conclusion #

This was a fun exercise and I enjoyed dipping my finger into the guts of binaries.


Leave a reply