Security/Mobile

[HacktheBox] SeeTheSharpFlag (feat. Xamarin)

Go0G 2022. 5. 27. 09:14

배경지식

크로스 플랫폼 앱

하나의 언어로 Android와 iOS 환경에서 구동될 수 있는 앱을 지칭하며 대표적으로 아래와 같은 플랫폼이 존재한다.

Feat. 네이티브 앱, 웹앱, 하이브리드 앱

  • Xamarin: 모노 프로젝트를 시작으로 마이크로소프트에서 무료 제공, C# 언어
  • Flutter: 구글에서 제공, Dart 언어(C언어 문법 기반 Java, C#, Javascript 기능 추가)
  • React Native: 메타(전 페이스북)에서 개발, React.js 문법

Xamarin 아키텍처

Java.*Android.* 라이브러리 사용이 가능하며, .NET 클래스 라이브러리를 사용하여 Linux 운영 체제 기능에 접근할 수 있다.

Xamarin 작동 방식

Xmarin.Android 애플리케이션은 C#에서 IL(중간 언어)로 컴파일 되며, 애플리케이션 시작 시 네이티브 어셈블리어로 *JIT(Just-in-Time) 컴파일이 진행된다. 

*JIT: 애플리케이션 실행시점에서 컴파일 되어 메모리에 상주

자마린 컴파일 과정

자마린 컴파일 단계

  1. C/C++ 소스 코드를 플랫폼별 네이티브 라이브러리로 컴파일(so 파일 생성)
  2. Visual Studio 솔루션을 사용하여 네이티브 라이브러리 래핑(Xamarin Android관련 so 파일 -> dll 파일)
  3. .NET Rapper용 NuGet 패키지 압축 및 푸시(dll을 Nuget 패키지로 압축)
  4. Xamarin 앱에서 Nuget 패키지 사용

모노

마이크로소프트에서 크로스 플랫폼(리눅스 배포판, macOS, 윈도우, 솔라리스, 안드로이드, PS3 등)에서 동작하는 프로그램을 위해 개발한 닷넷 프레임워크(.NET Framework)의 오픈소스 버전이다. 

dnSpy

.NET 어셈블러 에디터


문제

본 문제는 Xamarin을 이용하여 개발된 크로스 플랫폼 앱으로, Reference [6]를 참고하면 된다.

문자열을 입력 하고, CLICK ON ME 클릭 시 "Sorry. Not correct password" 라는 값을 출력한다.

애플리케이션 실행화면

 


분석

모노 관련 클래스를 import 하는 점, onCreate() 메소드가 네이티브로 구성되어 있고 자마린 관련 라이브러리를 사용하는 부분에서 자마린으로 개발된 애플리케이션임을 추측할 수 있다.

메인 엑티비티

라이브러리 파일을 분석하기 위해서는 APK 파일을 디컴파일 해야하는데, APK Tool을 사용하면 된다.

2021.09.12 - [Security/Mobile] - [Android analysis] 디컴파일 및 리패키징

 

자마린 관련 라이브러리 파일에서는 메인 엑티비티와 관련된 .rodata 만 존재하였으며, 다른 라이브러리에서 Flag와 관련된 함수나 문자열을 찾지 못하였다.

libxamarin-app.so 파일

자마린 컴파일 및 동작 과정에서 실제 Android와 통신은 NeGet 패키지를 사용하기 때문에 dll 파일에 대한 분석이 필요하다.

dll 분석

파일 경로: /unknown/assemblies/

Flag와 관련있어보이는 dll 파일을 Hex Editor로 열면 XALZ 파일 시그니처 값을 확인할 수 있다. 

Hex 값 확인 결과

Reference [6]을 통해 XALZ에 대한 내용을 참고할 수 있다. 20년도 5월에 APK 파일의 크기를 줄이기 위해 LZ4 압축을 도입하고 있으며, 디폴트로 이 값은 활성화되어 있다고 한다. 따라서 해당 dll 파일의 압축해제가 필요하다.

XALZ 압축 해제

Reference [7] github의 코드이다.

import lz4.block
import sys
import struct

def print_usage_and_exit(): 
    sys.exit("usage: ./command compressed-inputfile.dll uncompressed-outputfile.dll")

if __name__ == "__main__":
    if len(sys.argv) != 3:
        print_usage_and_exit()

    input_filepath = sys.argv[1]
    output_filepath = sys.argv[2]
    header_expected_magic = b'XALZ'
    
    with open(input_filepath, "rb") as xalz_file:
        data = xalz_file.read()
    
        if data[:4] != header_expected_magic:
            sys.exit("The input file does not contain the expected magic bytes, aborting ...")
    
        header_index = data[4:8]
        header_uncompressed_length = struct.unpack('<I', data[8:12])[0]
        payload = data[12:]
        
        print("header index: %s" % header_index)
        print("compressed payload size: %s bytes" % len(payload))
        print("uncompressed length according to header: %s bytes" % header_uncompressed_length)
        
        decompressed = lz4.block.decompress(payload, uncompressed_size=header_uncompressed_length)
                
        with open(output_filepath, "wb") as output_file:
            output_file.write(decompressed)
            output_file.close()
        print("result written to file")

파이썬에 필요 모듈 설치 후, 아래 명령어를 통해 디컴파일 output 파일을 획득한다.

디컴파일된 output 파일 획득

디컴파일

해당 output 파일을 IDA를 통해 실행한 결과, MainActivity에 존재하는 Native코드를 확인할 수 있다.

메인 엑티비티내 메소드명
output 파일 내 함수명

SeeTheSharpFlag.Android.dll 파일내에는 Flag 관련 정보가 존재하지 않아 SeeTheSharpFlag.dll 파일에 대한 압축 해제를 진행하였다.

SeeTheSharpFlag 압축해제

IDA를 통해 확인한 결과, SecretInput과 관련된 값을 확인할 수 있었으나 .NET에 대하여 디컴파일을 수행하지 못하였다. 

따라서 dnSpy를 이용하여 디컴파일을 수행한 뒤, 해당 함수에 접근하였다.

로직 접근 결과

디컴파일 결과, CreateDecryptor 함수(Referene [8] 참고)을 확인하면 암호화 키 값과 IV 값을 파라미터 값인 것을 확인할 수 있다. 또한, CryptoStream 클래스(Reference [9] 참고)를 이용하여 복호화를 진행하는 것을 알 수 있다.


PoC

사용자로 부터 입력 받은 값과 AES를 통해 복호화 한 값을 비교하여 일치한다면 "Congratz! You found the secret message" 메시지를 띄울 것이다.

C#(.NET)을 온라인으로 컴파일 할 수 있는 사이트(Reference [10])를 통해 복호화 로직을 구현한다.

using System;
using System.Text;
using System.Security.Cryptography;
using System.IO;					
public class Program
{
	public static void Main()
	{
		byte[] rgbKey = Convert.FromBase64String("6F+WgzEp5QXodJV+iTli4Q==");
		byte[] rgbIV = Convert.FromBase64String("DZ6YdaWJlZav26VmEEQ31A==");

		byte[] Encry_Data = Convert.FromBase64String("sjAbajc4sWMUn6CHJBSfQ39p2fNg2trMVQ/MmTB5mno=");

		AesManaged aesManaged = new AesManaged();
		ICryptoTransform cryptoTransform = aesManaged.CreateDecryptor(rgbKey, rgbIV);
		
		MemoryStream memoryStream = new MemoryStream(Encry_Data);
		CryptoStream cryptoStream = new CryptoStream(memoryStream, cryptoTransform, 0);
		StreamReader streamReader = new StreamReader(cryptoStream);
		Console.WriteLine(streamReader.ReadToEnd());
	}
}

실행결과

위 코드 실행 결과, Flag 포맷의 데이터가 출력되는 것을 확인할 수 있다.

AES 복호화 결과

앱을 실행시키고 해당 값을 입력하면 "Congratz! You found the secret message" 메시지를 확인할 수 있다.

앱내 실행 결과


Reference

더보기

[1] Xamarin 위키: https://namu.wiki/w/Dart(%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D%20%EC%96%B8%EC%96%B4)
[2] Flutter 위키: https://namu.wiki/w/Flutter(%ED%94%84%EB%A0%88%EC%9E%84%EC%9B%8C%ED%81%AC)
[3] React Native 위키: https://namu.wiki/w/React(%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC)?from=React%20Native#s-6.1
[4] 자마린 아키텍처: https://docs.microsoft.com/ko-kr/xamarin/android/internals/architecture
[5] 자마린 컴파일 단계: https://docs.microsoft.com/ko-kr/xamarin/cross-platform/cpp/
[5] 문제 파일: https://app.hackthebox.com/challenges/seethesharpflag
[6] 자마린 dll 압축 해제: https://www.x41-dsec.de/security/news/working/research/2020/09/22/xamarin-dll-decompression/
[7] lz 압축해제: https://github.com/x41sec/tools/blob/master/Mobile/Xamarin/Xamarin_XALZ_decompress.py
[8] .NET의 AES 암호화 함수 CreateDecryptor: https://docs.microsoft.com/ko-kr/dotnet/api/system.security.cryptography.aesmanaged.createdecryptor?view=net-6.0
[9] .NET의 AES 복호화 클래스 CryptoStream: https://docs.microsoft.com/ko-kr/dotnet/api/system.security.cryptography.cryptostream?view=net-6.0
[10] .NET Fiddle Online: https://dotnetfiddle.net/