Des agents ont réussi à exfiltrer un fichier en utilisant la LED du disque dur durant une copie de disque. Ils nous ont fourni l’image de la capture. Retrouvez le flag.

We have a PNG file that contains a picture of the LED of a hard drive during a disk copy, we have to find the flag.

Here’s the original image:

Infiltrate

TL;DR

  • Extract the binary from the image (convert black pixels to 0 and white pixels to 1)
  • Convert the binary to ELF (remove the first bytes to create a valid ELF)
  • Reverse the program to see it compares the input with a SHA1 hash
  • Recreate the SHA1

From Image to Binary

The image only has black or white pixels, it seems clear that we have to convert the pixels to binary.

Black pixel means the led is turned off, so the bit is 0. White pixel means the led is turned on, the bit is 1.

Let’s write a small python script to convert our pixels to a binary file:

#!/usr/bin/python3.6
# coding: utf-8
from PIL import Image

if __name__ == '__main__':
    img = Image.open("infiltrate.png")
    pixels = img.load()

    flag = ""

    for height in range(img.size[1]):
        for width in range(img.size[0]):
            if pixels[width, height] == (0, 0, 0):
                flag += "0"
            else:
                flag += "1"

    with open('program.bin', 'wb') as target:
        target.write(int(flag, 2).to_bytes(len(flag) // 8, byteorder='big'))

Look! An ELF

Ok, so we have a binary file, let’s check the first bytes to see what we can do.

magnussen@funcMyLife:~/infiltrate$ xxd program.bin | head
00000000: 0451 8191 5550 89d5 c0e4 4d3d 6ae7 1ded  .Q..UP....M=j...
00000010: 7f45 4c46 0201 0100 0000 0000 0000 0000  .ELF............
00000020: 0300 3e00 0100 0000 9007 0000 0000 0000  ..>.............
00000030: 4000 0000 0000 0000 881a 0000 0000 0000  @...............
00000040: 0000 0000 4000 3800 0900 4000 1d00 1c00  ....@.8...@.....
00000050: 0600 0000 0400 0000 4000 0000 0000 0000  ........@.......
00000060: 4000 0000 0000 0000 4000 0000 0000 0000  @.......@.......
00000070: f801 0000 0000 0000 f801 0000 0000 0000  ................
00000080: 0800 0000 0000 0000 0300 0000 0400 0000  ................
00000090: 3802 0000 0000 0000 3802 0000 0000 0000  8.......8.......

It’s an ELF file, but there’s some garbage before the magic bytes, an ELF starts with 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00. Let’s remove the garbage and see if the file is recognized as ELF.

magnussen@funcMyLife:~/infiltrate$ dd if=program.bin of=program bs=1 skip=16
13109+0 records in
13109+0 records out
13109 bytes (13 kB, 13 KiB) copied, 0,0762559 s, 172 kB/s
magnussen@funcMyLife:~/infiltrate$ xxd program | head
00000000: 7f45 4c46 0201 0100 0000 0000 0000 0000  .ELF............
00000010: 0300 3e00 0100 0000 9007 0000 0000 0000  ..>.............
00000020: 4000 0000 0000 0000 881a 0000 0000 0000  @...............
00000030: 0000 0000 4000 3800 0900 4000 1d00 1c00  ....@.8...@.....
00000040: 0600 0000 0400 0000 4000 0000 0000 0000  ........@.......
00000050: 4000 0000 0000 0000 4000 0000 0000 0000  @.......@.......
00000060: f801 0000 0000 0000 f801 0000 0000 0000  ................
00000070: 0800 0000 0000 0000 0300 0000 0400 0000  ................
00000080: 3802 0000 0000 0000 3802 0000 0000 0000  8.......8.......
00000090: 3802 0000 0000 0000 1c00 0000 0000 0000  8...............
magnussen@funcMyLife:~/infiltrate$ readelf -h program
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
  Clawas an                           ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              DYN (Shared object file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x790
  Start of program headers:          64 (bytes into file)
  Start of section headers:          6792 (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

Nice! We have a valid ELF! Let’s reverse it!

It’s reverse time!

magnussen@funcMyLife:~/infiltrate$ radare2 -d program
Process with PID 23417 started...
= attach 23417 23417
bin.baddr 0x55c50a6d5000
Using 0x55c50a6d5000
asm.bits 64
[0x7ff5ef2d0090]> aaa
[ WARNING : block size exceeding max block size at 0x55c50a8d5fe0
[+] Try changing it with e anal.bb.maxsize
[x] Analyze all flags starting with sym. and entry0 (aa)
[x] Analyze len bytes of instructions for references (aar)
[x] Analyze function calls (aac)
[x] Use -AA or aaaa to perform additional experimental analysis.
[x] Constructing a function name for fcn.* and sym.func.* functions (aan)
= attach 23417 23417
23417
[0x7ff5ef2d0090]> s main
[0x55c50a6d59f5]> pdf
            ;-- main:
/ (fcn) sym.main 108
|   sym.main ();
|           ; var int local_70h @ rbp-0x70
|           ; var int local_8h @ rbp-0x8
|              ; DATA XREF from 0x55c50a6d57ad (entry0)
|           0x55c50a6d59f5      55             push rbp
|           0x55c50a6d59f6      4889e5         mov rbp, rsp
|           0x55c50a6d59f9      4883ec70       sub rsp, 0x70           ; 'p'
|           0x55c50a6d59fd      64488b042528.  mov rax, qword fs:[0x28] ; [0x28:8]=-1 ; '(' ; 40
|           0x55c50a6d5a06      488945f8       mov qword [local_8h], rax
|           0x55c50a6d5a0a      31c0           xor eax, eax
|           0x55c50a6d5a0c      488d3d0e0100.  lea rdi, qword str.Hello__Entrez_la_cl ; 0x55c50a6d5b21 ; "Hello! Entrez la cl\u00e9 "
|           0x55c50a6d5a13      e808fdffff     call sym.imp.puts       ; int puts(const char *s)
|           0x55c50a6d5a18      488b15f10520.  mov rdx, qword [obj.stdin] ; loc.stdin ; [0x55c50a8d6010:8]=0
|           0x55c50a6d5a1f      488d4590       lea rax, qword [local_70h]
|           0x55c50a6d5a23      be07000000     mov esi, 7
|           0x55c50a6d5a28      4889c7         mov rdi, rax
|           0x55c50a6d5a2b      e810fdffff     call sym.imp.fgets      ; char *fgets(char *s, int size, FILE *stream)
|           0x55c50a6d5a30      488d4590       lea rax, qword [local_70h]
|           0x55c50a6d5a34      4889c7         mov rdi, rax
|           0x55c50a6d5a37      e85efeffff     call sym.doit
|           0x55c50a6d5a3c      bf0a000000     mov edi, 0xa
|           0x55c50a6d5a41      e8eafcffff     call sym.imp.putchar    ; int putchar(int c)
|           0x55c50a6d5a46      b800000000     mov eax, 0
|           0x55c50a6d5a4b      488b4df8       mov rcx, qword [local_8h]
|           0x55c50a6d5a4f      6448330c2528.  xor rcx, qword fs:[0x28]
|       ,=< 0x55c50a6d5a58      7405           je 0x55c50a6d5a5f
|       |   0x55c50a6d5a5a      e811fdffff     call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
|       `-> 0x55c50a6d5a5f      c9             leave
\           0x55c50a6d5a60      c3             ret

The program prints a message when it starts: “Hello! Entrez la clé”. It takes an input of length 7 from stdin, and pass this string to a function called doit.

Let’s see what that function does.

[0x55c50a6d59f5]> s sym.doit
[0x55c50a6d589a]> pdf
/ (fcn) sym.doit 347
|   sym.doit ();
|           ; var int local_38h @ rbp-0x38
|           ; var int local_28h @ rbp-0x28
|           ; var int local_24h @ rbp-0x24
|           ; var int local_20h @ rbp-0x20
|           ; var int local_1fh @ rbp-0x1f
|           ; var int local_1eh @ rbp-0x1e
|           ; var int local_1dh @ rbp-0x1d
|           ; var int local_1ch @ rbp-0x1c
|           ; var int local_1bh @ rbp-0x1b
|           ; var int local_1ah @ rbp-0x1a
|           ; var int local_19h @ rbp-0x19
|           ; var int local_18h @ rbp-0x18
|           ; var int local_17h @ rbp-0x17
|           ; var int local_16h @ rbp-0x16
|           ; var int local_15h @ rbp-0x15
|           ; var int local_14h @ rbp-0x14
|           ; var int local_13h @ rbp-0x13
|           ; var int local_12h @ rbp-0x12
|           ; var int local_11h @ rbp-0x11
|           ; var int local_10h @ rbp-0x10
|           ; var int local_fh @ rbp-0xf
|           ; var int local_eh @ rbp-0xe
|           ; var int local_dh @ rbp-0xd
|           ; var int local_8h @ rbp-0x8
|              ; CALL XREF from 0x55c50a6d5a37 (sym.main)
|           0x55c50a6d589a      55             push rbp
|           0x55c50a6d589b      4889e5         mov rbp, rsp
|           0x55c50a6d589e      4883ec40       sub rsp, 0x40           ; '@'
|           0x55c50a6d58a2      48897dc8       mov qword [local_38h], rdi
|           0x55c50a6d58a6      64488b042528.  mov rax, qword fs:[0x28] ; [0x28:8]=-1 ; '(' ; 40
|           0x55c50a6d58af      488945f8       mov qword [local_8h], rax
|           0x55c50a6d58b3      31c0           xor eax, eax
|           0x55c50a6d58b5      488b45c8       mov rax, qword [local_38h]
|           0x55c50a6d58b9      4889c7         mov rdi, rax
|           0x55c50a6d58bc      e88ffeffff     call sym.imp.strlen     ; size_t strlen(const char *s)
|           0x55c50a6d58c1      4889c1         mov rcx, rax
|           0x55c50a6d58c4      488d55e0       lea rdx, qword [local_20h]
|           0x55c50a6d58c8      488b45c8       mov rax, qword [local_38h]
|           0x55c50a6d58cc      4889ce         mov rsi, rcx
|           0x55c50a6d58cf      4889c7         mov rdi, rax
|           0x55c50a6d58d2      e889feffff     call sym.imp.SHA1
|           0x55c50a6d58d7      c745dc000000.  mov dword [local_24h], 0
|           0x55c50a6d58de      c745d8000000.  mov dword [local_28h], 0
|       ,=< 0x55c50a6d58e5      e9be000000     jmp 0x55c50a6d59a8
|      .--> 0x55c50a6d58ea      0fb645e0       movzx eax, byte [local_20h]
|      :|   0x55c50a6d58ee      3c58           cmp al, 0x58            ; 'X' ; 88
|     ,===< 0x55c50a6d58f0      0f85aa000000   jne 0x55c50a6d59a0
|     |:|   0x55c50a6d58f6      0fb645e1       movzx eax, byte [local_1fh]
|     |:|   0x55c50a6d58fa      3c23           cmp al, 0x23            ; '#' ; 35
|    ,====< 0x55c50a6d58fc      0f859e000000   jne 0x55c50a6d59a0
|    ||:|   0x55c50a6d5902      0fb645ea       movzx eax, byte [local_16h]
|    ||:|   0x55c50a6d5906      3ca3           cmp al, 0xa3            ; 163
|   ,=====< 0x55c50a6d5908      0f8592000000   jne 0x55c50a6d59a0
|   |||:|   0x55c50a6d590e      0fb645e2       movzx eax, byte [local_1eh]
|   |||:|   0x55c50a6d5912      3cdb           cmp al, 0xdb            ; 219
|  ,======< 0x55c50a6d5914      0f8586000000   jne 0x55c50a6d59a0
|  ||||:|   0x55c50a6d591a      0fb645e3       movzx eax, byte [local_1dh]
|  ||||:|   0x55c50a6d591e      3c97           cmp al, 0x97            ; 151
| ,=======< 0x55c50a6d5920      757e           jne 0x55c50a6d59a0
| |||||:|   0x55c50a6d5922      0fb645e6       movzx eax, byte [local_1ah]
| |||||:|   0x55c50a6d5926      3cc4           cmp al, 0xc4            ; 196
| ========< 0x55c50a6d5928      7576           jne 0x55c50a6d59a0
| |||||:|   0x55c50a6d592a      0fb645e4       movzx eax, byte [local_1ch]
| |||||:|   0x55c50a6d592e      3c68           cmp al, 0x68            ; 'h' ; 104
| ========< 0x55c50a6d5930      756e           jne 0x55c50a6d59a0
| |||||:|   0x55c50a6d5932      0fb645e5       movzx eax, byte [local_1bh]
| |||||:|   0x55c50a6d5936      3c01           cmp al, 1               ; 1
| ========< 0x55c50a6d5938      7566           jne 0x55c50a6d59a0
| |||||:|   0x55c50a6d593a      0fb645f2       movzx eax, byte [local_eh]
| |||||:|   0x55c50a6d593e      3c26           cmp al, 0x26            ; '&' ; 38
| ========< 0x55c50a6d5940      755e           jne 0x55c50a6d59a0
| |||||:|   0x55c50a6d5942      0fb645e7       movzx eax, byte [local_19h]
| |||||:|   0x55c50a6d5946      3ca0           cmp al, 0xa0            ; 160
| ========< 0x55c50a6d5948      7556           jne 0x55c50a6d59a0
| |||||:|   0x55c50a6d594a      0fb645e8       movzx eax, byte [local_18h]
| |||||:|   0x55c50a6d594e      3ce2           cmp al, 0xe2            ; 226
| ========< 0x55c50a6d5950      754e           jne 0x55c50a6d59a0
| |||||:|   0x55c50a6d5952      0fb645e9       movzx eax, byte [local_17h]
| |||||:|   0x55c50a6d5956      3cd7           cmp al, 0xd7            ; 215
| ========< 0x55c50a6d5958      7546           jne 0x55c50a6d59a0
| |||||:|   0x55c50a6d595a      0fb645f3       movzx eax, byte [local_dh]
| |||||:|   0x55c50a6d595e      3c12           cmp al, 0x12            ; 18
| ========< 0x55c50a6d5960      753e           jne 0x55c50a6d59a0
| |||||:|   0x55c50a6d5962      0fb645eb       movzx eax, byte [local_15h]
| |||||:|   0x55c50a6d5966      3c30           cmp al, 0x30            ; '0' ; 48
| ========< 0x55c50a6d5968      7536           jne 0x55c50a6d59a0
| |||||:|   0x55c50a6d596a      0fb645ec       movzx eax, byte [local_14h]
| |||||:|   0x55c50a6d596e      3cb2           cmp al, 0xb2            ; 178
| ========< 0x55c50a6d5970      752e           jne 0x55c50a6d59a0
| |||||:|   0x55c50a6d5972      0fb645ed       movzx eax, byte [local_13h]
| |||||:|   0x55c50a6d5976      3cbb           cmp al, 0xbb            ; 187
| ========< 0x55c50a6d5978      7526           jne 0x55c50a6d59a0
| |||||:|   0x55c50a6d597a      0fb645ef       movzx eax, byte [local_11h]
| |||||:|   0x55c50a6d597e      3cfe           cmp al, 0xfe            ; section_end..shstrtab
| ========< 0x55c50a6d5980      751e           jne 0x55c50a6d59a0
| |||||:|   0x55c50a6d5982      0fb645f0       movzx eax, byte [local_10h]
| |||||:|   0x55c50a6d5986      3c27           cmp al, 0x27            ; ''' ; 39
| ========< 0x55c50a6d5988      7516           jne 0x55c50a6d59a0
| |||||:|   0x55c50a6d598a      0fb645ee       movzx eax, byte [local_12h]
| |||||:|   0x55c50a6d598e      3c82           cmp al, 0x82            ; 130
| ========< 0x55c50a6d5990      750e           jne 0x55c50a6d59a0
| |||||:|   0x55c50a6d5992      0fb645f1       movzx eax, byte [local_fh]
| |||||:|   0x55c50a6d5996      3ccc           cmp al, 0xcc            ; 204
| ========< 0x55c50a6d5998      7506           jne 0x55c50a6d59a0
| |||||:|   0x55c50a6d599a      8345dc01       add dword [local_24h], 1
| ========< 0x55c50a6d599e      eb04           jmp 0x55c50a6d59a4
| `````---> 0x55c50a6d59a0      836ddc01       sub dword [local_24h], 1
|      :|      ; JMP XREF from 0x55c50a6d599e (sym.doit)
| --------> 0x55c50a6d59a4      8345d801       add dword [local_28h], 1
|      :|      ; JMP XREF from 0x55c50a6d58e5 (sym.doit)
|      :`-> 0x55c50a6d59a8      837dd813       cmp dword [local_28h], 0x13 ; [0x13:4]=-1 ; 19
|      `==< 0x55c50a6d59ac      0f8e38ffffff   jle 0x55c50a6d58ea
|           0x55c50a6d59b2      837ddc14       cmp dword [local_24h], 0x14 ; [0x14:4]=-1 ; 20
|       ,=< 0x55c50a6d59b6      751a           jne 0x55c50a6d59d2
|       |   0x55c50a6d59b8      488b45c8       mov rax, qword [local_38h]
|       |   0x55c50a6d59bc      4889c6         mov rsi, rax
|       |   0x55c50a6d59bf      488d3d2e0100.  lea rdi, qword str.Bravo__le_flag_est_FCSC__s ; 0x55c50a6d5af4 ; "Bravo, le flag est FCSC{%s}\n"
|       |   0x55c50a6d59c6      b800000000     mov eax, 0
|       |   0x55c50a6d59cb      e840fdffff     call sym.imp.printf     ; int printf(const char *format)
|      ,==< 0x55c50a6d59d0      eb0c           jmp 0x55c50a6d59de
|      |`-> 0x55c50a6d59d2      488d3d380100.  lea rdi, qword str.Mauvaise_cl ; 0x55c50a6d5b11 ; "Mauvaise cl\u00e9 !"
|      |    0x55c50a6d59d9      e842fdffff     call sym.imp.puts       ; int puts(const char *s)
|      |       ; JMP XREF from 0x55c50a6d59d0 (sym.doit)
|      `--> 0x55c50a6d59de      90             nop
|           0x55c50a6d59df      488b45f8       mov rax, qword [local_8h]
|           0x55c50a6d59e3      644833042528.  xor rax, qword fs:[0x28]
|       ,=< 0x55c50a6d59ec      7405           je 0x55c50a6d59f3
|       |   0x55c50a6d59ee      e87dfdffff     call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
|       `-> 0x55c50a6d59f3      c9             leave
\           0x55c50a6d59f4      c3             ret

The program calls a function SHA1 with our input as parameter and then loop for each character of our string and compares it with some static values.

At the end, if the string we gave is the same as the local one, the program prints “Bravo le flag est FCSC”, otherwise it prints “Mauvaise clé”.

So the program is pretty simple, we gave it a 6 chars string (the 7th char is the end of the string \0). Then it creates the SHA1 hash of our string and compares this hash with the right one. If the two hashes are the same it prints the flag, otherwise it prints “Mauvaise clé”.

All we have to do is collect all the value of the right hash (cmp instructions) and recreate the hash in the correct order (thanks to the local_XX).

Let’s take an example. The first compare instruction is:

movzx eax, byte [local_20h]
cmp al, 0x58      
jne 0x55c50a6d59a0;[ge]

It takes the hex value at the position local_20h, so it’s the last char of our string. Then it compares this value with the right one 0x58. So the last char of the correct hash is 0x58.

Here’s the python script to find the correct SHA1:

flag = [0x58, 0x23, 0xdb, 0x97, 0x68, 1, 0xc4, 0xa0, 0xe2, 0xd7, 0xa3, 0x30, 0xb2, 0xbb, 0x82, 0xfe, 0x27, 0xcc, 0x26, 0x12]
order = [0x20, 0x1f, 0x1e, 0x1d, 0x1c, 0x1b, 0x1a, 0x19, 0x18, 0x17, 0x16, 0x15, 0x14, 0x13, 0x11, 0x12, 0x10, 0xf, 0xe, 0xd]

password = ""

for value in flag:
    password += str.format('0x{:02x}', value)[2:]

print(password)

The first array is the values it compares, the second one is their position in the correct SHA1 (local_XX).

We find the following hash:

5823db976801c4a0e2d7a330b2bb82fe27cc2612

Let’s crack that hash to find the correct string! (I use Crackstation for this kind of things)

We find the string: 401445.

Let’s try it!

magnussen@funcMyLife:~/infiltrate$ ./program
Hello! Entrez la clé
401445
Bravo, le flag est FCSC{401445}

This was one of my favorite challenges! Not very difficult but so fun!

Thanks to the FCSC team, this was an awesome CTF, I’ve learned a lot and had a lot of fun. Congratulation for the organization, see you next year!