Arno HTB

naibu3 ·

Hoy traigo la resolución de un challenge Fácil de HackTheBox, un reto bastante disfrutón aunque tengo sensaciones encontradas ya que la resolución se acerca más a la categoría de reversing que a la de mobile. Aún así buen reto e interesante para aprender acerca de apps compiladas con Unity.

Overview

La APK es un juego Unity compilado con IL2CPP dicho componente permite una traducción a un lenguaje intermedio para permitir compilación en ciertos dispositivos (reemplaza a la antigua mono). La aplicación no es especialmente útil, al pulsar un botón, muestra frases aleatorias.


Image

Extraer ficheros del APK

Comenzamos extrayendo los ficheros de la app y echando un vistazo:

apktool d Arno.apk

En este punto me encontraba bastante perdido ya que con un primer vistazo al código no encontré gran cosa. Sin embargo, al ver que teníamos un libil2cpp.so y que estaba compilado con Unity comprendí que la parte importante estaba en dichas librerías.

Ejecutar Cpp2IL

Para la extracción y análisis del código de la librería utilicé Cpp2Il una herramienta alternativa a Il2CppDumper que me dió problemas en debian.

./Cpp2IL-2022.1.0-pre-release.20-Linux --game-path Arno.apk --output-as dll_il_recovery

Esto genró un fichero FlagControl.cs con el siguiente contenido:

public class FlagControl : MonoBehaviour
{
    public GameObject textField; //Field offset: 0x20
    public List<String> quotes; //Field offset: 0x28

    public FlagControl() { }

    public string DecryptFlag(Byte[] key, Byte[] iv, Byte[] encryptedData) { }

    public Byte[] GetFlag() { }

    public Byte[] GetIV() { }

    public Byte[] GetKey() { }

    public void PopulateQuotes() { }

    public void ShowQuote() { }

    private void Start() { }
}

Como vemos la flag está siendo encriptada probablemente con AES. Además tenemos un archivo _PrivateImplementationDetails_.cs, que contiene:

[CompilerGenerated]
internal sealed class <PrivateImplementationDetails>
{
    private struct __StaticArrayInitTypeSize=16 {}
    private struct __StaticArrayInitTypeSize=17 {}
    private struct __StaticArrayInitTypeSize=32 {}
    private struct __StaticArrayInitTypeSize=38 {}
    private struct __StaticArrayInitTypeSize=48 {}

    internal static readonly __StaticArrayInitTypeSize=38 2694407F2358DB433C52827F5FB5F1567F7EB2FA89053E8D6B162179C6DAAE7B; //Field offset: 0x0 || Has Field RVA (address hidden for diffability) || Field RVA Decoded (hex blob): [00 00 00 01 00 00 00 1E 5C 41 73 73 65 74 73 5C 53 63 72 69 70 74 73 5C 46 6C 61 67 43 6F 6E 74 72 6F 6C 2E 63 73]
    internal static readonly __StaticArrayInitTypeSize=16 36CB71326BD2E601D33D1ECCA3CD621FA05ED31A26608CF775331557E461F8EB; //Field offset: 0x26 || Has Field RVA (address hidden for diffability) || Field RVA Decoded (hex blob): [BB F5 A8 D7 06 6F D5 1B 43 D9 59 C0 44 36 5C DF]
    internal static readonly __StaticArrayInitTypeSize=17 43563FB6E1278B5116042C66FC24F6F5641E3D931363433DB0CE8626281DA4BB; //Field offset: 0x36 || Has Field RVA (address hidden for diffability) || Field RVA Decoded (hex blob): [00 00 00 00 0C 7C 46 6C 61 67 43 6F 6E 74 72 6F 6C]
    internal static readonly __StaticArrayInitTypeSize=48 6F30F150623A66022280A433383C0BC30B7A0B571923A2762FD18DC078AE8A28; //Field offset: 0x47 || Has Field RVA (address hidden for diffability) || Field RVA Decoded (hex blob): [13 EB F3 95 3A 9B 8C 13 C6 E5 47 1F 7E EA A0 17 4B 6C 1F AC 41 80 20 02 DA 16 EB 32 FA 88 F6 3C 57 01 85 A8 BC 21 8D 9E F3 AC 03 E2 18 D3 0C 55]
    internal static readonly __StaticArrayInitTypeSize=32 703A5CD6F009A97282036DDF638EFAE313527A0F5709E6C83CC971FEF17B59EB; //Field offset: 0x77 || Has Field RVA (address hidden for diffability) || Field RVA Decoded (hex blob): [CF DC 33 CC BE E6 DC 77 5B A1 46 B9 5D 0F EA 6C BC C3 EE 3E 5E 76 53 1D 2C D7 9C 14 07 58 F0 8D]
}

Con esto tenemos los tamaños de cada array:

GetKey() → 32 bytes
GetIV() → 16 bytes
GetFlag() → 48 bytes

Estos tamaños encajan con:

AES-256 → key de 32 bytes
CBC → IV de 16 bytes
ciphertext múltiplo de 16 → 48 bytes (3 bloques AES)

Cuando .NET compila arrays constantes, a menudo los mete en <PrivateImplementationDetails> y luego los rellena con RuntimeHelpers.InitializeArray.

Con el siguiente comando podemos extraerlos fácilmente:

sed -n '1,220p' cpp2il_out/DiffableCs/Assembly-CSharp/_PrivateImplementationDetails_.cs

Ahí aparecen blobs hex ya decodificados. Los tres que nos interesan son:

IV (16 bytes):
BB F5 A8 D7 06 6F D5 1B 43 D9 59 C0 44 36 5C DF

Ciphertext (48 bytes):
13 EB F3 95 3A 9B 8C 13 C6 E5 47 1F 7E EA A0 17
4B 6C 1F AC 41 80 20 02 DA 16 EB 32 FA 88 F6 3C
57 01 85 A8 BC 21 8D 9E F3 AC 03 E2 18 D3 0C 55

Key (32 bytes):
CF DC 33 CC BE E6 DC 77 5B A1 46 B9 5D 0F EA 6C
BC C3 EE 3E 5E 76 53 1D 2C D7 9C 14 07 58 F0 8D

Descifrar (AES-256-CBC + PKCS7)

Con python podemos descifrar fácilmente:

from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad

key = bytes.fromhex("CFDC33CCBEE6DC775BA146B95D0FEA6CBCC3EE3E5E76531D2CD79C140758F08D")
iv  = bytes.fromhex("BBF5A8D7066FD51B43D959C044365CDF")
ct  = bytes.fromhex(
    "13EBF3953A9B8C13C6E5471F7EEAA017"
    "4B6C1FAC41802002DA16EB32FA88F63C"
    "570185A8BC218D9EF3AC03E218D30C55"
)

cipher = AES.new(key, AES.MODE_CBC, iv)
pt = unpad(cipher.decrypt(ct), 16)
print(pt.decode())

Y finalmente obtendríamos la flag:

HTB{c@n_90u_533_w17h_90ur_5h@rp_e9e5}

Al final un reto más de reversing que de explotación como tal, pero que nos da una pista grande para próximas ocasiones, ya que en CTFs con Unity e IL2CPP, podemos probar a mirar en librerías con el método que hemos seguido aquí.

¡Nos vemos en el próximo challenge!

Twitter, Facebook