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:
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!