상세 컨텐츠

본문 제목

[Google CTF 2020] android

REVERSING/Android

by koharin 2021. 7. 21. 15:46

본문

728x90
반응형

Can you find the correct key to unlock this app?


 

Setting


Download

luker983/google-ctf-2020

Install

  • Android Studio

문제 파일에서 사용하는 SDK 버전이 없으면 위와 같은 오류가 발생하는데, "Install missing platform and fix project"를 누르면 사용하는 Android platform 28을 설치해줘서 이후 정상적으로 실행이 된다.

 

 

Decompile


apktool

 

dex2jar

dex2jar로는 classes.dex 파일 디컴파일이 안 된다.

 

jadx

Release v1.2.0 · skylot/jadx

jadx-gui-1.2.0-with-jre-win.zip를 다운받아서 exe 파일로 jadx-gui를 실행하고 reverse.apk 파일을 열어줬다.

JD-GUI와 다르게 dex to jar conversion 필요없이 apk 파일을 로드하면 java 소스코드로 디컴파일해준다..!

 

Dynamic Analysis


key가 틀리면 X로 바뀌는 것을 알 수 있다.

 

 

Static Analysis


public class ActivityC0000 extends Activity {

    /* renamed from: class  reason: not valid java name */
    long[] f0class;

    /* renamed from: ő  reason: contains not printable characters */
    int f1;

    /* renamed from: ő  reason: contains not printable characters and collision with other field name */
    long[] f2;

    public ActivityC0000() {
        try {
            this.f0class = new long[]{40999019, 2789358025L, 656272715, 18374979, 3237618335L, 1762529471, 685548119, 382114257, 1436905469, 2126016673, 3318315423L, 797150821};
            this.f2 = new long[12];
            this.f1 = 0;
        } catch (I unused) {
        }
    }

    /* access modifiers changed from: protected */
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        final EditText editText = (EditText) findViewById(R.id.editText);
        final TextView textView = (TextView) findViewById(R.id.textView);
        ((Button) findViewById(R.id.button)).setOnClickListener(new View.OnClickListener() {
            /* class com.google.ctf.sandbox.ActivityC0000.AnonymousClass1 */

            public void onClick(View v) {
                ActivityC0000.this.f1 = 0;
                try {
                    StringBuilder keyString = new StringBuilder();
                    for (Object chr : new Object[]{65, 112, 112, 97, 114, 101, 110, 116, 108, 121, 32, 116, 104, 105, 115, 32, 105, 115, 32, 110, 111, 116, 32, 116, 104, 101, 32, 102, 108, 97, 103, 46, 32, 87, 104, 97, 116, 39, 115, 32, 103, 111, 105, 110, 103, 32, 111, 110, 63}) {
                        keyString.append(((Character) chr).charValue());
                    }
                    if (editText.getText().toString().equals(keyString.toString())) {
                        textView.setText("🚩");
                    } else {
                        textView.setText("❌");
                    }
                } catch (J | Error | Exception unused) {
                    String flagString = editText.getText().toString();
                    if (flagString.length() != 48) {
                        textView.setText("❌");
                        return;
                    }
                    for (int i = 0; i < flagString.length() / 4; i++) {
                        ActivityC0000.this.f2[i] = (long) (flagString.charAt((i * 4) + 3) << 24);
                        long[] jArr = ActivityC0000.this.f2;
                        jArr[i] = jArr[i] | ((long) (flagString.charAt((i * 4) + 2) << 16));
                        long[] jArr2 = ActivityC0000.this.f2;
                        jArr2[i] = jArr2[i] | ((long) (flagString.charAt((i * 4) + 1) << '\b'));
                        long[] jArr3 = ActivityC0000.this.f2;
                        jArr3[i] = jArr3[i] | ((long) flagString.charAt(i * 4));
                    }
                    ActivityC0000 r6 = ActivityC0000.this;
                    if (((R.m0(ActivityC0000.this.f2[ActivityC0000.this.f1], 4294967296L)[0] % 4294967296L) + 4294967296L) % 4294967296L != ActivityC0000.this.f0class[ActivityC0000.this.f1]) {
                        textView.setText("❌");
                        return;
                    }
                    ActivityC0000.this.f1++;
                    if (ActivityC0000.this.f1 >= ActivityC0000.this.f2.length) {
                        textView.setText("🚩");
                        return;
                    }
                    throw new RuntimeException();
                }
            }
        });
    }
}

버튼을 누르는 이벤트가 발생하면, 이벤트 핸들러는 editText의 스트링과 keyString의 스트링이 동일한지 비교 후 동일하면 textView로 플래그를, 동일하지 않으면 textView로 X를 보여준다.

StringBuilder keyString = new StringBuilder();
 for (Object chr : new Object[]{65, 112, 112, 97, 114, 101, 110, 116, 108, 121, 32, 116, 104, 105, 115, 32, 105, 115, 32, 110, 111, 116, 32, 116, 104, 101, 32, 102, 108, 97, 103, 46, 32, 87, 104, 97, 116, 39, 115, 32, 103, 111, 105, 110, 103, 32, 111, 110, 63}) {
     keyString.append(((Character) chr).charValue());
}
if (editText.getText().toString().equals(keyString.toString())) {
    textView.setText("🚩");
} else {
    textView.setText("❌");
}

아스키코드로 된 key를 문자로 변환해보면 Apparently this is not the flag. What's going on? 를 얻을 수 있다.

flag가 아니라고 한다.

 

String flagString = editText.getText().toString();
if (flagString.length() != 48) {
    textView.setText("❌");
    return;
}

editText에 입력한 스트링의 길이는 48이어야 한다.

ActivityC0000.this.f1 = 0;
for (int i = 0; i < flagString.length() / 4; i++) {
	ActivityC0000.this.f2[i] = (long) (flagString.charAt((i * 4) + 3) << 24);
  long[] jArr = ActivityC0000.this.f2;
  jArr[i] = jArr[i] | ((long) (flagString.charAt((i * 4) + 2) << 16));
  long[] jArr2 = ActivityC0000.this.f2;
  jArr2[i] = jArr2[i] | ((long) (flagString.charAt((i * 4) + 1) << '\b'));
  long[] jArr3 = ActivityC0000.this.f2;
  jArr3[i] = jArr3[i] | ((long) flagString.charAt(i * 4));
}
ActivityC0000 r6 = ActivityC0000.this;
if (((R.m0(ActivityC0000.this.f2[ActivityC0000.this.f1], 4294967296L)[0] % 4294967296L) + 4294967296L) % 4294967296L != ActivityC0000.this.f0class[ActivityC0000.this.f1]) {
	textView.setText("❌");
  return;
}
ActivityC0000.this.f1++;
if (ActivityC0000.this.f1 >= ActivityC0000.this.f2.length) {
	textView.setText("🚩");
  return;
}

12만큼 반복

처음 flagString은 CTF{으로 시작한다.

jArr3[0] = jArr3[0] | (long)flagString.charAt(0)

jArr2[0] = jArr2[0] | (long)flagString.charAt(1) << 8

jArr[0] = jArr[0] | flagString.charAt(2) << 16

f2[0] = flagString.charAt(3) << 24

각 loop마다 4개씩 총 12*4 = 48개 문자 조합을 만들어낸다.

초기에 ActivityC0000.this.f1 = 0으로, 이후 ActivityC0000.this.f1++을 하므로 인덱스를 하나씩 증가시킨다.

m0 함수

4글자마다 shift해서 숫자를 만들고, m0 함수에 넣은 후 해당 값이 포함된 연산을 진행하여 얻은 값과 f0class 내 각 인덱스에서의 값과 동일한지 비교한다.

이때 f0class에서의 값은 초기에 넣는 4개의 문자와 동일하다.

따라서 brute force로 각 loop마다 문자가 m0으로 구해서 얻은 문자들과 동일한지 비교해서 12번의 loop를 돌고, 48길이의 flag를 구하면 된다.

 

public ActivityC0000() {
        try {
            this.f0class = new long[]{40999019, 2789358025L, 656272715, 18374979, 3237618335L, 1762529471, 685548119, 382114257, 1436905469, 2126016673, 3318315423L, 797150821};
            this.f2 = new long[12];
            this.f1 = 0;
        } catch (I unused) {
        }
}

f0class의 각 값은 위와 같음을 알 수 있다.

(m0(2068206659, 0x100000000)[0] % 0x100000000 + 0x100000000) % 0x100000000 수식 결과가 f0class 내에 있으면 입력으로 사용한 a,b,c,d 문자들이 flag 값에 포함된다. 이때 수식 결과 값의 f0class에서의 위치가 flag에서 입력으로 사용한 a,b,c,d 문자들의 위치이다.

 

 

Exploit


from __future__ import print_function
f0class = [40999019, 2789358025, 656272715, 18374979, 3237618335, 1762529471, 685548119, 382114257, 1436905469, 2126016673, 3318315423, 797150821]
answer = [0 for i in range(12)]
def num(a, b, c, d):
    return a + (b << 8) + (c << 16) + (d << 24)

def m0(a, b):
    if(a == 0):
        return [0,1]
    x = m0(b % a, a)
    return [x[1] - ((b/a)*x[0]), x[0]]


for a in range(0x20, 0x7E):
    for b in range(0x20, 0x7E):
        for c in range(0x20, 0x7E):
            for d in range(0x20, 0x7E):
                number = num(a,b,c,d)
                output = (m0(number, 0x100000000)[0] % 0x100000000 + 0x100000000) % 0x100000000
                if output in f0class:
                    print("output: " + str(output) + " number: " + str(number))
                    answer[f0class.index(output)] = number

for a in answer:
    print(chr(a & 0xFF), end='')
    print(chr((a >> 8) & 0xFF), end='')
    print(chr((a >> 16) & 0xFF), end='')
    print(chr((a >> 24) & 0xFF), end='')

number의 경우 원래 문자가 아닌 shift 연산을 한 값이기 때문에 다시 shift를 해서 각 4개의 문자들을 구해준다.

python2 환경에서 exploit code를 작성하여 flag가 한 줄로 나오도록 __future__ 모듈의 print_function을 import해서 사용했다. (__future__ 모듈은 python2에서 python3의 문법을 사용할 수 있도록 한다.)

728x90
반응형

관련글 더보기