Challenge 05

5 - TKApp

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 developer tools.

Đề bài cho 1 file .tpk, sau 1 lúc google, mình phát hiện đây là file chạy trên hệ điều hành Tizen. Tải giả lập Tizen Studio về, chạy thử file, thì đây trông giống 1 app trên đồng hồ thông minh, có các chức năng định vị, đo nhịp tim, v.v…

Bài này không cần chạy file, chỉ cần phân tích tĩnh cũng có thể giải được.

Hai byte đầu của file .tpk là PK, nên mình đổi định dạng file thành “.zip” rồi giải nén.

Dùng “Detect it easy”:

File được nhận diện là .net, dùng “dnspy” để mở lên và phân tích.

dnspy là một tool dùng để phân tích các file .net.

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[] { /* a lot of bytes here */   };
        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;
    }
}
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[]
    {
        // a lot of chars here, take from App.Password/Note/Step/Desc
    });
    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;
}

Về cơ bản, chương trình lấy 4 string App.Password, App.Note, App.Step, App.Desc nối lại với nhau, tính SHA256 rồi lấy hash đó để giải mã file “Runtime.dll” với thuật toán AES256.

Để tìm xem App.Password được dùng ở chỗ nào, ta chuột phải vào App.Password trong hàm PedDataUpdate, chọn “Analyze”:

Sau đó ta có thể thấy nó được “set” (tức là được gán) ở hàm OnLoginButtonClicked:

Ta đến hàm này xem code:

private async void OnLoginButtonClicked(object sender, EventArgs e)
{
    if (this.IsPasswordCorrect(this.passwordEntry.Text))
    {
        App.IsLoggedIn = true;
        App.Password = this.passwordEntry.Text;
        base.Navigation.InsertPageBefore(new MainPage(), this);
        await base.Navigation.PopAsync();
    }
    else
    {
        Toast.DisplayText("Unlock failed!", 2000);
        this.passwordEntry.Text = string.Empty;
    }
}

Hàm này lại dùng hàm IsPasswordCorrect, ta tới hàm này xem:

public static byte[] Password = new byte[] {62, 38, 63, 63, 54, 39, 59, 50, 39};
private bool IsPasswordCorrect(string password)
{
    return password == Util.Decode(TKData.Password);
}
public static string Decode(byte[] e)
{
    string text = "";
    foreach (byte b in e)
    {
        text += Convert.ToChar((int)(b ^ 83)).ToString();
    }
    return text;
}

Với App.Password, ta tính ra được chuỗi “mullethat”. Ta tiếp tục làm tương tự (chuột phải -> analyze với App.Note, App.Step, App.Desc).

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

Với App.Note, ta có thể thấy được nó là chuỗi “keep steaks for dinner”.

private void PedDataUpdate(object sender, PedometerDataUpdatedEventArgs e)
{
    if (e.StepCount > 50U && string.IsNullOrEmpty(App.Step))
    {
        App.Step = Application.Current.ApplicationInfo.Metadata["its"];
    }
    // truncated ...
}

Với App.Step, nó chính là chuỗi “magic”.

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

Với App.Desc, ta có thể cài đặt thư viện ExifLib, viết lại một đoạn code như trên và lấy được chuỗi “water”, còn file ảnh “05.jpg” có thể lấy từ các file có được sau khi giải nén TKApp.tpk.

Tất cả mọi thứ đã có, ta viết đoạn script để decrypt file:

import hashlib
from base64 import b64decode
from Crypto.Cipher import AES

def sha256(text: str) -> tuple:
    h = hashlib.sha256()
    h.update(text.encode())
    return h.hexdigest(), h.digest()

def aes_dec(data: bytes, key: bytes, iv: bytes) -> bytes:
    key_size = 32
    assert len(key) == key_size and len(iv) == 16
    cipher = AES.new(key, AES.MODE_CBC, iv=iv)
    s = cipher.decrypt(data)
    return s[:-ord(s[len(s)-1:])]

if __name__ == '__main__':
    Password = "mullethat"
    Note ="keep steaks for dinner"
    Step = "magic"
    Desc = "water"
    
    final = Password + Note + Step + Desc
    
    text = [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]
    ]
    text = ''.join(text)
    data = b''
    with open('Runtime.dll', 'rb') as f:
        data = f.read()
    dec = aes_dec(data, sha256(text)[1], b'NoSaltOfTheEarth')
    dec = b64decode(dec)
    with open('out.bin', 'wb') as f:
        f.write(dec)
        print ('[+] Done')

Sau khi chạy xong đoạn code trên ta được 1 file mới, có chứa JFIF trong phần header.

Đổi tên file mới thành “out.jpg”, ta được ảnh sau:

n3ver_go1ng_to_recov3r@flare-on.com

[+] Source code dùng để giải cho tất cả các bài nằm ở đây