Writeup: FlareOn 2020: 005 - TKApp

Task description

1. TLDR

report graph

2. Input data

The challenge file is here. Password: flare.

The subject of the task is a file TKApp.tpk. It is known from the content of the task that it is a file containing smartwatch software.

3. Inspection of TKApp.tpk file

I verified the file using file tool:

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

TPK file should be an application designed for the Tizen operating system.

After running in the emulator, the application looked like this:

TKApp main

4. Decompilation

In order to prepare the application for analysis, I unpacked the archive containing the application:

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

I looked at the archive structure:

$ 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

The file TKApp.dll immediately caught my attention:

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

I used ILSpy to decompile the TKApp.dll file and saved the project code TKApp (1.0.0.0) to analyze it.

5. Static analysis

Static analysis revealed two pieces of code responsible for reading the flag:

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;
	}

and

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();
				}
                ...

An encrypted flag (graphic file) was in the Runtime.dll file embedded in the application. The Rijndael algorithm in CBC mode with a block length of 128 bits and a 256-bit key was used to encrypt the flag. IV was the ASCII string NoSaltOfTheEarth. The 256-bit key was derived from the SHA-256 hash value computed for the ASCII character string, which was composed of the selected variable elements: Password, Step, Note and Desc.

5.1 Static field Password

The password was stored in the byte array:

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

When the application was launched, the password entered by the user was compared with the stored string, decoded by the Decode(byte[]) method:

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

In order to decode the password, I ran a script in python:

#! /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()

This revealed the correct password: '’ mullethat```. Once entered, it allowed access to the application:

TKApp signed in

5.2 Static field Step

The field ‘Step’ was set by executing the code:

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"];
        }
        ...
}

So it was necessary to read the stored metadata:

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

So the correct field value is magic.

5.3 Static field Note

The Note field was set by executing the SetupList() method:

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;
    }
    ...
}

So the correct value of the field Note: keep steaks for dinner

5.4 Static field Desc

The field Desc was set while handling the CurrentPageChanged event:

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 = "";
        }
    }
    ...
}

So it was necessary to read the meta tag Image Description:

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

So the correct value of the field Desc: water

6. Reading the flag

To read the flag, I have modified the MainPage class by adding the SetImage() method:

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));
    }
    ...
}

Then I ran the 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();
    }
}

The result of executing the program was saving the flag.jpg file, which contained the flag:

Flag