Writeup: FlareOn 2020: 005 - TKApp

Task description

1. TLDR

report graph

2. Dane wejściowe

Plik z zadaniem znajduje się tutaj. Hasło: flare.

Przedmiot zadania stanowi plik TKApp.tpk. Z treści zadania wiadomo, że jest to plik zawierający oprogramowanie na smartwatch.

3. Inspekcja pliku TKApp.tpk

Zweryfikowałem plik narzędziem file:

$ file TKApp.tpk
TKApp.tpk: Zip archive data, at least v2.0 to extract

Plik TPK powinien być aplikacją przeznaczoną dla systemu operacyjnego Tizen.

Po uruchomieniu w emulatorze, aplikacja prezentowała się jak poniżej:

TKApp main

4. Dekompilacja

W celu przygotowania aplikacji do analizy, rozpakowałem archiwum zawierające aplikację:

$ unar TKApp.tpk -o TKApp_unpacked
...
Successfully extracted to "TKApp_unpacked/TKApp".

Przejrzałem strukturę archiwum:

$ tree .
.
└── TKApp
    ├── author-signature.xml
    ├── bin
    │   ├── ExifLib.Standard.dll
    │   ├── Tizen.Wearable.CircularUI.Forms.dll
    │   ├── Tizen.Wearable.CircularUI.Forms.Renderer.dll
    │   ├── TKApp.dll
    │   ├── Xamarin.Forms.Core.dll
    │   ├── Xamarin.Forms.Platform.dll
    │   ├── Xamarin.Forms.Platform.Tizen.dll
    │   └── Xamarin.Forms.Xaml.dll
    ├── lib
    ├── res
    │   ├── gallery
    │   │   ├── 01.jpg
    │   │   ├── 02.jpg
    │   │   ├── 03.jpg
    │   │   ├── 04.jpg
    │   │   └── 05.jpg
    │   └── img
    │       ├── img.png
    │       ├── tiger1.png
    │       ├── tiger2.png
    │       └── todo.png
    ├── shared
    │   └── res
    │       └── TKApp.png
    ├── signature1.xml
    ├── tizen-manifest.xml
    └── TKApp.deps.json

8 directories, 22 files

Moją uwagę natychmiast zwrócił plik TKApp.dll:

$ file TKApp/bin/TKApp.dll                                                                          
TKApp/bin/TKApp.dll: PE32 executable (console) Intel 80386 Mono/.Net assembly, for MS Windows

Do dekompilacji pliku TKApp.dll wykorzystałem ILSpy i zapisałem kod projektu TKApp (1.0.0.0) w celu przeprowadzania jego analizy.

5. Analiza kodu

Analiza statyczna pozwoliła ujawnić dwa fragmenty kodu odpowiedzialne za odczyt flagi:

private bool GetImage(object sender, EventArgs e)
{
	if (string.IsNullOrEmpty(App.Password) || string.IsNullOrEmpty(App.Note) || string.IsNullOrEmpty(App.Step) || string.IsNullOrEmpty(App.Desc))
	{
		btn.Source = "img/tiger1.png";
		btn.Clicked -= Clicked;
		return false;
	}
	string text = new string(new char[45]
	{
		App.Desc[2],
		App.Password[6],
		App.Password[4],
		App.Note[4],
        ...
		App.Step[4],
		...
		App.Desc[3]
	});
	byte[] key = SHA256.Create().ComputeHash(Encoding.get_ASCII().GetBytes(text));
	byte[] bytes = Encoding.get_ASCII().GetBytes("NoSaltOfTheEarth");
	try
	{
		App.ImgData = Convert.FromBase64String(Util.GetString(Runtime.Runtime_dll, key, bytes));
		return true;
	}

oraz

public static string GetString(byte[] cipherText, byte[] Key, byte[] IV)
{
	string text = null;
	RijndaelManaged val = (RijndaelManaged)(object)new RijndaelManaged();
	try
	{
		((SymmetricAlgorithm)(object)val).Key = Key;
		((SymmetricAlgorithm)(object)val).IV = IV;
		ICryptoTransform cryptoTransform = ((SymmetricAlgorithm)(object)val).CreateDecryptor(((SymmetricAlgorithm)(object)val).Key, ((SymmetricAlgorithm)(object)val).IV);
		MemoryStream val2 = (MemoryStream)(object)new MemoryStream(cipherText);
		try
		{
			using (CryptoStream cryptoStream = new CryptoStream((Stream)(object)val2, cryptoTransform, CryptoStreamMode.Read))
			{
				StreamReader val3 = (StreamReader)(object)new StreamReader((Stream)(object)cryptoStream);
				try
				{
					return ((TextReader)val3).ReadToEnd();
				}
                ...

Zaszyfrowana flaga (plik graficzny) znajdowała się w pliku Runtime.dll osadzonym w aplikacji. Do zaszyfrowania flagi wykorzystano algorytm Rijndael w trybie CBC z blokiem o długości 128 bitów i 256-bitowym kluczem. IV stanowił ciąg znaków ASCII NoSaltOfTheEarth. 256-bitowy klucz był wyznaczany na podstawie wartości skrótu SHA-256 obliczanego dla ciągu znaków ASCII, który był zbudowany z wybranych elementów zmiennych: Password, Step, Note oraz Desc.

5.1 Statyczne pole Password

Hasło było przechowywane w tablicy bajtów:

internal class TKData
{
	public static byte[] Password = new byte[9]
	{
            62, 38, 63, 63, 54, 39, 59, 50, 39
	};
        ...
}

Przy uruchomieniu aplikacji, wprowadzone przez użytkownika hasło było porównywane z przechowywanym, zdekodowanym przez metodę Decode(byte[]), ciągiem:

public static string Decode(byte[] e)
{
    string text = "";
    foreach (byte b in e)
    {
        text += Convert.ToChar(b ^ 0x53).ToString();
    }
    return text;
}

W celu zdekodowania hasła wykonałem skrypt w pythonie:

#! /usr/bin/python3

def main():
    password = [62, 38, 63, 63, 54, 39, 59, 50, 39]
    key = [0x53]*len(password)
    plaintext = ''.join([chr(p^k) for p,k in zip(password,key)])
    print(plaintext)

if __name__ == "__main__":
    main()

W ten sposób zostało ujawnione prawidłowe hasło: mullethat. Po wprowadzeniu, pozwalało ono na uzyskanie dostępu do aplikacji:

TKApp signed in

5.2 Statyczne pole Step

Pole Step było ustawiane poprzez wykonanie kodu:

public class MainPage : CirclePage
{
    ...
    private void PedDataUpdate(object sender, PedometerDataUpdatedEventArgs e)
    {
        if (e.get_StepCount() > 50 && string.IsNullOrEmpty(App.Step))
        {
        	App.Step = Application.get_Current().get_ApplicationInfo().get_Metadata()["its"];
        }
        ...
}

Należało więc odczytać przechowywane metadane:

$ grep its TKApp/tizen-manifest.xml                                                         
        <metadata key="its" value="magic" />

Zatem prawidłowa wartość pola to magic.

5.3 Statyczne pole Note

Pole Note było ustawiane poprzez wykonanie metody SetupList():

public class TodoPage : CirclePage
{
    public class Todo
    {
    	public string Name { get; set; }
    
    	public string Note { get; set; }
    
    	public bool Done { get; set; }
    
    	public Todo(string Name, string Note, bool Done)
    	{
    		this.Name = Name;
    		this.Note = Note;
    		this.Done = Done;
    	}
    }
    ...
    private void SetupList()
    {
        List<Todo> list = new List<Todo>();
    	if (!isHome)
    	{
            list.Add(new Todo("go home", "and enable GPS", Done: false));
    	}
    	else
    	{
            Todo[] collection = new Todo[5]
            {
                new Todo("hang out in tiger cage", "and survive", Done: true),
                new Todo("unload Walmart truck", "keep steaks for dinner", Done: false),
                new Todo("yell at staff", "maybe fire someone", Done: false),
                new Todo("say no to drugs", "unless it's a drinking day", Done: false),
                new Todo("listen to some tunes", "https://youtu.be/kTmZnQOfAF8", Done: true)
            };
            list.AddRange(collection);
    	}
    	List<Todo> list2 = new List<Todo>();
    	foreach (Todo item in list)
    	{
            if (!item.Done)
            {
                list2.Add(item);
            }
    	}
    	mylist.ItemsSource = list2;
    	App.Note = list2[0].Note;
    }
    ...
}

Zatem prawidłowa wartość pola Note: keep steaks for dinner

5.4 Statyczne pole Desc

Pole Desc było ustawiane w trakcie obsługi zdarzenia CurrentPageChanged:

public class GalleryPage : IndexPage
{
    ...
    private void IndexPage_CurrentPageChanged(object sender, EventArgs e)
    {
        if (base.Children.IndexOf(base.CurrentPage) == 4)
        {
            using (ExifReader exifReader = new ExifReader(Path.Combine(Application.get_Current().get_DirectoryInfo(get_Resource(), "gallery", "05.jpg")))
            {
                if (exifReader.GetTagValue(ExifTags.ImageDescription, out string result))
                {
                    App.Desc = result;
                }
            }
    	}
        else
        {
            App.Desc = "";
        }
    }
    ...
}

Należało więc odczytać metaznacznik Image Description:

$ exiftool TKApp/res/gallery/05.jpg | grep Image\ Description
Image Description               : water

Zatem prawidłowa wartość pola Desc: water

6. Odczytanie flagi

W celu odczytania flagi zmodyfikowałem klasę MainPage poprzez dodanie metody SetImage():

public class MainPage : CirclePage
{
    ...
    public static void SetImage()
    {
        string text = new string(new char[45]
        {
            App.Desc[2],
            App.Password[6],
            ...
            App.Desc[4],
            App.Desc[3]
        });
        byte[] key = SHA256.Create().ComputeHash(Encoding.ASCII.GetBytes(text));
        byte[] bytes = Encoding.ASCII.GetBytes("NoSaltOfTheEarth");
        byte[] runtime = File.ReadAllBytes(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "res", "Runtime.dll"));
        App.ImgData = Convert.FromBase64String(Util.GetString(runtime, key, bytes));
    }
    ...
}

Następnie uruchomiłem program:

class Program
{
    private static void SetUp()
    {
        App.Password = "mullethat";
        App.Step = "magic";
        App.Note = "keep steaks for dinner";
        App.Desc = "water";
    }
    private static void SaveFlag()
    {
        var flagPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "flag.jpg");
        File.WriteAllBytes(flagPath, App.ImgData);
    }
    static void Main(string[] args)
    {
        SetUp();
        MainPage.SetImage();
        SaveFlag();
    }
}

Skutkiem wykonania programu, było zapisanie pliku flag.jpg, który zawierał flagę:

Flag