HTB「Exatlon」Easy Writeup
UPXでパックされたバイナリの解析
HTB「Exatlon」Easy Writeup
20260516-htb-chall-rev-easy-Exatlon
stringsの結果から,UPX 3.95 によってパックされていることがわかりました.upxコマンドでアンパックしておきます.
Surface Analysis
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ file exatlon_v1
exatlon_v1: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, no section header
$ checksec --file=exatlon_v1
[*] '/home/sl91994/wksp/sec/ctf/htb/challenges/rev/rev_easy_exatlon/exatlon_v1'
Arch: amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
Packer: Packed with UPX
$ upx -d -o exatlon_v1_unpacked ./exatlon_v1
Ultimate Packer for eXecutables
Copyright (C) 1996 - 2026
UPX 5.1.0 Markus Oberhumer, Laszlo Molnar & John Reiser Jan 7th 2026
File size Ratio Format Name
-------------------- ------ ----------- -----------
2202568 <- 709524 32.21% linux/amd64 exatlon_v1_unpacked
Unpacked 1 file.
$ objdump -d -M intel ./exatlon_v1_unpacked | grep -A 108 "<main>:"
Dynamic Analysis
1
2
3
4
5
6
7
8
9
10
11
12
13
$ ./exatlon_v1
███████╗██╗ ██╗ █████╗ ████████╗██╗ ██████╗ ███╗ ██╗ ██╗ ██╗ ██╗
██╔════╝╚██╗██╔╝██╔══██╗╚══██╔══╝██║ ██╔═══██╗████╗ ██║ ██║ ██║███║
█████╗ ╚███╔╝ ███████║ ██║ ██║ ██║ ██║██╔██╗ ██║ ██║ ██║╚██║
██╔══╝ ██╔██╗ ██╔══██║ ██║ ██║ ██║ ██║██║╚██╗██║ ╚██╗ ██╔╝ ██║
███████╗██╔╝ ██╗██║ ██║ ██║ ███████╗╚██████╔╝██║ ╚████║███████╗╚████╔╝ ██║
╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚══════╝ ╚═════╝ ╚═╝ ╚═══╝╚══════╝ ╚═══╝ ╚═╝
[+] Enter Exatlon Password : 123
[-] ;(
# 以降ループ
Static Analysis
入力値を exatlon() という関数に渡して,返ってきた数列と 1152 1344 1056... という数列を比較し,合致していれば正解となるようです.
main()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
undefined4 main(void)
{
bool is_correct;
ostream *poVar1;
undefined4 status;
string input_password [32];
string encrypted_password [32];
do {
std::operator<<((ostream *)&std::cout,"\n");
std::operator<<((ostream *)&std::cout,&DAT_0054b018);
std::operator<<((ostream *)&std::cout,&DAT_0054b0d8);
sleep(1);
std::operator<<((ostream *)&std::cout,&DAT_0054b1a8);
std::operator<<((ostream *)&std::cout,&DAT_0054b260);
sleep(1);
std::operator<<((ostream *)&std::cout,&DAT_0054b320);
sleep(1);
std::operator<<((ostream *)&std::cout,&DAT_0054b400);
sleep(1);
std::__cxx11::string::string(input_password);
/* try { // try from 00404cfe to 00404dce has its CatchHandler @ 00404def */
std::operator<<((ostream *)&std::cout,"[+] Enter Exatlon Password : ");
std::operator>>((istream *)&std::cin,input_password);
exatlon(encrypted_password);
is_correct = std::operator==(encrypted_password,
"1152 1344 1056 ..." // <confidential>
);
std::__cxx11::string::~string(encrypted_password);
if (is_correct) {
poVar1 = std::operator<<((ostream *)&std::cout,"[+] Looks Good ^_^ \n\n\n");
std::ostream::operator<<(poVar1,std::endl<>);
status = 0;
is_correct = false;
}
else {
is_correct = std::operator==(input_password,"q");
if (is_correct) {
status = 0;
is_correct = false;
}
else {
poVar1 = std::operator<<((ostream *)&std::cout,"[-] ;(\n");
std::ostream::operator<<(poVar1,std::endl<>);
is_correct = true;
}
}
std::__cxx11::string::~string(input_password);
} while (is_correct);
return status;
}
exatlon() 関数では,入力された文字列を,比較のために先ほどの 1152 1344 1056... のような形式に暗号化する処理が書かれています.具体的には,入力文字列をasciiコードに変換し,1文字毎のascii数値を 4bit 左シフト (2^4=16倍) したものを返しているようです.
そのため,1152 1344 1056... をスペース毎に 16で除算 した数値をasciiに基づいて元の文字に変換することでFlagが入手できます.
exatlon()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/* exatlon(std::__cxx11::string const&) */
string * exatlon(string *out_encrypted)
{
bool has_next;
char *current_char_ptr;
string *input_str;
undefined8 end_iter;
undefined8 char_iter;
allocator string_allocator;
string formatted_str [32];
__cxx11 shifted_str [39];
char one_password;
string *input_password;
std::allocator<char>::allocator();
/* try { // try from 00404ae8 to 00404aec has its CatchHandler @ 00404bc1 */
std::__cxx11::string::string(out_encrypted,"",&string_allocator);
std::allocator<char>::~allocator((allocator<char> *)&string_allocator);
input_password = input_str;
char_iter = std::__cxx11::string::begin(input_str);
end_iter = std::__cxx11::string::end(input_password);
while( true ) {
has_next = __gnu_cxx::operator!=((__normal_iterator *)&char_iter,(__normal_iterator *)&end_iter);
if (!has_next) break;
current_char_ptr = (char *)__gnu_cxx::__normal_iterator<>::operator*((__normal_iterator<> *)&char_iter);
one_password = *current_char_ptr;
/* try { // try from 00404b63 to 00404b67 has its CatchHandler @ 00404bfd */
std::__cxx11::to_string(shifted_str,(int)one_password << 4);
/* try { // try from 00404b7d to 00404b81 has its CatchHandler @ 00404bec */
std::operator+(formatted_str,(char *)shifted_str);
/* try { // try from 00404b93 to 00404b97 has its CatchHandler @ 00404bdb */
std::__cxx11::string::operator+=(out_encrypted,formatted_str);
std::__cxx11::string::~string(formatted_str);
std::__cxx11::string::~string((string *)shifted_str);
__gnu_cxx::__normal_iterator<>::operator++((__normal_iterator<> *)&char_iter);
}
return out_encrypted;
}
復号
decrypt.py
1
2
3
4
5
6
7
8
9
10
11
12
#!/usr/bin/env python3
# 暗号化された数字列
encrypted_data = "1152 1344 1056 ..."
# スペースで分割して整数のリストに変換
numbers = [int(x) for x in encrypted_data.split()]
# 各数値を16で割り,chr()でASCII文字に変換して結合
flag = "".join(chr(num // 16) for num in numbers)
print(f"[+] Decrypted Flag: {flag}")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ chmod +x ./exatlon_decrypt.py
$ ./exatlon_decrypt.py
[+] Decrypted Flag: <confidential>
$ ./exatlon_v1
███████╗██╗ ██╗ █████╗ ████████╗██╗ ██████╗ ███╗ ██╗ ██╗ ██╗ ██╗
██╔════╝╚██╗██╔╝██╔══██╗╚══██╔══╝██║ ██╔═══██╗████╗ ██║ ██║ ██║███║
█████╗ ╚███╔╝ ███████║ ██║ ██║ ██║ ██║██╔██╗ ██║ ██║ ██║╚██║
██╔══╝ ██╔██╗ ██╔══██║ ██║ ██║ ██║ ██║██║╚██╗██║ ╚██╗ ██╔╝ ██║
███████╗██╔╝ ██╗██║ ██║ ██║ ███████╗╚██████╔╝██║ ╚████║███████╗╚████╔╝ ██║
╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚══════╝ ╚═════╝ ╚═╝ ╚═══╝╚══════╝ ╚═══╝ ╚═╝
[+] Enter Exatlon Password : <credentials>
[+] Looks Good ^_^
This post is licensed under CC BY 4.0 by the author.