[HacktheBox] SeeTheSharpFlag (feat. Xamarin)
배경지식
크로스 플랫폼 앱
하나의 언어로 Android와 iOS 환경에서 구동될 수 있는 앱을 지칭하며 대표적으로 아래와 같은 플랫폼이 존재한다.
Feat. 네이티브 앱, 웹앱, 하이브리드 앱
- Xamarin: 모노 프로젝트를 시작으로 마이크로소프트에서 무료 제공, C# 언어
- Flutter: 구글에서 제공, Dart 언어(C언어 문법 기반 Java, C#, Javascript 기능 추가)
- React Native: 메타(전 페이스북)에서 개발, React.js 문법
Xamarin 아키텍처
Java.*와 Android.* 라이브러리 사용이 가능하며, .NET 클래스 라이브러리를 사용하여 Linux 운영 체제 기능에 접근할 수 있다.
Xmarin.Android 애플리케이션은 C#에서 IL(중간 언어)로 컴파일 되며, 애플리케이션 시작 시 네이티브 어셈블리어로 *JIT(Just-in-Time) 컴파일이 진행된다.
*JIT: 애플리케이션 실행시점에서 컴파일 되어 메모리에 상주
자마린 컴파일 과정
- C/C++ 소스 코드를 플랫폼별 네이티브 라이브러리로 컴파일(so 파일 생성)
- Visual Studio 솔루션을 사용하여 네이티브 라이브러리 래핑(Xamarin Android관련 so 파일 -> dll 파일)
- .NET Rapper용 NuGet 패키지 압축 및 푸시(dll을 Nuget 패키지로 압축)
- 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와 관련된 함수나 문자열을 찾지 못하였다.
자마린 컴파일 및 동작 과정에서 실제 Android와 통신은 NeGet 패키지를 사용하기 때문에 dll 파일에 대한 분석이 필요하다.
dll 분석
파일 경로: /unknown/assemblies/
Flag와 관련있어보이는 dll 파일을 Hex Editor로 열면 XALZ 파일 시그니처 값을 확인할 수 있다.
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 파일을 IDA를 통해 실행한 결과, MainActivity에 존재하는 Native코드를 확인할 수 있다.
SeeTheSharpFlag.Android.dll 파일내에는 Flag 관련 정보가 존재하지 않아 SeeTheSharpFlag.dll 파일에 대한 압축 해제를 진행하였다.
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 포맷의 데이터가 출력되는 것을 확인할 수 있다.
앱을 실행시키고 해당 값을 입력하면 "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/