Home (mobilehacking.kr) Mobile Baby Analysis4
Post
Cancel

(mobilehacking.kr) Mobile Baby Analysis4

처음으로 풀어보는 ios 앱 리버싱 문제이다.
mac 환경이 없어서 에뮬레이터조차 돌릴 수 없어 못 풀고 있었는데, 맥북을 얻게 되어 한 번 풀어봤다.
정적 분석 툴은 IDA, 시뮬레이터는 mac book의 xcode 시뮬레이터를 이용했다.

우선 기본 정보를 알기 위해서는 ipa앱을 unzip한 다음 내용을 볼 수 있다고 한다.

이렇게 보면 분석해야 하는 파일은 일단 Info.plist를 슥 본 다음
에뮬로 앱을 돌리면서 코드를 봐야할 것 같다.

1
plutil -convert xml1 Info.plist -o Info_readable.plist

바이너리 파일이라 xml로 바꿔서 보기좋게 보면

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>BuildMachineOSBuild</key>
	<string>24G419</string>
	<key>CFBundleDevelopmentRegion</key>
	<string>en</string>
	<key>CFBundleExecutable</key>
	<string>MobileBabyAnalysis4</string>
	<key>CFBundleIdentifier</key>
	<string>mobilehacking.kr.MobileBabyAnalysis4</string>
	<key>CFBundleInfoDictionaryVersion</key>
	<string>6.0</string>
	<key>CFBundleName</key>
	<string>MobileBabyAnalysis4</string>
	<key>CFBundlePackageType</key>
	<string>APPL</string>
	<key>CFBundleShortVersionString</key>
	<string>1.0</string>
	<key>CFBundleSupportedPlatforms</key>
	<array>
		<string>iPhoneOS</string>
	</array>
	<key>CFBundleVersion</key>
	<string>1</string>
	<key>DTCompiler</key>
	<string>com.apple.compilers.llvm.clang.1_0</string>
	<key>DTPlatformBuild</key>
	<string>23C53</string>
	<key>DTPlatformName</key>
	<string>iphoneos</string>
	<key>DTPlatformVersion</key>
	<string>26.2</string>
	<key>DTSDKBuild</key>
	<string>23C53</string>
	<key>DTSDKName</key>
	<string>iphoneos26.2</string>
	<key>DTXcode</key>
	<string>2620</string>
	<key>DTXcodeBuild</key>
	<string>17C52</string>
	<key>LSRequiresIPhoneOS</key>
	<true/>
	<key>MinimumOSVersion</key>
	<string>26.2</string>
	<key>UIApplicationSceneManifest</key>
	<dict>
		<key>UIApplicationSupportsMultipleScenes</key>
		<true/>
		<key>UISceneConfigurations</key>
		<dict/>
	</dict>
	<key>UIApplicationSupportsIndirectInputEvents</key>
	<true/>
	<key>UIDeviceFamily</key>
	<array>
		<integer>1</integer>
		<integer>2</integer>
	</array>
	<key>UILaunchScreen</key>
	<dict>
		<key>UILaunchScreen</key>
		<dict/>
	</dict>
	<key>UIRequiredDeviceCapabilities</key>
	<array>
		<string>arm64</string>
	</array>
	<key>UISupportedInterfaceOrientations~ipad</key>
	<array>
		<string>UIInterfaceOrientationPortrait</string>
		<string>UIInterfaceOrientationPortraitUpsideDown</string>
		<string>UIInterfaceOrientationLandscapeLeft</string>
		<string>UIInterfaceOrientationLandscapeRight</string>
	</array>
	<key>UISupportedInterfaceOrientations~iphone</key>
	<array>
		<string>UIInterfaceOrientationPortrait</string>
		<string>UIInterfaceOrientationLandscapeLeft</string>
		<string>UIInterfaceOrientationLandscapeRight</string>
	</array>
</dict>
</plist>

이런 내용들이 담겨있다. 보기에도 그렇고 claude한테 물어봐도 특별한 것 없이 바이너리에 모두 로직이 있다고 한다.

다음으로 앱을 실행해보면

이런 용돈 기입장 페이지가 나오고,

+버튼을 누르면 이렇게 거래 추가 페이지가 나오고, 카테고리와 금액을 적을 후 저장을 할 수 있다.

그러면 이렇게 내역이 기록되는 가계부 앱이다.
안드로이드와 다르게 IOS는 따로 함수명의 양식이 어느정도 정해져있거나 하지 않아서
실행해보면서 코드를 찾아나가는 부분이 더 중요한 것 같다.

이제 IDA를 통해서 분석을 해보자.
기본이 swift->C로 변환된 파일이라 보기 많이 불편해서 플러그인들을 찾아다녔지만 좋은걸 찾지는 못했다.
능력이 된다면 좀 사람이 볼만한 플러그인을 만들어보고 싶긴한데 불가능한걸까.

어쨌든 함수를 보면

이렇게 AddTransactionView, MainView, SecretView가 있고, 가장 의심스러운 곳은 SecretView이다.

아무래도 AddTransactionView는 거래를 추가하는 저 화면이고, SecretView가 도달해야하는 위치로 보인다. 그래서 SecretView의 SuccessView.getter() 함수를 보니 ChallengeValidator.generateSecret() 이라는 함수가 있다.

1
v94 = (_QWORD *)Text.init<A>(_:)(v109, &type metadata for String, v29);

이것이 텍스트를 만드는 함수이고,

1
v109[0] = specialized static ChallengeValidator.generateSecret(_:)(*v94);

그 v109는 genarateSecret 함수에서 나온다.
분석을 해보면

1
2
3
4
5
6
7
8
9
10
11
12
13
        v42 = v7;
        KeyPath = swift_getKeyPath(&unk_1000161E8);
        v10 = lazy protocol witness table accessor for type Transaction and conformance Transaction(
                &lazy protocol witness table cache variable for type Transaction and conformance Transaction,
                &protocol conformance descriptor for Transaction);
        ObservationRegistrar.access<A, B>(_:keyPath:)(&v42, KeyPath, v10);
        swift_release(KeyPath);
        v11 = swift_getKeyPath(&unk_1000161E8);
        v12 = lazy protocol witness table accessor for type Transaction and conformance Transaction(
                &lazy protocol witness table cache variable for type Transaction and conformance Transaction,
                &protocol conformance descriptor for Transaction);
        PersistentModel.getValue<A>(forKey:)(&v42, v11, v12, &protocol witness table for String);
        swift_release(v11);

이 부분이 트랜잭션을 받고, getValue가 값을 꺼내는 패턴이라고 한다.

1
2
3
4
if ( v42 == 0x6365532090949FF0LL && v43 == 0xAB00000000746572LL )
        {
          v4 = swift_bridgeObjectRelease(0xAB00000000746572LL);
        }

여기에서 string으로 가져온 값을 비교한다.
위의 값들을 utf-8로 변환해보면 🔐 Secret 라는 값이 나온다.

다음 조건문은

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  v15 = (unsigned __int64)&_swiftEmptyArrayStorage;
  if ( ((unsigned __int64)&_swiftEmptyArrayStorage & 0x8000000000000000LL) != 0 )
    goto LABEL_43;
LABEL_25:
  if ( (v15 & 0x4000000000000000LL) == 0 )
  {
    if ( *(_QWORD *)(v15 + 16) == 3 )
    {
      v17 = 3;
      goto LABEL_28;
    }
LABEL_45:
    swift_release(v15);
    return 0;
  }

우선 v15가 어떤 array의 저장소이고 *(_QWORD *)(v15 + 16)가 배열의 count로써 쓰인다고 한다.
즉 필요한 배열의 개수가 3개라는 것을 의미한다고 한다.

마지막 조건문은

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    v23 = swift_getKeyPath(&unk_100016210);
    v24 = lazy protocol witness table accessor for type Transaction and conformance Transaction(
            &lazy protocol witness table cache variable for type Transaction and conformance Transaction,
            &protocol conformance descriptor for Transaction);
    PersistentModel.getValue<A>(forKey:)(&v42, v23, v24, &protocol witness table for Int);
    swift_release(v23);
    swift_release(v20);
    ++v18;
    v25 = __OFADD__(v19, v42);
    v19 += v42;
    if ( v25 )
      goto LABEL_42;
  
  swift_release(v15);
  if ( v19 != 1337 )
    return 0;

여기에서는 필터된 트랜잭션에서 int값을 가져오고 그것을 모두 더한 값이 1337이면 secretview가 실행되는 것이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
v42 = 0x7B67616C66LL;
  v43 = 0xE500000000000000LL;
  v35._countAndFlagsBits = v26;
  v35._object = v28;
  String.append(_:)(v35);
  swift_bridgeObjectRelease(v28);
  v36._countAndFlagsBits = 95;
  v36._object = (void *)0xE100000000000000LL;
  String.append(_:)(v36);
  v37._countAndFlagsBits = v29;
  v37._object = v31;
  String.append(_:)(v37);
  swift_bridgeObjectRelease(v31);
  v38._countAndFlagsBits = 95;
  v38._object = (void *)0xE100000000000000LL;
  String.append(_:)(v38);
  v39._countAndFlagsBits = v32;
  v39._object = v34;
  String.append(_:)(v39);
  swift_bridgeObjectRelease(v34);
  v40._countAndFlagsBits = 125;
  v40._object = (void *)0xE100000000000000LL;
  String.append(_:)(v40);
  return v42;

마지막 부분의 v42와 문자열을 만드는 것에서 flag{_ _ } 형식임을 알 수 있다.

이제 화면을 트리거하는 부분을 찾아야 하는데.. 사실 어떻게 찾는지 모르겠다.
클로드의 말에 따르면 함수 이름에서 mainview.handlesecretTap()이 있기에 수상하니까 여기를 분석하면 된다.

1
2
3
4
5
6
7
8
Date.init()(v2);                        // 현재 시간
State.wrappedValue.getter(v4);          // lastTapDate 읽기
v8 = Date.timeIntervalSince(_:)(v4);   // 현재 - 마지막탭 = 경과시간

if ( v8 >= 1.0 )   // 1초 이상 지남
    tapCount = 1;  // 리셋
else
    tapCount += 1; // 누적

이게 timestamp이고

State.wrappedValue.getter(v29);  // tapCount 읽기
if ( v29[0] >= 3LL )             // 3번 이상이면
{
    tapCount = 0;                // 리셋
    checkConditions(transactions) // 데이터 검증
    if ( true )
    {
        showSecret = true;       // SecretView 오픈
        State.wrappedValue.setter(v29, v23);
    }
}

요게 트리거하는 핵심 조건이므로 1초에 3번 이상 바텀 탭을 누르면 flag가 열리게 된다.

조건을 맞추고

코드를 보기가 너무 어렵다.. 리버싱 능력의 한계를 느꼈지만 그래도 어느정도 감은 잡을 수 있었다.

This post is written by PRO.