Dylan's Blog

2020 Flare-On Challenge 5 Writeup: TKApp

Category: CTF

Challenge 5 - TKApp

Description

Now you can play Flare-On on your watch! As long as you still have an arm left to put a watch on, or emulate the watch’s operating system with sophisticated devloper tools.

Download - TKApp.7z

Contents

Tools Used

Solution

Intro

tl;dr: AES decrypt a ‘resource’ file packaged with the app by determining how and where the values used in the decryption are set.

In challenge 5, we’re given a single file, TKApp.tpk. I’m not too familiar with .tpk files and 5 seconds of Googling didn’t show anything too promising. However, the description and title of the challenge/file hints at a wearable app, so the first thing I try to do is unzip the file (which you can do with regular Android APKs).

Initial Analysis

After unzipping TKApp.tpk, a bunch of files and directories get extracted.

files

The first file I check out is tizen-manaifext.xml as this can give us a lot of information on the app.

AppManifest

As we can see on line 5, the executable we should look into is TKApp.dll (located in the bin directory). Furthermore, we can see that the type is a dotnet application, lets confirm this.

FileTypeDetection

Opening TKApp.dll in Detect It Easy confirms this is a .Net executable.

Reversing with dnSpy

After opening TKApp.dll with dnSpy and exploring the code, I find the following code in MainPage.

private void PedDataUpdate(object sender, PedometerDataUpdatedEventArgs e)
		{
			if (e.StepCount > 50U && string.IsNullOrEmpty(App.Step))
			{
				App.Step = Application.Current.ApplicationInfo.Metadata["its"];
			}
			if (!string.IsNullOrEmpty(App.Password) && !string.IsNullOrEmpty(App.Note) && !string.IsNullOrEmpty(App.Step) && !string.IsNullOrEmpty(App.Desc))
			{
				HashAlgorithm hashAlgorithm = SHA256.Create();
				byte[] bytes = Encoding.ASCII.GetBytes(App.Password + App.Note + App.Step + App.Desc);
				byte[] first = hashAlgorithm.ComputeHash(bytes);
				byte[] second = new byte[]
				{
					50,
					148,
					76,
					233,
					110,
					199,
					228,
					72,
					114,
					227,
					78,
					138,
					93,
					189,
					189,
					147,
					159,
					70,
					66,
					223,
					123,
					137,
					44,
					73,
					101,
					235,
					129,
					16,
					181,
					139,
					104,
					56
				};
				if (first.SequenceEqual(second))
				{
					this.btn.Source = "img/tiger2.png";
					this.btn.Clicked += this.Clicked;
					return;
				}
				this.btn.Source = "img/tiger1.png";
				this.btn.Clicked -= this.Clicked;
			}
		}

Looking at this line:

byte[] bytes = Encoding.ASCII.GetBytes(App.Password + App.Note + App.Step + App.Desc);

It looks like we need 4 pieces of information, App.Password, App.Note, App.Step, and App.Desc.

Finding App.Step

Luckily, we can see that App.Step gets set a few lines above with information from the apps metadata and the its key.

If we go back to the manifest file, we can see that the metadata key its has a value of magic, giving us App.Step.

AppStep

App.Password = 
App.Note = 
App.Step = magic
App.Desc = 

Flag Function?

While looking for the other values, I came across an interesting function, GetImage which does some interesting things.

private bool GetImage(object sender, EventArgs e)
		{
			if (string.IsNullOrEmpty(App.Password) || string.IsNullOrEmpty(App.Note) || string.IsNullOrEmpty(App.Step) || string.IsNullOrEmpty(App.Desc))
			{
				this.btn.Source = "img/tiger1.png";
				this.btn.Clicked -= this.Clicked;
				return false;
			}
			string text = new string(new char[]
			{
				App.Desc[2],
				App.Password[6],
				App.Password[4],
				App.Note[4],
				App.Note[0],
				App.Note[17],
				App.Note[18],
				App.Note[16],
				App.Note[11],
				App.Note[13],
				App.Note[12],
				App.Note[15],
				App.Step[4],
				App.Password[6],
				App.Desc[1],
				App.Password[2],
				App.Password[2],
				App.Password[4],
				App.Note[18],
				App.Step[2],
				App.Password[4],
				App.Note[5],
				App.Note[4],
				App.Desc[0],
				App.Desc[3],
				App.Note[15],
				App.Note[8],
				App.Desc[4],
				App.Desc[3],
				App.Note[4],
				App.Step[2],
				App.Note[13],
				App.Note[18],
				App.Note[18],
				App.Note[8],
				App.Note[4],
				App.Password[0],
				App.Password[7],
				App.Note[0],
				App.Password[4],
				App.Note[11],
				App.Password[6],
				App.Password[4],
				App.Desc[4],
				App.Desc[3]
			});
			byte[] key = SHA256.Create().ComputeHash(Encoding.ASCII.GetBytes(text));
			byte[] bytes = Encoding.ASCII.GetBytes("NoSaltOfTheEarth");
			try
			{
				App.ImgData = Convert.FromBase64String(Util.GetString(Runtime.Runtime_dll, key, bytes));
				return true;
			}
			catch (Exception ex)
			{
				Toast.DisplayText("Failed: " + ex.Message, 1000);
			}
			return false;
		}

It uses the 4 values and builds a new string out of them then calculates the SHA256 hash of it and assigns it to key. It then gets the bytes from the string, NoSaltOfTheEarth and assigns it to the variable bytes.

Finally, it passes the file Runtime.dll, key, and bytes to the function GetString, decodes the result from base64 and assigns it to App.ImgData.

I had a hunch that this function would give the flag, so given this new information, I add Runtime.dll to the list of things I need to find.

Finding App.Password

I come across the IsPasswordCorrect function in the UnlockPage class, which introduced the TkData.Password variable.

private bool IsPasswordCorrect(string password)
		{
			return password == Util.Decode(TKData.Password);
		}

Following TKData.Password brings me to the following byte array:

public static byte[] Password = new byte[]
		{
			62,
			38,
			63,
			63,
			54,
			39,
			59,
			50,
			39
		};

Next I follow the Util.Decode function to see how these bytes are decoded:

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

The Decode function is simple, it takes in a byte array and XOR’s each byte with 83 and converts the result to a char before appending it to the text variable. I quickly threw together a Python script to solve this:

password = [62,38,63,63,54,39,59,50,39]
text = ''.join(chr(int(c)^83) for c in password)
print(text)

mullethat

We now have App.Password.

App.Password = mullethat
App.Note = 
App.Step = magic
App.Desc = 

Finding App.Note

Continuing my search for App.Note leads me to the SetupList function.

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

As the name suggests, this sets up the todo list by adding a bunch of different todo’s in the format of Name, Note, Done, where Name and Note are Strings and Done is a boolean.

At the end of the function, we see a foreach loop that loops through the todo list and if Done is False, adds it to a new list.

Finally, App.Note is set to the Note value of the first todo item in the new list, which would be: keep steaks for dinner

We now have App.Note.

App.Password = mullethat
App.Note = keep steaks for dinner
App.Step = magic
App.Desc = 

Finding App.Desc

Finally, looking for where App.Desc is assigned brings me to the IndexPage_CurrentPageChanged function.

private void IndexPage_CurrentPageChanged(object sender, EventArgs e)
		{
			if (base.Children.IndexOf(base.CurrentPage) == 4)
			{
				using (ExifReader exifReader = new ExifReader(Path.Combine(Application.Current.DirectoryInfo.Resource, "gallery", "05.jpg")))
				{
					string desc;
					if (exifReader.GetTagValue<string>(ExifTags.ImageDescription, out desc))
					{
						App.Desc = desc;
					}
					return;
				}
			}
			App.Desc = "";
		}

All we need to know for this function is that it reads the exif data from the picture, 05.jpg and assigns the ImageDescription exif tag value to App.Desc.

05.jpg is located in res/gallery/

Gallery

05.jpg

Running exiftool on it:

ExifData

So App.Desc has a value of water.

App.Password = mullethat
App.Note = keep steaks for dinner
App.Step = magic
App.Desc = water

Putting Everything Together

Runtime.dll is in Resources/TKApp.Runtime.resources/ (if using dnSpy). I save this to my desktop for later.

I wrote a Python script to get the calculated string in the GetImage function using our new found values.

Desc = "water"
Password = "mullethat"
Note = "keep steaks for dinner"
Step = "magic"

x = [
Desc[2],
Password[6],
Password[4],
Note[4],
Note[0],
Note[17],
Note[18],
Note[16],
Note[11],
Note[13],
Note[12],
Note[15],
Step[4],
Password[6],
Desc[1],
Password[2],
Password[2],
Password[4],
Note[18],
Step[2],
Password[4],
Note[5],
Note[4],
Desc[0],
Desc[3],
Note[15],
Note[8],
Desc[4],
Desc[3],
Note[4],
Step[2],
Note[13],
Note[18],
Note[18],
Note[8],
Note[4],
Password[0],
Password[7],
Note[0],
Password[4],
Note[11],
Password[6],
Password[4],
Desc[4],
Desc[3]]

print(''.join(x))

the kind of challenges we are gonna make here

Now that we have everything we need, let’s revist the ending of the GetImage function:

        byte[] key = SHA256.Create().ComputeHash(Encoding.ASCII.GetBytes(text));
		byte[] bytes = Encoding.ASCII.GetBytes("NoSaltOfTheEarth");
		try
		{
			App.ImgData = Convert.FromBase64String(Util.GetString(Runtime.Runtime_dll, key, bytes));
			return true;
		}

We have one last function we need to explore before solving this, GetString.

GetString Function

public static string GetString(byte[] cipherText, byte[] Key, byte[] IV)
{
	string result = null;
	using (RijndaelManaged rijndaelManaged = new RijndaelManaged())
	{
		rijndaelManaged.Key = Key;
		rijndaelManaged.IV = IV;
		ICryptoTransform cryptoTransform = rijndaelManaged.CreateDecryptor(rijndaelManaged.Key, rijndaelManaged.IV);
		using (MemoryStream memoryStream = new MemoryStream(cipherText))
		{
			using (CryptoStream cryptoStream = new CryptoStream(memoryStream, cryptoTransform, 0))
			{
				using (StreamReader streamReader = new StreamReader(cryptoStream))
				{
					result = streamReader.ReadToEnd();
				}
			}
		}
	}
	return result;
}

Looks like GetString does AES decryption on Runtime.dll with key as the Key and the bytes array as the IV.

This is where CyberChef comes in handy. I calculate the SHA256 of the kind of challenges we are gonna make here and encode NoSaltOfTheEarth as Hex, which gives the 2 following values.

248e9d7323a1a3c5d5b3283dcb2b40211a14415b6dcd2a86181721fd74b4befd

4e6f53616c744f665468654561727468

Finally, I upload Runtime.dll, convert to Hex, AES decrypt with the values I just calculated, Base64 decode, and finally render the output as an image, which gives the flag.

Solution

Flag

Flag: n3ver_go1ng_to_recov3r@flare-on.com