t35h@ctf:~$

Sogeti Qualif - reverse - Be3rP4ck

RE: Be3rP4ck [ 495 points ] (Author: AK)

This is a 64-bit ELF file.

$ file Be3rP4ck 
Be3rP4ck: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=da32e0c02e1bf1f0afd1c005b3d4fb86b23840b8, not stripped

Misleading flag

After launching the program, it asks us to enter the flag, and if it’s wrong, nothing would happen:

$ ./Be3rP4ck 
Flag: test

Let’s analyze the main function.

The user input is stored in [rbp+string]. The program performs several operations on our input: a xor with 0x87 and 4 not. Finally it is compared with the value stored at unk_401210.

So, to recover the flag, I just xored the data in unk_401210 with 0x87.

unk_401210 = [0xc9, 0xb7, 0xf3, 0xd8, 0xd3, 0xef, 0xb4, 0xd8, 0xe1, 0xeb, 0xb3, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0]
print(''.join([chr(x^0x87) for x in unk_401210]))

And I got N0t_Th3_fl4GGGGG.

$ ./Be3rP4ck
Flag:N0t_Th3_fl4GGGGG
Well... played?

Unpacking the program

I noticed that if I pass an argument to the program, it will print another message that corresponds to the real program, which is packed:

$ ./Be3rP4ck test
-- Real program --
Give me the real flag

Indeed, the program doesn’t start on main but on another function called main_0. This function checks if argc is equal to 2, if not, it will call main.

.text:0000000000400C45 ; int __cdecl main_0(int, char **, char **)
.text:0000000000400C45 ; __unwind {
.text:0000000000400C45                 push    rbp
.text:0000000000400C46                 mov     rbp, rsp
.text:0000000000400C49                 sub     rsp, 10h
.text:0000000000400C4D                 mov     [rbp-4], edi
.text:0000000000400C50                 mov     [rbp-10h], rsi
.text:0000000000400C54                 cmp     dword ptr [rbp-4], 2     ; [rbp-4] contains argc
.text:0000000000400C58                 jz      short loc_400C7B         ; jump if argc = 2
.text:0000000000400C5A                 call    main
...

If an argument is given, we arrive on the function fetch_data. This is the function used to unpack the hidden program.

...
.text:0000000000400EB0 loc_400EB0:
.text:0000000000400EB0                 add     [r8+48h], bl
.text:0000000000400EB4                 mov     eax, [rbp-40h]  ; [rbp-40h] contains rsi, ie argv
.text:0000000000400EB7                 lea     rsi, aRb        ; "rb"
.text:0000000000400EBE                 mov     rdi, rax        ; argv[0]: "Be3rP4ck"
.text:0000000000400EC1                 call    _fopen
.text:0000000000400EC6                 mov     [rbp-18h], rax
.text:0000000000400ECA                 cmp     qword ptr [rbp-18h], 0
.text:0000000000400ECF                 jnz     short loc_400EE7
.text:0000000000400ED1                 lea     rdi, aFuck      ; "fuck"
.text:0000000000400ED8                 call    _perror
.text:0000000000400EDD                 mov     eax, 0FFFFFFFFh
.text:0000000000400EE2                 jmp     locret_400F93
.text:0000000000400EE7 ; ---------------------------------------------------------------------------
.text:0000000000400EE7
.text:0000000000400EE7 loc_400EE7:
.text:0000000000400EE7                 mov     rax, [rbp-18h]
.text:0000000000400EEB                 mov     edx, 0
.text:0000000000400EF0                 mov     esi, 1
.text:0000000000400EF5                 mov     rdi, rax
.text:0000000000400EF8                 call    _fseek
.text:0000000000400EFD                 mov     rdx, [rbp-18h]
.text:0000000000400F01                 lea     rax, [rbp-2Ah]
.text:0000000000400F05                 mov     rcx, rdx
.text:0000000000400F08                 mov     edx, 3
.text:0000000000400F0D                 mov     esi, 1
.text:0000000000400F12                 mov     rdi, rax
.text:0000000000400F15                 call    _fread
.text:0000000000400F1A                 mov     eax, 1
.text:0000000000400F1F                 push    rax
.text:0000000000400F20                 xor     eax, eax
.text:0000000000400F22                 jz      short near ptr loc_400F33+2
...
.text:0000000000400F33 loc_400F33:
.text:0000000000400F33                 add     [r8+48h], bl
.text:0000000000400F37                 mov     eax, [rbp-18h]
.text:0000000000400F3A                 mov     rdi, rax
.text:0000000000400F3D                 call    _fclose
.text:0000000000400F42                 mov     rsi, [rbp-20h]   ; 0x13455C
.text:0000000000400F46                 lea     rdx, [rbp-2Ah]
.text:0000000000400F4A                 mov     rax, [rbp-8]
.text:0000000000400F4E                 mov     ecx, 3
.text:0000000000400F53                 mov     rdi, rax
.text:0000000000400F56                 call    mb_xor
.text:0000000000400F5B                 mov     eax, 1
...

I put a breakpoint at 0x400F56 call mb_xor and I noticed that rdi contains the chars “ELF” after executing this instruction.

So I copied the 0x13455c bytes of data (size in rsi) stored at the address contained in rdi and I pasted it in a hexadecimal editor called HxD.

After saving it, I got a new ELF which corresponds to the real program.

$ file real_program 
real_program: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, Go BuildID=b5b5bb9544aeca5d8e3dd360bad2dfb3f9d87aacA, not stripped

PS: The extracted program can be executed with 2 arguments.

Analysing the real program

It’s a program written in Go. The main.main function is called runtime_text here.



After examining this function, I noticed that if I enter “almost_it”, it would print another wrong flag…

.text:000000000040126B loc_40126B:
.text:000000000040126B                 mov     [rsp+0B8h+var_B8], rcx
.text:000000000040126F                 mov     [rsp+0B8h+var_B0], rax
.text:0000000000401274                 lea     rax, unk_4ADCBF          ; "almost_it"
.text:000000000040127B                 mov     [rsp+0B8h+var_A8], rax
.text:0000000000401280                 mov     qword ptr [rsp+0B8h+var_A0], 9
.text:0000000000401289                 call    runtime_eqstring
.text:000000000040128E                 movzx   eax, byte ptr [rsp+0B8h+var_A0+8]
.text:0000000000401293                 test    al, al
.text:0000000000401295                 jnz     short loc_40129C         ; print a wrong flag
$ ./Be3rP4ck almost_it
-- Real program --
flag is: Y0u_A1nt_Th3r3_Y3t

Then, the program does a custom hash on our input using the function main_sogehash. And its final value is compared to 1f4e509605c9f4bf22f4bf22a5c9fe23bbfee5dd22ffdde5fb22aafedcdd22f5f1d6f0a4a5a589.

.text:000000000040118E                 call    main_sogehash
.text:0000000000401193                 mov     rax, [rsp+0B8h+var_A8]
.text:0000000000401198                 mov     rcx, qword ptr [rsp+0B8h+var_A0]
.text:000000000040119D                 cmp     rcx, 4Eh                     ; length
.text:00000000004011A1                 jz      short loc_4011B3
...
.text:00000000004011B3 loc_4011B3:
.text:00000000004011B3                 mov     [rsp+0B8h+var_B8], rax
.text:00000000004011B7                 mov     [rsp+0B8h+var_B0], rcx
.text:00000000004011BC                 lea     rax, unk_4B3F03              ; "1f4e509605c9f4bf22f4bf22a5c9fe23bbfee5dd22ffdde5fb22aafedcdd22f5f1d6f0a4a5a589"
.text:00000000004011C3                 mov     [rsp+0B8h+var_A8], rax
.text:00000000004011C8                 mov     qword ptr [rsp+0B8h+var_A0], 4Eh
.text:00000000004011D1                 call    runtime_eqstring

Cracking the hash

After analyzing the main_sogehash, I wrote this little python script to bruteforce the hash.

target = '1f4e509605c9f4bf22f4bf22a5c9fe23bbfee5dd22ffdde5fb22aafedcdd22f5f1d6f0a4a5a589'
alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!_{}0123456789'
flag = ''
d = 0
for i in range(0, 78, 2):
    for s in alphabet:
        x = d^ord(s)^4*ord(s)
        if hex(x)[-2:] == target[i:i+2]:
            flag += s
            d = x >> 8
            break
print(flag)
$ ./Be3rP4ck SCE{Th1s_1s_th3_r3al_fl4g_w3ll_d0ne\!\!\!}
-- Real program --
Give me the real flag
Congratulations, you can use this flag to validate :)

Finally, the flag is:

SCE{Th1s_1s_th3_r3al_fl4g_w3ll_d0ne!!!}