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
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.
The first file I check out is tizen-manaifext.xml
as this can give us a lot of information on the app.
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.
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
.
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/
Running exiftool
on it:
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.
Flag
Flag: n3ver_go1ng_to_recov3r@flare-on.com