FactCheck

Reversing decompiled C++ code to extract a flag by tracing string manipulation logic

Challenge Info

This binary is putting together some important piece of information… Can you uncover that information?
Examine this file. Do you understand its inner workings?

Basic forensics & info

1
2
[marcial@arch ~/desktop/cyber/pico/FactCheck]$ file bin                
bin: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=ed9d01aa375e575eb2cd16506aa83d6951841f87, for GNU/Linux 3.2.0, not stripped

Basic info, we see we’re dealing with a non-stripped, 64-bit, linux executable.

Running strings and grepping for “flag” yields something interesting:

1
2
3
[marcial@arch ~/desktop/cyber/pico/FactCheck]$ strings bin | grep "pico"
picoCTF{wELF_d0N3_mate_
[marcial@arch ~/desktop/cyber/pico/FactCheck]$

Just like that, it looks like we have at least half our flag.

Examining in Ghidra

After running “analyze all” in Ghidra, my go-to is to check the symbol tree, specifically for functions. I noticed a FUN_00101020 function, which looked interesting, but didn’t yield anything. After just clicking and glancing at all of them, the one that caught my eye was 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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
/* WARNING: Removing unreachable block (ram,0x0010170c) */

undefined8 main(void)

{
char cVar1;
char *pcVar2;
long in_FS_OFFSET;
allocator local_249;
string local_248 [32];
string local_228 [32];
string local_208 [32];
string local_1e8 [32];
string local_1c8 [32];
string local_1a8 [32];
string local_188 [32];
string local_168 [32];
string local_148 [32];
string local_128 [32];
string local_108 [32];
string local_e8 [32];
string local_c8 [32];
string local_a8 [32];
string local_88 [32];
string local_68 [32];
string local_48 [40];
long local_20;

local_20 = *(long *)(in_FS_OFFSET + 0x28);
std::allocator<char>::allocator();
/* try { // try from 001012cf to 001012d3 has its CatchHandler @ 00101975 */
std::string::string(local_248,"picoCTF{wELF_d0N3_mate_",&local_249);
std::allocator<char>::~allocator((allocator<char> *)&local_249);
std::allocator<char>::allocator();
/* try { // try from 0010130a to 0010130e has its CatchHandler @ 00101996 */
std::string::string(local_228,"7",&local_249);
std::allocator<char>::~allocator((allocator<char> *)&local_249);
std::allocator<char>::allocator();
/* try { // try from 00101345 to 00101349 has its CatchHandler @ 001019b1 */
std::string::string(local_208,"5",&local_249);
std::allocator<char>::~allocator((allocator<char> *)&local_249);
std::allocator<char>::allocator();
/* try { // try from 00101380 to 00101384 has its CatchHandler @ 001019cc */
std::string::string(local_1e8,"4",&local_249);
std::allocator<char>::~allocator((allocator<char> *)&local_249);
std::allocator<char>::allocator();
/* try { // try from 001013bb to 001013bf has its CatchHandler @ 001019e7 */
std::string::string(local_1c8,"3",&local_249);
std::allocator<char>::~allocator((allocator<char> *)&local_249);
std::allocator<char>::allocator();
/* try { // try from 001013f6 to 001013fa has its CatchHandler @ 00101a02 */
std::string::string(local_1a8,"6",&local_249);
std::allocator<char>::~allocator((allocator<char> *)&local_249);
std::allocator<char>::allocator();
/* try { // try from 00101431 to 00101435 has its CatchHandler @ 00101a1d */
std::string::string(local_188,"9",&local_249);
std::allocator<char>::~allocator((allocator<char> *)&local_249);
std::allocator<char>::allocator();
/* try { // try from 0010146c to 00101470 has its CatchHandler @ 00101a38 */
std::string::string(local_168,"a",&local_249);
std::allocator<char>::~allocator((allocator<char> *)&local_249);
std::allocator<char>::allocator();
/* try { // try from 001014a7 to 001014ab has its CatchHandler @ 00101a53 */
std::string::string(local_148,"e",&local_249);
std::allocator<char>::~allocator((allocator<char> *)&local_249);
std::allocator<char>::allocator();
/* try { // try from 001014e2 to 001014e6 has its CatchHandler @ 00101a6e */
std::string::string(local_128,"3",&local_249);
std::allocator<char>::~allocator((allocator<char> *)&local_249);
std::allocator<char>::allocator();
/* try { // try from 0010151d to 00101521 has its CatchHandler @ 00101a89 */
std::string::string(local_108,"d",&local_249);
std::allocator<char>::~allocator((allocator<char> *)&local_249);
std::allocator<char>::allocator();
/* try { // try from 00101558 to 0010155c has its CatchHandler @ 00101aa4 */
std::string::string(local_e8,"b",&local_249);
std::allocator<char>::~allocator((allocator<char> *)&local_249);
std::allocator<char>::allocator();
/* try { // try from 00101593 to 00101597 has its CatchHandler @ 00101abf */
std::string::string(local_c8,"1",&local_249);
std::allocator<char>::~allocator((allocator<char> *)&local_249);
std::allocator<char>::allocator();
/* try { // try from 001015ce to 001015d2 has its CatchHandler @ 00101ada */
std::string::string(local_a8,"6",&local_249);
std::allocator<char>::~allocator((allocator<char> *)&local_249);
std::allocator<char>::allocator();
/* try { // try from 00101606 to 0010160a has its CatchHandler @ 00101af5 */
std::string::string(local_88,"e",&local_249);
std::allocator<char>::~allocator((allocator<char> *)&local_249);
std::allocator<char>::allocator();
/* try { // try from 0010163e to 00101642 has its CatchHandler @ 00101b0d */
std::string::string(local_68,"c",&local_249);
std::allocator<char>::~allocator((allocator<char> *)&local_249);
std::allocator<char>::allocator();
/* try { // try from 00101676 to 0010167a has its CatchHandler @ 00101b25 */
std::string::string(local_48,"8",&local_249);
std::allocator<char>::~allocator((allocator<char> *)&local_249);
/* try { // try from 00101699 to 0010185f has its CatchHandler @ 00101b3d */
pcVar2 = (char *)std::string::operator[]((ulong)local_208);
if (*pcVar2 < 'B') {
std::string::operator+=(local_248,local_c8);
}
pcVar2 = (char *)std::string::operator[]((ulong)local_a8);
if (*pcVar2 != 'A') {
std::string::operator+=(local_248,local_68);
}
pcVar2 = (char *)std::string::operator[]((ulong)local_1c8);
cVar1 = *pcVar2;
pcVar2 = (char *)std::string::operator[]((ulong)local_148);
if ((int)cVar1 - (int)*pcVar2 == 3) {
std::string::operator+=(local_248,local_1c8);
}
std::string::operator+=(local_248,local_1e8);
std::string::operator+=(local_248,local_188);
pcVar2 = (char *)std::string::operator[]((ulong)local_168);
if (*pcVar2 == 'G') {
std::string::operator+=(local_248,local_168);
}
std::string::operator+=(local_248,local_1a8);
std::string::operator+=(local_248,local_88);
std::string::operator+=(local_248,local_228);
std::string::operator+=(local_248,local_128);
std::string::operator+=(local_248,'}');
std::string::~string(local_48);
std::string::~string(local_68);
std::string::~string(local_88);
std::string::~string(local_a8);
std::string::~string(local_c8);
std::string::~string(local_e8);
std::string::~string(local_108);
std::string::~string(local_128);
std::string::~string(local_148);
std::string::~string(local_168);
std::string::~string(local_188);
std::string::~string(local_1a8);
std::string::~string(local_1c8);
std::string::~string(local_1e8);
std::string::~string(local_208);
std::string::~string(local_228);
std::string::~string(local_248);
if (local_20 == *(long *)(in_FS_OFFSET + 0x28)) {
return 0;
}
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
  • Starts by declaring a number of C++ string variables (local_248, local_228, etc.)
  • Initializes the first string (local_248) with the partial flag we saw earlier: "picoCTF{wELF_d0N3_mate_}")
  • Additionally, there’s a series of unconditionaly statements that append certain characters to the flag, but based on specific conditions, which I’ll cover more in the next section

Flag construction logic & solution

The flag’s construction logic:

  • If the value at index 0 of local_208 (“5”) is less than ‘B’, append local_c8 (“1”) - this condition is true
  • If the value at index 0 of local_a8 (“6”) is not ‘A’, append local_68 (“c”) - this condition is true
  • If the difference between the value at index 0 of local_1c8 (“3”) and local_148 (“e”) equals 3, append local_1c8 (“3”) - this needs calculation
  • Unconditionally append local_1e8 (“4”)
  • Unconditionally append local_188 (“9”)
  • If the value at index 0 of local_168 (“a”) is ‘G’, append local_168 (“a”) - this condition is false
  • Unconditionally append local_1a8 (“6”)
  • Unconditionally append local_88 (“e”)
  • Unconditionally append local_228 (“7”)
  • Unconditionally append local_128 (“3”)
  • Unconditionally append “}” to close the flag

The solve script I used:

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
53
54
55
56
57
58
59
60
61
#!/usr/bin/env python3

def solve_picoctf():
flag = "picoCTF{wELF_d0N3_mate_"

# Define all the character variables as they appeared in Ghidra
char_map = {
'local_248': "picoCTF{wELF_d0N3_mate_",
'local_228': "7",
'local_208': "5",
'local_1e8': "4",
'local_1c8': "3",
'local_1a8': "6",
'local_188': "9",
'local_168': "a",
'local_148': "e",
'local_128': "3",
'local_108': "d",
'local_e8': "b",
'local_c8': "1",
'local_a8': "6",
'local_88': "e",
'local_68': "c",
'local_48': "8"
}

# Follow the logic in the decompiled code

# If the value at index 0 of local_208 ("5") is less than 'B'
if char_map['local_208'][0] < 'B':
flag += char_map['local_c8'] # Append "1"

# If the value at index 0 of local_a8 ("6") is not 'A'
if char_map['local_a8'][0] != 'A':
flag += char_map['local_68'] # Append "c"

# If the difference between the value at index 0 of local_1c8 ("3") and local_148 ("e") equals 3
# ASCII '3' is 51, ASCII 'e' is 101, difference = -50, not 3, so this condition is false
if ord(char_map['local_1c8'][0]) - ord(char_map['local_148'][0]) == 3:
flag += char_map['local_1c8'] # This won't execute

# Unconditionally append these characters
flag += char_map['local_1e8']
flag += char_map['local_188']

# If the value at index 0 of local_168 ("a") is 'G'
if char_map['local_168'][0] == 'G':
flag += char_map['local_168'] # This won't execute

# Unconditionally append these characters
flag += char_map['local_1a8']
flag += char_map['local_88']
flag += char_map['local_228']
flag += char_map['local_128']

flag += "}"

return flag

if __name__ == "__main__":
print(solve_picoctf())

flag: picoCTF{wELF_d0N3_mate_1c496e73}