Binary analysis and XOR decryption challenge involving memory manipulation and shellcode execution.
Starting with basic binary analysis:
ELF 64-bit LSB executable, x86-64, not strippedOpening the binary in Ghidra for static analysis. Let's examine the main function:
undefined8 main(int param_1,undefined8 *param_2,undefined8 param_3,uchar *param_4,size_t param_5) {
undefined8 uVar1;
ulong uVar2;
size_t *outlen;
uchar *out;
if (param_1 < 2) {
printf("Usage: %s hex_key",*param_2);
uVar1 = 1;
} else {
out = (uchar *)0x0;
uVar2 = strtol((char *)param_2[1],(char **)0x0,0x10);
decrypt((EVP_PKEY_CTX *)(uVar2 & 0xffffffff),out,outlen,param_4,param_5);
if (flag_ciphertext == -0x1a76b7ab) {
(*(code *)&flag_ciphertext)();
putchar(10);
} else {
puts("Invalid key");
}
uVar1 = 0;
}
return uVar1;
}
Key Observations:
strtol() in base 16decrypt() function is called with the keyflag_ciphertext == -0x1a76b7ab, it executes the flag as shellcodeNow let's examine the decrypt function:
int decrypt(EVP_PKEY_CTX *ctx,uchar *out,size_t *outlen,uchar *in,size_t inlen) {
int iVar1;
long lVar2;
uint local_28;
lVar2 = sysconf(0x1e);
iVar1 = mprotect((void *)(-lVar2 & 0x104020U),(long)(0x104020 - (int)(void *)(-lVar2 & 0x104020U)),7);
if (iVar1 < 0) {
perror("mprotect:");
}
for (local_28 = 0; local_28 < 0x18; local_28 = local_28 + 1) {
(&flag_ciphertext)[(int)local_28] = (&flag_ciphertext)[(int)local_28] ^ (uint)ctx;
}
return local_28;
}
Function Analysis:
mprotect() makes memory executable (RWX permissions)0x18) of flag_ciphertextUsing GDB to find the memory addresses and extract the encrypted flag:
pwndbg> info files
Symbols from "/home/kali/Documents/studsec/simply_unstable/Handout/challenge".
Local exec file:
`/home/kali/Documents/studsec/simply_unstable/Handout/challenge', file type elf64-x86-64.
. . .
0x0000555555558020 - 0x0000555555558082 is .secret
Now let's examine the encrypted flag data:
pwndbg> x/24xb 0x0000555555558020
0x555555558020 <flag_ciphertext>: 0x81 0x8c 0x3c 0x41 0x9c 0x49 0xe0 0x44
0x555555558028 <flag_ciphertext+8>: 0x9c 0x4d 0x62 0xec 0x6d 0x85 0xf4 0xe5
0x555555558030 <flag_ciphertext+16>: 0x95 0x85 0xf4 0xe5 0x95 0x8c 0x0d 0xa6
Now we need to calculate the XOR key. We know:
flag_ciphertext == -0x1a76b7ab-0x1a76b7ab = 0xe5894855
0x413c8c81 (first 4 bytes, little-endian)
initial_value ⊕ target_value
initial_value = 0x413c8c81
target_value = 0xe5894855
X = initial_value ^ target_value
print(f"XOR key should be: {hex(X)}")
# Result: 0xa4b5c4d4
Testing our calculated XOR key:
./challenge 0xa4b5c4d4
The challenge was solved through a systematic approach:
This challenge demonstrates the importance of understanding both static code analysis and dynamic memory manipulation in reverse engineering tasks.