Home (mobilehacking.kr) Smart Grid
Post
Cancel

(mobilehacking.kr) Smart Grid

Smart Grid 문제이다.
우선 블루스택으로 앱을 켰는데 바로 꺼지는 것을 보니 루팅 감지가 있는 듯 하다.
그래서 루팅 권한을 뺀 다음 앱을 다시 켜보니

image

이렇게 로그인 화면을 볼 수 있다.
입력되어 있는 정보로 로그인을 하면 딱히 눈에 띄는 기능은 보이지 않고

image

이렇게 demo/일반 사용자라고 있는 것을 보아 admin도 있지 않을까 생각을 했다.
비밀번호를 복사해보면 demo1234인 것을 확인할 수 있다.

다음 jadx로 apk를 열어 MainActivity를 분석해보면

1
2
3
4
5
 private final void loadDynamicDex() {
        try {
            Log.d(TAG, "Starting Dynamic DEX Loading...");
            File fileQ = s0.a.q(this);

동적으로 dex 파일을 가져와서 런타임 로드를 하고,
performInitialSecurityCheck() 함수를 통해서 Frida, 디버거, 루팅 탐지를 하고 있다.

로그인을 한 후에 루트 권한을 켜면 앱은 꺼지지만 앱의 database나 shared_prefs에 접근할 수 있다.

우선 database를 보면

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
taimen:/data/data/mobilehacking.kr.smartgrid/databases # ls
smartgrid_database smartgrid_database-shm smartgrid_database-wal
taimen:/data/data/mobilehacking.kr.smartgrid/databases # sqlite3 sma
smartgrid_database      smartgrid_database-shm  smartgrid_database-wal
qlite3 smartgrid_database                                                                                             <
SQLite version 3.22.0 2019-09-03 18:36:11
Enter ".help" for usage hints.
sqlite> .tables
android_metadata   power_usages       users
devices            room_master_table
sqlite> .schema users
CREATE TABLE `users` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `username` TEXT NOT NULL, `password` TEXT NOT NULL, `email` TEXT NOT NULL, `isAdmin` INTEGER NOT NULL, `lastLogin` INTEGER NOT NULL);
sqlite> SELECT id, username, password, isAdmin FROM users;
1|demo|0ead2060b65992dca4769af601a1b3a35ef38cfad2c2c465bb160ea764157c5d|0
2|admin|fd48d4edbe78b5fda5214c|1
sqlite>

이렇게 demo와 admin의 계정이 있음을 알 수 있고,
password라는 column에 저장이 되어있는 값은 암호화되어서 저장된 비밀번호라고 생각할 수 있다.

MainActivity를 보면 private i authManager;로 정의가 되어있어서 i를 분석해보면

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
public static String d(User user) throws IllegalStateException, NoSuchAlgorithmException, InvalidKeyException {
        String str = user.getId() + ":" + user.getUsername() + ":" + user.getPassword() + ":" + user.isAdmin();
        byte[] bArr = a0.O;
        ArrayList arrayList = new ArrayList(16);
        int i = 0;
        for (int i2 = 0; i2 < 16; i2++) {
            arrayList.add(Byte.valueOf((byte) (bArr[i2] ^ 90)));
        }
        byte[] bArr2 = new byte[arrayList.size()];
        Iterator it = arrayList.iterator();
        while (it.hasNext()) {
            bArr2[i] = ((Number) it.next()).byteValue();
            i++;
        }
        Charset charset = c1.a.f471a;
        String str2 = new String(bArr2, charset);
        s0.c.n(str, "data");
        Mac mac = Mac.getInstance("HmacSHA256");
        byte[] bytes = str2.getBytes(charset);
        s0.c.m(bytes, "this as java.lang.String).getBytes(charset)");
        mac.init(new SecretKeySpec(bytes, "HmacSHA256"));
        byte[] bytes2 = str.getBytes(charset);
        s0.c.m(bytes2, "this as java.lang.String).getBytes(charset)");
        byte[] bArrDoFinal = mac.doFinal(bytes2);
        s0.c.k(bArrDoFinal);
        return r.x0(bArrDoFinal, w.f166p);
    }

이렇게 세션을 만드는 코드가 있다.

1
2
3
4
5
6
7
byte[] bArr = a0.O;
        ArrayList arrayList = new ArrayList(16);
        int i = 0;
        for (int i2 = 0; i2 < 16; i2++) {
            arrayList.add(Byte.valueOf((byte) (bArr[i2] ^ 90)));
        }

a0.O 배열을 90으로 xor한 값이 HMAC key로 사용되므로

1
public static final byte[] O = {41, 105, 41, 41, 107, 106, 52, 5, 50, 55, 59, 57, 5, 49, 105, 35};

복호화 하면 HMAC Key: s3ss10n_hmac_k3y 임을 알 수 있다.

이제 database에 있던

1
1|demo|0ead2060b65992dca4769af601a1b3a35ef38cfad2c2c465bb160ea764157c5d|0

값을 가지고 세션을 만드는 로직에 넣어보면

1
2
3
4
5
6
============================================================
[Demo Account]
============================================================
[*] HMAC Key: s3ss10n_hmac_k3y
[*] Data to sign: 1:demo:0ead2060b65992dca4769af601a1b3a35ef38cfad2c2c465bb160ea764157c5d:false
[*] Session Token: 837f04183eca161a61410c59757c964cf67b9d02700272caf2c1587c809c565f

이런 결과를 볼 수 있고, Session Token에 있는 값은

1
2
3
4
5
6
7
taimen:/data/data/mobilehacking.kr.smartgrid/shared_prefs # cat auth_prefs.xml
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
    <string name="session_token">837f04183eca161a61410c59757c964cf67b9d02700272caf2c1587c809c565f</string>
    <long name="user_id" value="1" />
    <string name="username">demo</string>
</map>

로그인 했을 때 shard_prefs에 저장되어있는 값과 일치한다.

그러면 admin의 정보를 가지고 session 하이재킹이 가능할 것이라 생각했고

1
2|admin|fd48d4edbe78b5fda5214c|1

이 정보를 통해서 세션을 만든 다음

1
2
3
4
5
6
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
    <string name="session_token">ad1e2397ab33fb2e89b38194b9aecc91412ae03780e24129910404d8180bbaa5</string>
    <long name="user_id" value="2" />
    <string name="username">admin</string>
</map>

다시 앱을 켜보면

image

이렇게 관리자 계정으로 로그인이 잘 되었음을 확인할 수 있다.
database를 읽거나 xml 조작 과정에는 루팅 권한이 필요하지만 앱을 켤 때는 루팅 권한이 없어야 하니 잘 봐야 한다.

그런데 보다시피 flag가 바로 나오는 것이 아닌 logic check 과정에 flag가 있다고 한다.
무슨 말일까 생각을 해보면 세션 만들 때 쓴 값이 암호화 된 PW 값이었으니까 진짜 PW를 구하라는 뜻이 아닐까 싶다.

logcat을 켜고 조금 더 보니

1
2
12-26 14:06:05.097  6877  6877 D BoundBrokerSvc: onUnbind: Intent { act=com.google.android.gms.measurement.START pkg=com.google.android.gms }
12-26 14:06:07.787 12538 12559 D AuthManager: 로그인 성공: demo

이렇게 로그인 했을 때 로그가 남길래 이 부분은 jadx에서 따라갔다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
boolean zIsAdmin = user.isAdmin();
            String str2 = this.f1239h;
            if (zIsAdmin) {
                try {
                    zT = c1.g.T(new q1.a().a(str2), user.getPassword());
                } catch (Exception e2) {
                    Log.e("AuthManager", "관리자 비밀번호 비교 실패", e2);
                    zT = false;
                }
            } else {
                zT = s0.c.e(user.getPassword(), m.r(str2));
            }
            if (!zT) {
                Log.d("AuthManager", "로그인 실패: 비밀번호 불일치");
                return Boolean.FALSE;
            }
            i.a(iVar, user);
            Log.d("AuthManager", "로그인 성공: " + str);
            return Boolean.TRUE;
        } catch (Exception e3) {
            Log.e("AuthManager", "로그인 중 오류 발생", e3);
            return Boolean.FALSE;
        }

그러니 이렇게 비밀번호 값을 받고, 암호화하는 코드가 있는 것 같다.
q1.a의 코드를 보면 RC4로 암호화되어서 저장을 하고 있었고 복호화 코드를 짜서 실행하면

1
2
3
4
5
[*] RC4 Key: gr33nP0w3rS3cur1ty

============================================================
[+] ADMIN PASSWORD (FLAG): smartic_0-0
============================================================

이렇게 최종 flag는 flag{smartic_0-0} 로 구할 수 있다.

This post is written by PRO.