Reflection
3 minutos de lectura
Se nos proporciona un archivo binario llamado 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
Si abrimos Ghidra y echamos un vistazo al código fuente en C descompilado, veremos la función main
:
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;
}
El programa espera que pongamos la flag y utiliza XOR para cifrar la flag y la compara con datos almacenados en el binario. Específicamente, la comparación se realiza con la cabecera ELF (los primeros 65 bytes, omitiendo los bytes nulos).
El símbolo global flag
está en la dirección 0x4060
:
$ readelf -s reflection | grep flag
68: 0000000000004060 65 OBJECT GLOBAL DEFAULT 25 flag
Podemos mirar los bytes con xxd
(pero hay que restar 0x1000
, ya que el binario no está cargado):
$ 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
Y podemos extraer los primeros 65 bytes (130 dígitos hexadecimales) como sigue:
$ 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
Y podemos realizar operaciones similares para obtener los primeros 65 bytes de la cabecera ELF sin bytes nulos:
$ 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
Finalmente, podemos aplicar XOR para cifrar ambos payloads y extraer la 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