HTB Sherlocks: Lockpick 2

The Lockpick 2 challenge is part of the HackTheBox Sherlocks defensive security scenarios. This challenge is already retired, and available for VIP members.

The challenge description mentions that the organization was hit with a ransomware, with it managing to encrypt a large amount of files. The organization has a stance on not paying the ransom, thus they need to have the captured sample analyzed to determine if it's possible to decrypt the files.

This write-up uses static analysis of the files, and Radare2 for the analysis of the binary.

The provided ZIP archive contains several encrypted files with the extension 24bes, the ransomware and danger note, and another ZIP archive that contains the binary sample to be analyzed. Below is the structure of the ZIP archive for the challenge.

├── DANGER.txt
└── share
    ├── countdown.txt
    ├── expanding-horizons.pdf.24bes
    └── takeover.docx.24bes

For documentation purposes, hashes of the files are collected.

452c3328667b7242132bf0821c9d4424  ./lockpick2.0/share/takeover.docx.24bes
425d610faab2eb49d5aec7f37de59484  ./lockpick2.0/share/expanding-horizons.pdf.24bes
e2edc252b5776a1e9c63c58b5328ae3a  ./lockpick2.0/share/countdown.txt
62c05b0f64aa0bd8419c30216b2f106d  ./lockpick2.0/DANGER.txt
bee5debabd4ab24a5d844f8bc0562e33  ./lockpick2.0/

The DANGER.txt file is a note to the analyst, providing some analysis instructions and the password for the ZIP archive that contains the binary sample.

Analyze the Share Files

Let us analyze the files within the share directory.

The countdown.txt file is the ransom note left by the ransomware, it contains a BTC address and an Onion address that can be used to reach out to the threat actors.

The BTC address can be checked through a blockchain tracker. This is outside of the scope of this analysis, so we'll just make a note of the information as indicators of compromise (IoC).

The Onion address is also being documented as an IoC, and any monitoring tools used within the organization checked for the presence of connection attempts to this address.

Reviewing the files with the extension 24bes, they show up as being data

$ file -k expanding-horizons.pdf.24bes takeover.docx.24bes
expanding-horizons.pdf.24bes: data
takeover.docx.24bes:          data

Which is expected since the data is encrypted, this can be further confirmed by checking the initial bytes of the file with a hex editor or hex dump tool such as xxd

$ xxd -l 64 expanding-horizons.pdf.24bes
00000000: 0109 8fec f14f 7659 16bf ee41 7d13 1624  .....OvY...A}..$
00000010: 2175 87e4 9f7f ca99 db8b ab7f b13f 6370  !u...........?cp
00000020: 0517 0432 99d4 ed8f 494b 24c1 5e89 21b0  ...2....IK$.^.!.
00000030: 8ec5 74d3 e96a 80c8 389b 6c05 7f6a c0ef  ..t..j..8.l..j..
$ xxd -l 64 takeover.docx.24bes
00000000: 16b1 2ad0 2a87 38a4 7a5b 9e59 5b3a 8801  ..*.*.8.z[.Y[:..
00000010: 2daf 1eae bf01 5b06 5a3f b4f6 ef5c 4c00  -.....[.Z?...\L.
00000020: 1930 282b 1028 9553 ebaf ffbd 4118 9e86  .0(+.(.S....A...
00000030: 7c7b 70c3 102b f249 cd9e c0ec a7df 88f1  |{p..+.I........

There is no recognizable magic bytes for the files. At this point there's nothing else to analyze of these files.

Initial Analysis of update

The binary sample in the ZIP archive is named update, the first step after extracting the file is to generate the hash

$ md5sum update
8b2d4bc2f26d76c1a900e53483731013  update

The hash is then checked in malware analysis services, such as VirusTotal, to determine if the sample has been submitted previously or if it may be a new malware or targeted. Checking in VirusTotal shows that there are 33 out of 65 providers have tagged this binary as malicious, it confirms the suspicion on this being the ransomware.

Checking sample in VirusTotal

The tags for this entry show that the binary is packed using UPX Packer, which is a common packer used to obfuscate binaries, and threat actors also use this packer to obfuscate their malware with the intention of complicating analysis.

Let us further analyze this binary to determine how it manages to encrypts the data.

The binary file is loaded into Detect It Easy, with the intention of confirming the packing being used. There are several ways that the packing being used can be confirmed, further information on this can be found on the post Analyzing Packed Binaries.

Analyzing the sample in Detect It Easy

The packed binary can be extracted by using the UPX Packer with the following command

./upx -d -o update.elf update

The unpacked binary is saved to disk with the filename update.elf, this can now be analized by reverse engineering. Loading this binary into Radare2 for the reverse engineering.

$ r2 update.elf
[0x00001280]> aaaa
INFO: Analyze all flags starting with sym. and entry0 (aa)
INFO: Analyze imports (af@@@i)
INFO: Analyze entrypoint (af@ entry0)
INFO: Analyze symbols (af@@@s)
INFO: Analyze all functions arguments/locals (afva@@@F)
INFO: Analyze function calls (aac)
INFO: Analyze len bytes of instructions for references (aar)
INFO: Finding and parsing C++ vtables (avrr)
INFO: Analyzing methods (af @@ method.*)
INFO: Recovering local variables (afva@@@F)
INFO: Type matching analysis for all functions (aaft)
INFO: Propagate noreturn information (aanr)
INFO: Scanning for strings constructed in code (/azs)
INFO: Finding function preludes (aap)
INFO: Enable anal.types.constraint for experimental type propagation

After loading the binary in r2, the aaaa command analyzes the binary to identify the different parts that make up the program. One of the first parts to check are the strings, this is achieved with the iz command

[0x00001280]> iz
nth paddr      vaddr      len size section type  string
0   0x00003010 0x00003010 5   6    .rodata ascii \nCLIG
1   0x00003030 0x00003030 5   6    .rodata ascii \nCLIG
2   0x00003050 0x00003050 5   6    .rodata ascii \nCLIG
3   0x00003070 0x00003070 5   6    .rodata ascii \nCLIG
4   0x00003090 0x00003090 5   6    .rodata ascii \nCLIG
5   0x000030a3 0x000030a3 18  19   .rodata ascii b7894532snsmajuys6
6   0x000030b8 0x000030b8 31  32   .rodata ascii curl_easy_perform() failed: %s\n
7   0x000030d8 0x000030d8 31  32   .rodata ascii Could not open output file: %s\n
8   0x000030f8 0x000030f8 4   5    .rodata ascii .txt
9   0x000030fd 0x000030fd 4   5    .rodata ascii .pdf
10  0x00003102 0x00003102 4   5    .rodata ascii .sql
11  0x0000310b 0x0000310b 5   6    .rodata ascii .docx
12  0x00003111 0x00003111 5   6    .rodata ascii .xlsx
13  0x00003117 0x00003117 5   6    .rodata ascii .pptx
14  0x0000311d 0x0000311d 4   5    .rodata ascii .zip
15  0x00003122 0x00003122 4   5    .rodata ascii .tar
16  0x00003127 0x00003127 7   8    .rodata ascii .tar.gz
17  0x0000312f 0x0000312f 13  14   .rodata ascii countdown.txt
18  0x0000313d 0x0000313d 7   8    .rodata ascii /share/
19  0x00003145 0x00003145 15  16   .rodata ascii Update failed.\n
20  0x00003158 0x00003158 40  41   .rodata ascii Running update, testing update endpoints
21  0x00003181 0x00003181 11  12   .rodata ascii Updating %s
22  0x0000318d 0x0000318d 11  12   .rodata ascii  Successful
23  0x000031a0 0x000031a0 46  47   .rodata ascii Update complete - thank you for your patience.
24  0x000031cf 0x000031cf 16  17   .rodata ascii %s/countdown.txt
25  0x000031e0 0x000031e0 32  33   .rodata ascii
26  0x00003201 0x00003201 5   6    .rodata ascii %s/%s
27  0x00003210 0x00003210 30  31   .rodata ascii Could not open input file: %s\n
28  0x0000322f 0x0000322f 8   9    .rodata ascii %s.24bes
29  0x00003240 0x00003240 39  40   .rodata ascii Failed to delete the original file: %s\n

Let's analyze the strings that are shown here

Checking the URL, it shows that the ransom note is stored. This allows for the threat actor to modify the note without having to release new malware.

Ransom note on Pastes site

There's not much else to go on with the strings, so let's now look at the libraries that are used by the ransomware. The command il shows the linked libraries.

[0x00001280]> il
[Linked libraries]

3 libraries

The libraries that are imported give an idea of the functionality that this ransomware has, keeping in mind that it is possible that the malware loads additional libraries dynamically during execution.

Based on these libraries, the following can be determined

Based on the information that is known up to this point, the libcurl appears to be used to obtain the ransom note from the URL that was found in the strings. However, it's still unknown how the encryption happens or what key is used, so further analysis of the functions is needed.

Analyzing the Functions

Let's first focus on the functions that are used from the libraries that are imported by the binary. The command ii is used to list these functions, the output from Radare2 shows the function name

[0x00001280]> ii
nth vaddr      bind   type   lib name
1   0x00001030 GLOBAL FUNC       printf
2   0x00001040 GLOBAL FUNC       EVP_EncryptUpdate
3   0x00001050 GLOBAL FUNC       curl_global_init
4   0x00001060 GLOBAL FUNC       curl_global_cleanup
5   0x00001070 GLOBAL FUNC       strlen
6   0x00001080 GLOBAL FUNC       OPENSSL_init_crypto
7   0x00001090 GLOBAL FUNC       ERR_print_errors_fp
8   0x000010a0 GLOBAL FUNC       abort
9   0x000010b0 GLOBAL FUNC       EVP_EncryptInit_ex
10  0x000010c0 GLOBAL FUNC       EVP_aes_256_cbc
11  0x000010d0 GLOBAL FUNC       EVP_CIPHER_CTX_new
12  ---------- GLOBAL FUNC       __libc_start_main
13  0x000010e0 GLOBAL FUNC       sleep
14  0x000010f0 GLOBAL FUNC       memcpy
15  0x00001100 GLOBAL FUNC       stat
16  0x00001110 GLOBAL FUNC       fclose
17  0x00001120 GLOBAL FUNC       EVP_CIPHER_CTX_free
18  0x00001130 GLOBAL FUNC       strrchr
19  0x00001140 GLOBAL FUNC       curl_easy_setopt
20  0x00001150 GLOBAL FUNC       fflush
21  0x00001160 GLOBAL FUNC       fopen
22  0x00001170 GLOBAL FUNC       curl_easy_cleanup
23  0x00001180 GLOBAL FUNC       curl_easy_init
24  0x00001190 GLOBAL FUNC       curl_easy_perform
25  0x000011a0 GLOBAL FUNC       putchar
26  0x000011b0 GLOBAL FUNC       strcmp
27  0x000011c0 GLOBAL FUNC       fprintf
28  0x000011d0 GLOBAL FUNC       curl_easy_strerror
29  0x000011e0 GLOBAL FUNC       fread
30  0x000011f0 GLOBAL FUNC       opendir
31  0x00001200 GLOBAL FUNC       readdir
32  0x00001210 GLOBAL FUNC       puts
33  0x00001220 GLOBAL FUNC       EVP_EncryptFinal_ex
34  0x00001230 GLOBAL FUNC       snprintf
35  0x00001240 GLOBAL FUNC       closedir
36  ---------- WEAK   NOTYPE     _ITM_deregisterTMCloneTable
37  0x00001250 GLOBAL FUNC       remove
38  ---------- WEAK   NOTYPE     __gmon_start__
39  ---------- WEAK   NOTYPE     _ITM_registerTMCloneTable
40  0x00001260 GLOBAL FUNC       fwrite
42  0x00001270 WEAK   FUNC       __cxa_finalize

There are several functions that call my attention and that provide some relevant information

Before looking further into where the 2 functions mentioned above are used within the malware, let's look at the functions that are exported by the binary by using the iE command.

[0x00001280]> iE
nth paddr      vaddr      bind   type   size lib name                demangled
41  ---------- 0x00005140 GLOBAL OBJ    8        stdout
43  ---------- 0x00005160 GLOBAL OBJ    8        stderr
49  ---------- 0x00005140 GLOBAL OBJ    8        stdout@GLIBC_2.2.5
50  0x00003030 0x00003030 GLOBAL OBJ    19       K2
52  0x00001af3 0x00001af3 GLOBAL FUNC   394      get_key_from_url
53  ---------- 0x00005138 GLOBAL NOTYPE 0        _edata
56  0x00003000 0x00003000 GLOBAL OBJ    4        _IO_stdin_used
58  0x00003050 0x00003050 GLOBAL OBJ    19       K3
62  0x00001787 0x00001787 GLOBAL FUNC   810      main
63  0x00004130 0x00005130 GLOBAL OBJ    8        HESB
66  0x00004128 0x00005128 GLOBAL OBJ    0        __dso_handle
68  0x00001ab1 0x00001ab1 GLOBAL FUNC   66       write_binary_data
69  0x00003070 0x00003070 GLOBAL OBJ    19       K4
72  0x00001c7d 0x00001c7d GLOBAL FUNC   426      handle_directory
73  0x00002064 0x00002064 GLOBAL FUNC   0        _fini
77  0x00003090 0x00003090 GLOBAL OBJ    19       K5
79  0x0000141d 0x0000141d GLOBAL FUNC   140      xor_cipher
80  0x000014a9 0x000014a9 GLOBAL FUNC   58       write_data
81  0x0000161c 0x0000161c GLOBAL FUNC   24       handleErrors
82  0x00001280 0x00001280 GLOBAL FUNC   34       _start
84  0x000014e3 0x000014e3 GLOBAL FUNC   313      download_lyrics
87  0x00001000 0x00001000 GLOBAL FUNC   0        _init
88  ---------- 0x00005138 GLOBAL OBJ    0        __TMC_END__
94  0x0000173d 0x0000173d GLOBAL FUNC   74       encrypt
96  ---------- 0x00005160 GLOBAL OBJ    8        stderr@GLIBC_2.2.5
97  0x00004120 0x00005120 GLOBAL NOTYPE 0        __data_start
98  ---------- 0x00005170 GLOBAL NOTYPE 0        _end
105 ---------- 0x00005138 GLOBAL NOTYPE 0        __bss_start
108 0x00001e27 0x00001e27 GLOBAL FUNC   573      encrypt_file
109 0x00001634 0x00001634 GLOBAL FUNC   265      is_target_extension
115 0x00003010 0x00003010 GLOBAL OBJ    19       K1

Focusing on the lines that have the type of FUNC, there are 2 functions that appear to be relevant

Let's see if there is any relation between these 4 functions that are considered relevant in this binary. The axt command is used to find the references to a specified address, the sintaxis of this command is axt <addr>.

Let's look at the usage of the EVP_aes_256_cbc function. The output shows that the function is called from the sym.encrypt_file function, confirming that it's the encryption algorithm being used to encrypt the files.

[0x00001280]> axt 0x10c0
sym.encrypt_file 0x1f22 [CALL:--x] call sym.imp.EVP_aes_256_cbc

The curl_easy_perform function is called by 2 different functions, with the get_key_from_url being the more relevant one. This confirms that data is being downloaded.

[0x00001280]> axt 0x1190
sym.download_lyrics 0x159f [CALL:--x] call sym.imp.curl_easy_perform
sym.get_key_from_url 0x1bdc [CALL:--x] call sym.imp.curl_easy_perform

The get_key_from_url function is called from the main function.

[0x00001280]> axt 0x1af3
main 0x17c4 [CALL:--x] call sym.get_key_from_url

The xor_cipher is called from the get_key_from_url function, which can mean that there is one or more pieces of data that are encrypted and that are used in relation to the key that is downloaded.

[0x00001280]> axt 0x141d
main 0x186c [CALL:--x] call sym.xor_cipher
main 0x18fe [CALL:--x] call sym.xor_cipher
main 0x1990 [CALL:--x] call sym.xor_cipher
main 0x1a22 [CALL:--x] call sym.xor_cipher
sym.get_key_from_url 0x1b45 [CALL:--x] call sym.xor_cipher

The focus now shifts to look into the get_key_from_url function, in order to determine the URL that the key is obtained from. The first step is to move the address where the function starts, this is achieved with the command s 0x1af3, and then print the disassembled function using the pdf command.

[0x00001af3]> pdf
            ; CALL XREF from main @ 0x17c4(x)
┌ 394: sym.get_key_from_url (void *arg1, int64_t arg2);
│           ; arg void *arg1 @ rdi
│           ; arg int64_t arg2 @ rsi
│           ; var uint32_t var_8h @ rbp-0x8
│           ; var int64_t var_ch @ rbp-0xc
│           ; var int64_t var_10h @ rbp-0x10
│           ; var int64_t var_14h @ rbp-0x14
│           ; var int64_t var_18h @ rbp-0x18
│           ; var uint32_t var_1ch @ rbp-0x1c
│           ; var void *s2 @ rbp-0x50
│           ; var int64_t var_90h @ rbp-0x90
│           ; var void *s1 @ rbp-0x98
│           ; var int64_t var_a0h @ rbp-0xa0
│           0x00001af3      55             push rbp
│           0x00001af4      4889e5         mov rbp, rsp
│           0x00001af7      4881eca000..   sub rsp, 0xa0
│           0x00001afe      4889bd68ff..   mov qword [s1], rdi         ; arg1
│           0x00001b05      4889b560ff..   mov qword [var_a0h], rsi    ; arg2
│           0x00001b0c      bf03000000     mov edi, 3
│           0x00001b11      e83af5ffff     call sym.imp.curl_global_init
│           0x00001b16      e865f6ffff     call sym.imp.curl_easy_init
│           0x00001b1b      488945f8       mov qword [var_8h], rax
│           0x00001b1f      48837df800     cmp qword [var_8h], 0
│       ┌─< 0x00001b24      0f840f010000   je 0x1c39
│       │   0x00001b2a      488b15ff35..   mov rdx, qword [obj.HESB]   ; [0x5130:8]=0x30a3 str.b7894532snsmajuys6 ; char *arg3
│       │   0x00001b31      488d8570ff..   lea rax, [var_90h]
│       │   0x00001b38      4889c6         mov rsi, rax                ; int64_t arg2
│       │   0x00001b3b      488d05ce14..   lea rax, obj.K1             ; 0x3010 ; "\nCLIG\x0f\x1c\x1d\x01\f]\n\x18EF\x1f\x1fE\x1b"
│       │   0x00001b42      4889c7         mov rdi, rax                ; char *arg1
│       │   0x00001b45      e8d3f8ffff     call sym.xor_cipher

The function is long, so focusing on the first lines of the function, it becomes apparent data is decrypted as the initial steps. Let's break down the information that is seen.

The binary data is found at the address 0x3010, the command px can be used to print the hex dump of the memory region, the full command is px @ 0x3010, and the screenshot below shows the binary data that is referenced

XOR Encrypted Binary Data

To make it easier to handle the binary data, we'll copy the hex bytes, these being 0a434c49470f1c1d010c5d0a1845461f1f451b. CyberChef can be used to decrypt the data, with the operations From Hex and XOR.

Decrypting the XOR binary data via CyberChef

The encrypted data contained the URL, which would mean that the key that is used would be downloaded from this address.

Reviewing the hex dump above, it appears that there are other URLs that are encrypted, and the CLIG string is a common denominator among them. Decrypting these URLs can be done in the same manner as shown above, they are decoys and don't contain any valuable data, so are skipped for the purposes of this writeup.

Further reviewing the disassembled function, specifically how the data is downloaded from the URL. Checking the documentation for this function shows that this is used to perform a blocking file transfer, the example shows that the data that is downloaded is stored in a variable.

int main(void)
  CURL *curl = curl_easy_init();
  if(curl) {
    CURLcode res;
    curl_easy_setopt(curl, CURLOPT_URL, "");
    res = curl_easy_perform(curl);

The above code shows that the curl_easy_setopt function is called to configure the curl object, and then the curl_easy_perform function is called to execute the download action.

The following command can be used to download the data to a file named 3flsy.

curl -vLO --url ''

Checking the file, it contains binary data, and it can be viewed with xxd to obtain the data

$ xxd 3flsy
00000000: f3fc 056d a118 5eae 370d 76d4 7c4c f9db  ...m..^.7.v.|L..
00000010: 9f4e fd1c 1585 cde3 a7bc c6cb 5889 f6db  .N..........X...
00000020: 0144 8c79 0993 9e13 ce35 9710 b9f0 dc2e  .D.y.....5......

Decrypting the Files

Now that the key has been obtained, we need to determine how to decrypt the files. Reviewing the available data, the files are encrypted using AES 256 CBC algorithm, and this can be decrypted using CyberChef.

By using the AES Decrypt operation it is possible to decrypt the files, there is relevant information displayed in the output

Invalid key length: 0 bytes

The following algorithms will be used based on the size of the key: 16 bytes = AES-128 24 bytes = AES-192 32 bytes = AES-256

Knowing that the encryption used is AES 256, the key should have a length of 32 bytes. However, the file has a size of 48 bytes, this means that it contains 16 additional bytes.

The AES encryption requires a key and an initialization vector, meaning that it's possible that this key that was downloaded contains both values. Lets extract the key and IV values from the file with the xxd command

$ xxd -l 32 -ps 3flsy

$ xxd -s 32 -ps 3flsy

The -l stops the printing of the hexdump after the specified amount of bytes. The -s starts at the specified bytes, instead of starting at 0. The -ps option prints only the hex values

These values are used in the AES Decrypt recipe in CyberChef, along with the encrypted files.

Decrypting the files

The encrypted data can now be restored to it's original state.


The threat actor has altered their tactics and now doesn't hard code all of the necessary information, while making them available through a web service that can be easily altered to change the result of the encryption.