Reflection
3 minutes to read
We are given a binary file called reflection
:
$ file reflection
reflection: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=d57b0acdb0fda3fe599c48fa63ca61e7694b8b60, for GNU/Linux 3.2.0, not stripped
If we open Ghidra and take a look at the decompiled C source code, we will see the main
function:
undefined8 main() {
long j;
char *__format;
byte flag_input[100];
int k;
int _j;
int _length;
int i;
int length;
printf(">>> ");
fgets((char *) flag_input, 100, stdin);
_length = 0;
_j = 0;
k = 0;
while (true) {
length = _length;
i = _j;
j = (long)_j;
_j = _j + 1;
if ((flag[j] ^ flag_input[i]) != *(byte *) ((long) k + 0x100000)) break;
_length = _length + 1;
if (64 < length) break;
do {
k = k + 1;
} while (*(char *) ((long) k + 0x100000) == '\0');
}
if (_length == 65) {
__format = "yes";
} else {
__format = "no";
}
printf(__format);
return 0;
}
The program expects to enter the flag and it uses XOR to encrypt the flag and compares it to some data stored in the binary. Specifically, the comparison is made with the ELF header (first 65 bytes, skipping null bytes).
The global symbol flag is at address 0x4060
:
$ readelf -s reflection | grep flag
68: 0000000000004060 65 OBJECT GLOBAL DEFAULT 25 flag
We can take a look at these bytes with xxd
(but notice that we need to substract 0x1000
, because the binary is not loaded):
$ xxd reflection | grep -A 4 3060:
00003060: 1626 3820 7965 6867 6178 0f65 1f9b 542f .&8 yehgax.e..T/
00003070: 4f52 3477 7f72 5b26 352e bb76 b16d 665c OR4w.r[&5..v.mf\
00003080: 6a79 6e7d 7047 626e 795e 6b71 0372 2f76 jyn}pGbny^kq.r/v
00003090: 7f68 6b64 7562 ee5d e96d 4f62 6b44 451f .hkdub.].mObkDE.
000030a0: 2547 4343 3a20 2844 6562 6961 6e20 3130 %GCC: (Debian 10
And we can extract the first 65 bytes (130 hexadecimal digits) as follows:
$ xxd reflection | grep -A 4 3060: | xxd -r | tail -c 80
&8 yehgaxeT/OR4wr[&5.vmf\jyn}pGbny^kqr/vhkdub]mObkDE%GCC: (Debian 10
$ xxd reflection | grep -A 4 3060: | xxd -r | tail -c 80 | xxd -p
162638207965686761780f651f9b542f4f5234777f725b26352ebb76b16d
665c6a796e7d7047626e795e6b7103722f767f686b647562ee5de96d4f62
6b44451f254743433a202844656269616e203130
$ xxd reflection | grep -A 4 3060: | xxd -r | tail -c 80 | xxd -p | tr -d \\n
162638207965686761780f651f9b542f4f5234777f725b26352ebb76b16d665c6a796e7d7047626e795e6b7103722f767f686b647562ee5de96d4f626b44451f254743433a202844656269616e203130
$ xxd reflection | grep -A 4 3060: | xxd -r | tail -c 80 | xxd -p | tr -d \\n | cut -b-130
162638207965686761780f651f9b542f4f5234777f725b26352ebb76b16d665c6a796e7d7047626e795e6b7103722f767f686b647562ee5de96d4f626b44451f25
And we can perform similar operations to get the first 65 bytes of the ELF header without null bytes:
$ xxd reflection | head -30 | xxd -r | tr -d \\0 | xxd -p
7f454c46020101033e01601040f03a40380d401f1e0604404040d802d802
0803041803180318031c1c010104700670061001051010109d029d021001
0420202058015801100106e82de83de83db902d802100206f82df83df83d
e001e00108040438033803
$ xxd reflection | head -30 | xxd -r | tr -d \\0 | xxd -p | tr -d \\n
7f454c46020101033e01601040f03a40380d401f1e0604404040d802d8020803041803180318031c1c010104700670061001051010109d029d0210010420202058015801100106e82de83de83db902d802100206f82df83df83de001e00108040438033803
$ xxd reflection | head -30 | xxd -r | tr -d \\0 | xxd -p | tr -d \\n | cut -b-130
7f454c46020101033e01601040f03a40380d401f1e0604404040d802d8020803041803180318031c1c010104700670061001051010109d029d0210010420202058
Finally, we can apply XOR cipher to both payloads and extract the flag:
$ python3 -q
>>> from pwn import xor
>>> a = bytes.fromhex('7f454c46020101033e01601040f03a40380d401f1e0604404040d802d8020803041803180318031c1c010104700670061001051010109d029d0210010420202058')
>>> b = bytes.fromhex('162638207965686761780f651f9b542f4f5234777f725b26352ebb76b16d665c6a796e7d7047626e795e6b7103722f767f686b647562ee5de96d4f626b44451f25')
>>> xor(a, b)
b'ictf{did_you_know_that_function_names_are_just_pointers_to_code?}'
>>> exit()
$ ./reflection
>>> ictf{did_you_know_that_function_names_are_just_pointers_to_code?}
yes