Writeup: FlareOn 2020: 005 - TKApp
1. TLDR
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:
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:
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ę: