FlagYard: Hasher Write-up
Hasher
Description: Malware developers always use hashing to hide things.
Challenge Link -> Hasher
I first used exiftool on the file
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
$ exiftool Hasher.exe
ExifTool Version Number : 12.57
File Name : Hasher.exe
Directory : .
File Size : 13 kB
File Modification Date/Time : 2025:10:10 15:11:13+03:00
File Access Date/Time : 2026:03:08 20:44:10+03:00
File Inode Change Date/Time : 2026:03:08 20:44:04+03:00
File Permissions : -rwxrwxrwx
File Type : Win64 EXE
File Type Extension : exe
MIME Type : application/octet-stream
Machine Type : AMD AMD64
Time Stamp : 2025:06:10 23:18:13+03:00
Image File Characteristics : Executable, Large address aware
PE Type : PE32+
Linker Version : 14.43
Code Size : 5120
Initialized Data Size : 8192
Uninitialized Data Size : 0
Entry Point : 0x1670
OS Version : 6.0
Image Version : 0.0
Subsystem Version : 6.0
Subsystem : Windows command line
From the strings result, it seems the binary compares our input to the correct flag
1
2
3
4
5
6
Enter the flag:
%255s
Wrong length!
Wrong flag!
Correct flag!
RSDS`
And after decompiling the binary, this is the core function
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
147
148
149
void FUN_1400010e0(void)
{
ushort *puVar1;
ushort uVar2;
longlong lVar3;
uint uVar4;
longlong lVar5;
size_t sVar7;
int iVar8;
uint uVar9;
undefined8 uVar10;
char *pcVar11;
ulonglong uVar12;
ulonglong uVar13;
undefined8 uVar14;
ushort *puVar15;
undefined8 in_R9;
ulonglong uVar16;
uint uVar17;
undefined1 auStack_1e8 [32];
int local_1c8 [40];
undefined2 local_128;
ushort local_126 [7];
char local_118 [256];
ulonglong local_18;
longlong lVar6;
local_18 = DAT_140005000 ^ (ulonglong)auStack_1e8;
uVar10 = 0;
uVar14 = 0x100;
memset(local_118,0,0x100);
FUN_140001020("Enter the flag: ",uVar10,uVar14,in_R9);
puVar15 = (ushort *)0x100;
pcVar11 = local_118;
FUN_140001080("%255s",pcVar11,0x100,in_R9);
local_1c8[0] = -0x46629728;
local_1c8[1] = 0x4ea585c0;
local_1c8[2] = 0x93642e87;
local_1c8[3] = 0x52181b4;
local_1c8[4] = 0xe8b4cda3;
local_1c8[5] = 0x858ac814;
local_1c8[6] = 0x45e26648;
local_1c8[7] = 0x93642e87;
local_1c8[8] = 0xcbba9c51;
local_1c8[9] = 0xd659a5a8;
local_1c8[10] = 0x9d07d8da;
local_1c8[0xb] = 0x93642e87;
local_1c8[0xc] = 0xf2475372;
local_1c8[0xd] = 0xcbba9c51;
local_1c8[0xe] = 0xd659a5a8;
local_1c8[0xf] = 0x930b26e3;
local_1c8[0x10] = 0xcfef5b0b;
local_1c8[0x11] = 0xea1b9f56;
local_1c8[0x12] = 0x9d07d8da;
local_1c8[0x13] = 0xea1b9f56;
local_1c8[0x14] = 0x70c1a0e1;
local_1c8[0x15] = 0x8288d321;
local_1c8[0x16] = 0x115ea782;
local_1c8[0x17] = 0x45e26648;
local_1c8[0x18] = 0xf4c73ebf;
local_1c8[0x19] = 0x45e26648;
local_1c8[0x1a] = 0x93642e87;
local_1c8[0x1b] = 0xcbba9c51;
local_1c8[0x1c] = 0xd659a5a8;
local_1c8[0x1d] = 0x93642e87;
local_1c8[0x1e] = 0x9d07d8da;
local_1c8[0x1f] = 0xcbba9c51;
local_1c8[0x20] = 0xcfef5b0b;
local_1c8[0x21] = 0x930b26e3;
local_1c8[0x22] = 0x45e26648;
local_1c8[0x23] = 0x93642e87;
local_1c8[0x24] = 0x93642e87;
local_1c8[0x25] = 0x115ea782;
local_1c8[0x26] = 0x303097c9;
lVar6 = -1;
do {
lVar5 = lVar6 + 1;
lVar3 = lVar6 + 1;
lVar6 = lVar5;
} while (local_118[lVar3] != '\0');
if (lVar5 == 0x27) {
uVar13 = 0;
do {
local_128._0_1_ = local_118[uVar13];
uVar12 = 0x100;
local_128._1_1_ = 0;
sVar7 = strnlen((char *)&local_128,0x100);
if (sVar7 == 0) {
iVar8 = 0;
}
else {
puVar15 = &local_128;
uVar17 = (uint)sVar7 & 3;
uVar4 = 0;
for (uVar16 = sVar7 >> 2; uVar16 != 0; uVar16 = uVar16 - 1) {
uVar2 = *puVar15;
puVar1 = puVar15 + 1;
puVar15 = puVar15 + 2;
uVar9 = (uint)*puVar1 << 0xb ^ (uVar4 + uVar2) * 0x10000;
uVar12 = (ulonglong)uVar9;
uVar9 = uVar4 + uVar2 ^ uVar9;
uVar4 = uVar9 + (uVar9 >> 0xb);
}
if (uVar17 == 1) {
uVar4 = uVar4 + (byte)*puVar15;
uVar17 = uVar4 ^ uVar4 * 0x400;
uVar4 = uVar17 >> 1;
LAB_14000135d:
uVar4 = uVar17 + uVar4;
}
else {
if (uVar17 == 2) {
uVar17 = uVar4 + *puVar15 ^ (uVar4 + *puVar15) * 0x800;
uVar4 = uVar17 >> 0x11;
goto LAB_14000135d;
}
if (uVar17 == 3) {
uVar17 = (uint)(byte)puVar15[1] << 0x12 ^ (uVar4 + *puVar15) * 0x10000;
uVar12 = (ulonglong)uVar17;
uVar17 = uVar4 + *puVar15 ^ uVar17;
uVar4 = uVar17 >> 0xb;
goto LAB_14000135d;
}
}
uVar4 = uVar4 ^ uVar4 * 8;
uVar4 = uVar4 + (uVar4 >> 5);
uVar4 = uVar4 ^ uVar4 * 0x10;
uVar4 = uVar4 + (uVar4 >> 0x11);
uVar4 = uVar4 ^ uVar4 * 0x2000000;
iVar8 = (uVar4 >> 6) + uVar4;
}
if (iVar8 != local_1c8[uVar13]) {
FUN_140001020("Wrong flag!\n",uVar12,puVar15,0);
goto LAB_1400013c7;
}
uVar13 = uVar13 + 1;
} while (uVar13 < 0x27);
FUN_140001020("Correct flag!\n",uVar12,puVar15,0);
}
else {
FUN_140001020("Wrong length!\n",pcVar11,puVar15,in_R9);
}
LAB_1400013c7:
FUN_1400013f0(local_18 ^ (ulonglong)auStack_1e8);
return;
}
Instead of comparing the user input directly to a stored password, the program hashes each character of the user input and compares the resulting hash against a hardcoded list.
1. Input and Length Check
The function first initializes a buffer (local_118) and asks the user for a flag.
- It calculates the length of your input using a
do-whileloop. - The constraint: The length must be 0x27 (39 characters). If it isn’t, the program immediately prints “Wrong length!” and exits.
2. The Hardcoded Hash List
The array local_1c8 contains 39 hexadecimal values (e.g., 0x46629728, 0x4ea585c0, etc.). Each of these corresponds to the “SuperFastHash” result of a single character from the correct flag.
3. The Hashing Algorithm: SuperFastHash
The core of the logic is the loop starting at uVar13 = 0. For every character in the input, the code performs the following:
- Isolation: It takes one character, places it in a temporary buffer (
local_128), and null-terminates it so it’s treated as a string of length 1. - Bitwise Operations: The complex block of code involving
>> 0xb,^, and0x10000is a decompiler’s representation of the Paul Hsieh’s SuperFastHash algorithm. Final Mixing: After the main bit-shifting, it performs a final “avalanche” mix
1 2 3 4 5
uVar4 = uVar4 ^ uVar4 * 8; uVar4 = uVar4 + (uVar4 >> 5); uVar4 = uVar4 ^ uVar4 * 0x10; ... iVar8 = (uVar4 >> 6) + uVar4;
- Comparison: It compares the resulting hash (
iVar8) with the value stored in thelocal_1c8table at that same index.
Since the program hashes each character individually, we can crack it by replicating the hashing algorithm. The Strategy:
- Identify the Character Set: Usually printable ASCII (Space to
~). - Pre-compute: For every possible ASCII character, run it through the hashing logic found in the code.
- Map and Match:
- Create a dictionary/map where
Key = HashResultandValue = Character. - Iterate through the
local_1c8array from the code. - Look up each hex value in your map to retrieve the original character.
- Create a dictionary/map where
Example Logic:
- If
SuperFastHash('A')results in0x46629728, and the first value in the array is0x46629728, then the first letter of the flag is ‘A’.
On the first try, it resulted in a false.
1
2
python3 test.py
Flag: Fl?g??8?????????????c?b8?8????????8??b}
So I compared the first 6 correct characters FlagY{ to the target list to identify the issue
1
2
3
4
5
6
7
8
python3 test.py
--- Debugging Calibration ---
Char: F | Calculated: -0x46629728 | Target: -0x46629728 | MATCH
Char: l | Calculated: 0x4ea585c0 | Target: 0x4ea585c0 | MATCH
Char: a | Calculated: -0x6c9bd179 | Target: 0x93642e87 | FAIL
Char: g | Calculated: 0x52181b4 | Target: 0x52181b4 | MATCH
Char: Y | Calculated: -0x174b325d | Target: 0xe8b4cda3 | FAIL
Char: { | Calculated: -0x7a7537ec | Target: 0x858ac814 | FAIL
According to this source. The issue lies in how Python and C interpret the signed and unsigned integers.
- In C: the 32-bit register might hold the value
0x93642E87. If that register is compared to a signed integer variable, the computer sees the MSB is1and treats it as a negative value. - In Python: Python’s integers are arbitrary-precision (they don’t “overflow” at 32 bits). When you calculate a large number in Python, it just keeps growing. It doesn’t naturally wrap around to negative numbers unless you force it to using
ctypesor bit-masking.
The fix is applying a 0xFFFFFFFF mask to every operation, so we force the Python environment to respect the 32-bit boundary, resolving discrepancies caused by Two’s Complement interpretation of the Most Significant Bit (MSB).
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
import ctypes
def solve_hash_final(char_byte):
uVar4 = 0
uVar4 = (uVar4 + char_byte) & 0xFFFFFFFF
uVar17 = (uVar4 ^ (uVar4 << 10)) & 0xFFFFFFFF
uVar4 = (uVar17 + (uVar17 >> 1)) & 0xFFFFFFFF
# Avalanche
uVar4 = (uVar4 ^ (uVar4 << 3)) & 0xFFFFFFFF
uVar4 = (uVar4 + (uVar4 >> 5)) & 0xFFFFFFFF
uVar4 = (uVar4 ^ (uVar4 << 4)) & 0xFFFFFFFF
uVar4 = (uVar4 + (uVar4 >> 17)) & 0xFFFFFFFF
uVar4 = (uVar4 ^ (uVar4 << 25)) & 0xFFFFFFFF
# Return as raw 32-bit UNSIGNED
return ((uVar4 >> 6) + uVar4) & 0xFFFFFFFF
target_hashes = [
-0x46629728, 0x4ea585c0, 0x93642e87, 0x52181b4, 0xe8b4cda3, 0x858ac814, 0x45e26648,
0x93642e87, 0xcbba9c51, 0xd659a5a8, 0x9d07d8da, 0x93642e87, 0xf2475372, 0xcbba9c51,
0xd659a5a8, 0x930b26e3, 0xcfef5b0b, 0xea1b9f56, 0x9d07d8da, 0xea1b9f56, 0x70c1a0e1,
0x8288d321, 0x115ea782, 0x45e26648, 0xf4c73ebf, 0x45e26648, 0x93642e87, 0xcbba9c51,
0xd659a5a8, 0x93642e87, 0x9d07d8da, 0xcbba9c51, 0xcfef5b0b, 0x930b26e3, 0x45e26648,
0x93642e87, 0x93642e87, 0x115ea782, 0x303097c9
]
# Normalize the target hashes to unsigned 32-bit
normalized_targets = [h & 0xFFFFFFFF for h in target_hashes]
flag = ""
for h in normalized_targets:
found = False
for i in range(32, 127):
if solve_hash_final(i) == h:
flag += chr(i)
found = True
break
if not found:
flag += "?"
print(f"Flag: {flag}")