[GoogleCTF quals 2021] adspam

2022. 4. 23. 18:28CTF/ㅁㄹ

 

app-release.apk 라는 apk를 줌

apk 리버싱을 안해봤다

 

https://balsn.tw/ctf_writeup/20210717-googlectf2021/#adspam

 

Google CTF 2021 Quals

Google CTF 2021 Quals Reverse cpp The file cpp.c is a c source code file with lots of macro, the goal is to define the macro FLAG_0 ~ FLAG_20 with correct characters to pass the flag check. The logic of flag checker and the execution flow are implemented b

balsn.tw

https://artis24106.github.io/blog/posts/2021-googlectf/adspam/

 

artis24106 | 2021 Google CTF - ADSPAM

 

artis24106.github.io

https://github.com/nononovak/googlectf-writeups/blob/main/2021/reversing/adspam/README.md

 

GitHub - nononovak/googlectf-writeups: Writeups for Google CTF 2021

Writeups for Google CTF 2021. Contribute to nononovak/googlectf-writeups development by creating an account on GitHub.

github.com

 

그냥 따라하면서 배워보자

 

..

진짜 아는게 없어서 따라갈 수가 없다

https://www.ragingrock.com/AndroidAppRE/

 

Android App Reverse Engineering 101

Learn to reverse engineer Android applications!

www.ragingrock.com

이거다

 

jadx

https://github.com/skylot/jadx/releases/tag/v1.3.5

 

jadx-gui로 app-release.apk를 열어보면

이렇게 소스 코드를 볼 수 있음

 

# ad/spam/MainActivity.java

public class MainActivity extends e {
    ...
    @Override // c.b.c.e, c.k.a.e, androidx.activity.ComponentActivity, c.h.b.g, android.app.Activity
    public void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        if (NativeAdapter.oktorun()) {
            Context applicationContext = getApplicationContext();
            if (Build.VERSION.SDK_INT >= 26) {
                String string = getString(R.string.channel_name);
                String string2 = getString(R.string.channel_description);
                NotificationChannel notificationChannel = new NotificationChannel("d7a59938-d2e1-11eb-8e49-1fa596d3ced5", string, 3);
                notificationChannel.setDescription(string2);
                ((NotificationManager) getSystemService(NotificationManager.class)).createNotificationChannel(notificationChannel);
            }
            this.p = new j(this);
            View inflate = getLayoutInflater().inflate(R.layout.activity_main, (ViewGroup) null, false);
            Objects.requireNonNull(inflate, "rootView");
            setContentView((ConstraintLayout) inflate);
            new Timer().scheduleAtFixedRate(new a(this, new f(applicationContext, new c(applicationContext), new b(applicationContext, this.p))), 0L, 14400000L);
        }
    }
}
new Timer().scheduleAtFixedRate(new a(...), 0L, 14400000L);

scheduleAtFixedRate는 일정 시간마다 반복적으로 동작을 수행하도록 하는 함수

 

public class a extends TimerTask {

    /* renamed from: b  reason: collision with root package name */
    public final /* synthetic */ f f17b;

    public a(MainActivity mainActivity, f fVar) {
        this.f17b = fVar;
    }

    @Override // java.util.TimerTask, java.lang.Runnable
    public void run() {
        try {
            this.f17b.a();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

a도 MainActivity.java에 있음

 

https://javacan.tistory.com/entry/29

 

Timer 클래스를 이용한 작업 스케쥴링

JDK1.3에 새롭게 추가된 java.util.Timer 클래스를 사용하여 이벤트의 실행을 제어하는 것에 대해서 알아본다. java.util.Timer 클래스와 java.util.TimerTask 클래스 유닉스나 리눅스에서 특정 시간에 어떤 프

javacan.tistory.com

예제 코드를 보니까

public final f17b = new f(applicationContext, new c(applicationContext), new b(applicationContext, this.p));
try {
    this.f17b.a();
} catch (IOException e) {
    e.printStackTrace();
}

요게 반복적으로 수행되는 것 같다

this.f17b.a()를 하니까 f.a()를 보자

 

# a/a/f.java

...
public void a() {
    if (Long.valueOf(System.currentTimeMillis() / 1000).longValue() > Long.valueOf(this.f15b.f7c.getSharedPreferences(c.f5a, 0).getLong(c.f6b, 0L)).longValue() + 7200) {
        Resources resources = this.f14a.getResources();
        String string = resources.getString(R.string.ip);
        String string2 = resources.getString(R.string.port);
        Context context = this.f14a;
        d dVar = new d(context);
        JSONObject jSONObject = new JSONObject();
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(context.getResources().openRawResource(R.raw.lic)));
        StringBuilder sb = new StringBuilder();
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        while (true) {
            String readLine = bufferedReader.readLine();
            if (readLine == null) {
                break;
            }
            byteArrayOutputStream.write(NativeAdapter.declicstr(Base64.decode(readLine, 0)));
            sb.append(readLine);
            sb.append("::");
        }
        byte[] byteArray = byteArrayOutputStream.toByteArray();
        String str = new String(dVar.a(byteArray, 0));
        int length = str.length() + 1;
        int parseInt = Integer.parseInt(new String(dVar.a(byteArray, new String(dVar.a(byteArray, length)).length() + 1 + length)));
        String sb2 = sb.toString();
        try {
            jSONObject.put(d.f11d, str);
            jSONObject.put(d.e, parseInt);
            String str2 = d.f;
            JSONObject jSONObject2 = new JSONObject();
            try {
                jSONObject2.put(d.f8a, System.getProperty("os.version"));
                jSONObject2.put(d.f9b, Build.VERSION.SDK_INT);
                jSONObject2.put(d.f10c, Build.DEVICE);
            } catch (JSONException e) {
                e.printStackTrace();
            }
            jSONObject.put(str2, jSONObject2);
            jSONObject.put(d.g, sb2);
        } catch (JSONException e2) {
            e2.printStackTrace();
        }
        new a(this, this.f16c).execute(string, string2, Base64.encodeToString(NativeAdapter.encrypt(jSONObject.toString().getBytes(StandardCharsets.UTF_8)), 2));
    }
}
...

뭔가 많이 있고 encrypt하는 것처럼 보임

 

한줄씩 보는데 NativeAdapter.transform이 겁나 많이 나온다

 

# ad/spam/NativeAdapter.java

package ad.spam;

/* loaded from: classes.dex */
public class NativeAdapter {
    static {
        System.loadLibrary("native-lib");
    }

    public static native byte[] declicstr(byte[] bArr);

    public static native byte[] decrypt(byte[] bArr);

    public static native byte[] encrypt(byte[] bArr);

    public static native boolean oktorun();

    public static native String transform(byte[] bArr);
}

 

android application에 컴파일된 라이브러리를 포함시킬 수 있는데

얘가 /lib/<cpu>/libnative-lib.so에 있는 함수인듯

이걸 분석해야 할 것 같다

 

https://www.ragingrock.com/AndroidAppRE/reversing_native_libs.html

 

Android App Reverse Engineering 101

Learn to reverse engineer Android applications!

www.ragingrock.com

Java_패키지명_클래스명_함수명 이런 이름이 있으면 dynamic linking

아니면 static linking이고

static linking의 경우 RegisterNatives 함수가 호출돼야 함

일반적으로 JNI_OnLoad에서 RegisterNatives 함수 호출

 

jint RegisterNatives(JNIEnv *env, jclass clazz, const JNINativeMethod *methods, jint nMethods);

typedef struct { 
    char *name; 
    char *signature; 
    void *fnPtr; 
} JNINativeMethod;

method 이름이 있어야되는거 아닌가 -> 있다

 

$ jadx app-release.apk -d ./app

을 하고

app/resources/lib/x86_64/libnative-lib.so 를 ida freeware로 분석함

(그냥 unzip app-release.apk해도 나옴)

 

JNI_OnLoad 함수를 찾으면

__int64 __fastcall JNI_OnLoad(__int64 a1)
{
  __int64 *v1; // rsi
  unsigned int v2; // ebx
  const char *v3; // r14
  char *v4; // rsi
  __int64 v5; // rbx
  unsigned __int64 v6; // rax
  char *v8; // rax
  char *v9; // rax
  char *v10; // rax
  char *v11; // rax
  int v12; // ebx
  int v13; // edx
  int v14; // ecx
  int v15; // er8
  int v16; // er9
  __m128i v17; // [rsp+0h] [rbp-C8h] BYREF
  char v18; // [rsp+18h] [rbp-B0h] BYREF
  char v19; // [rsp+19h] [rbp-AFh] BYREF
  void *v20; // [rsp+28h] [rbp-A0h]
  char v21; // [rsp+30h] [rbp-98h] BYREF
  char v22; // [rsp+31h] [rbp-97h] BYREF
  void *v23; // [rsp+40h] [rbp-88h]
  char v24; // [rsp+48h] [rbp-80h] BYREF
  char v25; // [rsp+49h] [rbp-7Fh] BYREF
  void *v26; // [rsp+58h] [rbp-70h]
  char v27; // [rsp+60h] [rbp-68h] BYREF
  char v28; // [rsp+61h] [rbp-67h] BYREF
  void *v29; // [rsp+70h] [rbp-58h]
  char v30; // [rsp+78h] [rbp-50h] BYREF
  char v31; // [rsp+79h] [rbp-4Fh] BYREF
  void *v32; // [rsp+88h] [rbp-40h]
  char v33; // [rsp+90h] [rbp-38h] BYREF
  char v34[15]; // [rsp+91h] [rbp-37h] BYREF
  void *ptr; // [rsp+A0h] [rbp-28h]
  __int64 v36; // [rsp+A8h] [rbp-20h] BYREF
  unsigned __int64 v37; // [rsp+B0h] [rbp-18h]

  v37 = __readfsqword(0x28u);
  v1 = &v36;
  v2 = -1;
  if ( (*(unsigned int (__fastcall **)(__int64, __int64 *, __int64))(*(_QWORD *)a1 + 48LL))(a1, &v36, 65542LL) )
    goto LABEL_13;
  sub_445A0(&v33, off_D4000, (unsigned int)dword_D4008);
  v3 = v34;
  v4 = v34;
  if ( (v33 & 1) != 0 )
    v4 = (char *)ptr;
  v5 = (*(__int64 (__fastcall **)(__int64, char *))(*(_QWORD *)v36 + 48LL))(v36, v4);
  if ( !v5 )
  {
    if ( (v33 & 1) != 0 )
      v3 = (const char *)ptr;
    v2 = -1;
    v1 = (__int64 *)"AdSpamBot";
    __android_log_print(2LL, "AdSpamBot", "Class %s not found.", v3);
LABEL_11:
    if ( (v33 & 1) == 0 )
      goto LABEL_13;
    goto LABEL_12;
  }
  if ( !byte_D42B8 && (unsigned int)__cxa_guard_acquire(&byte_D42B8) )
  {
    sub_445A0(&v30, off_D4010, (unsigned int)dword_D4018);
    if ( (v30 & 1) != 0 )
      v8 = (char *)v32;
    else
      v8 = &v31;
    qword_D4240 = (__int64)v8;
    xmmword_D4248 = (__int128)_mm_unpacklo_epi64(
                                (__m128i)(unsigned __int64)"([B)Ljava/lang/String;",
                                (__m128i)(unsigned __int64)sub_41EB0);
    sub_445A0(&v27, off_D4020, (unsigned int)dword_D4028);
    if ( (v27 & 1) != 0 )
      v9 = (char *)v29;
    else
      v9 = &v28;
    qword_D4258 = (__int64)v9;
    xmmword_D4260 = (__int128)_mm_unpacklo_epi64(
                                (__m128i)(unsigned __int64)"()Z",
                                (__m128i)(unsigned __int64)&loc_41F70);
    sub_445A0(&v24, off_D4030, (unsigned int)dword_D4038);
    if ( (v24 & 1) != 0 )
      v10 = (char *)v26;
    else
      v10 = &v25;
    qword_D4270 = (__int64)v10;
    v17 = (__m128i)(unsigned __int64)"([B)[B";
    xmmword_D4278 = (__int128)_mm_unpacklo_epi64(
                                (__m128i)(unsigned __int64)"([B)[B",
                                (__m128i)(unsigned __int64)sub_41F80);
    sub_445A0(&v21, off_D4040, (unsigned int)dword_D4048);
    if ( (v21 & 1) != 0 )
      v11 = (char *)v23;
    else
      v11 = &v22;
    qword_D4288 = (__int64)v11;
    xmmword_D4290 = (__int128)_mm_unpacklo_epi64(_mm_load_si128(&v17), (__m128i)(unsigned __int64)sub_41FE0);
    sub_445A0(&v18, off_D4050, (unsigned int)dword_D4058);
    if ( (v18 & 1) != 0 )
    {
      qword_D42A0 = (__int64)v20;
      xmmword_D42A8 = (__int128)_mm_unpacklo_epi64(_mm_load_si128(&v17), (__m128i)(unsigned __int64)sub_42040);
      operator delete(v20);
      if ( (v21 & 1) == 0 )
        goto LABEL_35;
    }
    else
    {
      qword_D42A0 = (__int64)&v19;
      xmmword_D42A8 = (__int128)_mm_unpacklo_epi64(_mm_load_si128(&v17), (__m128i)(unsigned __int64)sub_42040);
      if ( (v21 & 1) == 0 )
      {
LABEL_35:
        if ( (v24 & 1) != 0 )
        {
          operator delete(v26);
          if ( (v27 & 1) == 0 )
          {
LABEL_37:
            if ( (v30 & 1) == 0 )
            {
LABEL_39:
              __cxa_guard_release(&byte_D42B8);
              goto LABEL_6;
            }
LABEL_38:
            operator delete(v32);
            goto LABEL_39;
          }
        }
        else if ( (v27 & 1) == 0 )
        {
          goto LABEL_37;
        }
        operator delete(v29);
        if ( (v30 & 1) == 0 )
          goto LABEL_39;
        goto LABEL_38;
      }
    }
    operator delete(v23);
    goto LABEL_35;
  }
LABEL_6:
  LODWORD(v1) = v5;
  v2 = (*(__int64 (__fastcall **)(__int64, __int64, __int64 *, __int64))(*(_QWORD *)v36 + 1720LL))(
         v36,
         v5,
         &qword_D4240,
         5LL);
  if ( v2 )
  {
    v1 = (__int64 *)"AdSpamBot";
    __android_log_print(2LL, "AdSpamBot", "RegisterNatives failed.");
    goto LABEL_11;
  }
  v2 = 65542;
  if ( (v33 & 1) != 0 )
LABEL_12:
    operator delete(ptr);
LABEL_13:
  v6 = __readfsqword(0x28u);
  if ( v6 == v37 )
    return v2;
  v12 = v6;
  if ( (v21 & 1) != 0 )
  {
    operator delete(v23);
    if ( (v24 & 1) == 0 )
    {
LABEL_45:
      if ( (v27 & 1) == 0 )
        goto LABEL_46;
      goto LABEL_50;
    }
  }
  else if ( (v24 & 1) == 0 )
  {
    goto LABEL_45;
  }
  operator delete(v26);
  if ( (v27 & 1) == 0 )
  {
LABEL_46:
    if ( (v30 & 1) == 0 )
      goto LABEL_52;
    goto LABEL_51;
  }
LABEL_50:
  operator delete(v29);
  if ( (v30 & 1) != 0 )
LABEL_51:
    operator delete(v32);
LABEL_52:
  __cxa_guard_abort(&byte_D42B8);
  if ( (v33 & 1) != 0 )
    operator delete(ptr);
  sub_A6F38(v12, (_DWORD)v1, v13, v14, v15, v16, v17.m128i_i8[0]);
  return sub_41EB0();
}

https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/types.html

type signature를 봤을 때 ([B)Ljava/lang/String;이랑 ()Z는 transform이랑 oktorun랑 관련이 있을듯한데

타입을 모르는게 너무 많다

 

https://rond-o.tistory.com/239

 

IDA JNI 분석

원글: 2021. 1. 14. 13:01 수정: 오타 수정 및 이미지 링크 수정 IDA를 이용한 JNI(Java Native Interface) 정적 분석 JNI JNI란 JVM이 native code(C/C++)로 만들어진 코드와 상호작용할 수 있도록 도와주는 인터..

rond-o.tistory.com

https://gist.github.com/Jinmo/048776db75067dcd6c57f1154e65b868

 

Useful when reversing JNI on IDA Pro

Useful when reversing JNI on IDA Pro. GitHub Gist: instantly share code, notes, and snippets.

gist.github.com

이거 두개 참고해서 jni.h를 넣어봄

 

jint __stdcall JNI_OnLoad(JavaVM *vm, void *reserved)
{
  __int64 *v2; // rsi
  unsigned int v3; // ebx
  const char *v4; // r14
  char *v5; // rsi
  __int64 v6; // rbx
  unsigned __int64 v7; // rax
  char *v9; // rax
  char *v10; // rax
  char *v11; // rax
  char *v12; // rax
  int v13; // ebx
  int v14; // edx
  int v15; // ecx
  int v16; // er8
  int v17; // er9
  __m128i v18; // [rsp+0h] [rbp-C8h] BYREF
  char v19; // [rsp+18h] [rbp-B0h] BYREF
  char v20; // [rsp+19h] [rbp-AFh] BYREF
  void *v21; // [rsp+28h] [rbp-A0h]
  char v22; // [rsp+30h] [rbp-98h] BYREF
  char v23; // [rsp+31h] [rbp-97h] BYREF
  void *v24; // [rsp+40h] [rbp-88h]
  char v25; // [rsp+48h] [rbp-80h] BYREF
  char v26; // [rsp+49h] [rbp-7Fh] BYREF
  void *v27; // [rsp+58h] [rbp-70h]
  char v28; // [rsp+60h] [rbp-68h] BYREF
  char v29; // [rsp+61h] [rbp-67h] BYREF
  void *v30; // [rsp+70h] [rbp-58h]
  char v31; // [rsp+78h] [rbp-50h] BYREF
  char v32; // [rsp+79h] [rbp-4Fh] BYREF
  void *v33; // [rsp+88h] [rbp-40h]
  char v34; // [rsp+90h] [rbp-38h] BYREF
  char v35[15]; // [rsp+91h] [rbp-37h] BYREF
  void *ptr; // [rsp+A0h] [rbp-28h]
  __int64 v37; // [rsp+A8h] [rbp-20h] BYREF
  unsigned __int64 v38; // [rsp+B0h] [rbp-18h]

  v38 = __readfsqword(0x28u);
  v2 = &v37;
  v3 = -1;
  if ( ((unsigned int (__fastcall *)(JavaVM *, __int64 *, __int64))(*vm)->GetEnv)(vm, &v37, 65542LL) )
    goto LABEL_13;
  sub_445A0(&v34, off_D4000, (unsigned int)dword_D4008);
  v4 = v35;
  v5 = v35;
  if ( (v34 & 1) != 0 )
    v5 = (char *)ptr;
  v6 = (*(__int64 (__fastcall **)(__int64, char *))(*(_QWORD *)v37 + 48LL))(v37, v5);
  if ( !v6 )
  {
    if ( (v34 & 1) != 0 )
      v4 = (const char *)ptr;
    v3 = -1;
    v2 = (__int64 *)"AdSpamBot";
    __android_log_print(2LL, "AdSpamBot", "Class %s not found.", v4);
LABEL_11:
    if ( (v34 & 1) == 0 )
      goto LABEL_13;
    goto LABEL_12;
  }
  if ( !byte_D42B8 && (unsigned int)__cxa_guard_acquire(&byte_D42B8) )
  {
    sub_445A0(&v31, off_D4010, (unsigned int)dword_D4018);
    if ( (v31 & 1) != 0 )
      v9 = (char *)v33;
    else
      v9 = &v32;
    qword_D4240 = (__int64)v9;
    xmmword_D4248 = (__int128)_mm_unpacklo_epi64(
                                (__m128i)(unsigned __int64)"([B)Ljava/lang/String;",
                                (__m128i)(unsigned __int64)sub_41EB0);
    sub_445A0(&v28, off_D4020, (unsigned int)dword_D4028);
    if ( (v28 & 1) != 0 )
      v10 = (char *)v30;
    else
      v10 = &v29;
    qword_D4258 = (__int64)v10;
    xmmword_D4260 = (__int128)_mm_unpacklo_epi64(
                                (__m128i)(unsigned __int64)"()Z",
                                (__m128i)(unsigned __int64)&loc_41F70);
    sub_445A0(&v25, off_D4030, (unsigned int)dword_D4038);
    if ( (v25 & 1) != 0 )
      v11 = (char *)v27;
    else
      v11 = &v26;
    qword_D4270 = (__int64)v11;
    v18 = (__m128i)(unsigned __int64)"([B)[B";
    xmmword_D4278 = (__int128)_mm_unpacklo_epi64(
                                (__m128i)(unsigned __int64)"([B)[B",
                                (__m128i)(unsigned __int64)sub_41F80);
    sub_445A0(&v22, off_D4040, (unsigned int)dword_D4048);
    if ( (v22 & 1) != 0 )
      v12 = (char *)v24;
    else
      v12 = &v23;
    qword_D4288 = (__int64)v12;
    xmmword_D4290 = (__int128)_mm_unpacklo_epi64(_mm_load_si128(&v18), (__m128i)(unsigned __int64)sub_41FE0);
    sub_445A0(&v19, off_D4050, (unsigned int)dword_D4058);
    if ( (v19 & 1) != 0 )
    {
      qword_D42A0 = (__int64)v21;
      xmmword_D42A8 = (__int128)_mm_unpacklo_epi64(_mm_load_si128(&v18), (__m128i)(unsigned __int64)sub_42040);
      operator delete(v21);
      if ( (v22 & 1) == 0 )
        goto LABEL_35;
    }
    else
    {
      qword_D42A0 = (__int64)&v20;
      xmmword_D42A8 = (__int128)_mm_unpacklo_epi64(_mm_load_si128(&v18), (__m128i)(unsigned __int64)sub_42040);
      if ( (v22 & 1) == 0 )
      {
LABEL_35:
        if ( (v25 & 1) != 0 )
        {
          operator delete(v27);
          if ( (v28 & 1) == 0 )
          {
LABEL_37:
            if ( (v31 & 1) == 0 )
            {
LABEL_39:
              __cxa_guard_release(&byte_D42B8);
              goto LABEL_6;
            }
LABEL_38:
            operator delete(v33);
            goto LABEL_39;
          }
        }
        else if ( (v28 & 1) == 0 )
        {
          goto LABEL_37;
        }
        operator delete(v30);
        if ( (v31 & 1) == 0 )
          goto LABEL_39;
        goto LABEL_38;
      }
    }
    operator delete(v24);
    goto LABEL_35;
  }
LABEL_6:
  LODWORD(v2) = v6;
  v3 = (*(__int64 (__fastcall **)(__int64, __int64, __int64 *, __int64))(*(_QWORD *)v37 + 1720LL))(
         v37,
         v6,
         &qword_D4240,
         5LL);
  if ( v3 )
  {
    v2 = (__int64 *)"AdSpamBot";
    __android_log_print(2LL, "AdSpamBot", "RegisterNatives failed.");
    goto LABEL_11;
  }
  v3 = 65542;
  if ( (v34 & 1) != 0 )
LABEL_12:
    operator delete(ptr);
LABEL_13:
  v7 = __readfsqword(0x28u);
  if ( v7 == v38 )
    return v3;
  v13 = v7;
  if ( (v22 & 1) != 0 )
  {
    operator delete(v24);
    if ( (v25 & 1) == 0 )
    {
LABEL_45:
      if ( (v28 & 1) == 0 )
        goto LABEL_46;
      goto LABEL_50;
    }
  }
  else if ( (v25 & 1) == 0 )
  {
    goto LABEL_45;
  }
  operator delete(v27);
  if ( (v28 & 1) == 0 )
  {
LABEL_46:
    if ( (v31 & 1) == 0 )
      goto LABEL_52;
    goto LABEL_51;
  }
LABEL_50:
  operator delete(v30);
  if ( (v31 & 1) != 0 )
LABEL_51:
    operator delete(v33);
LABEL_52:
  __cxa_guard_abort(&byte_D42B8);
  if ( (v34 & 1) != 0 )
    operator delete(ptr);
  sub_A6F38(v13, (_DWORD)v2, v14, v15, v16, v17, v18.m128i_i8[0]);
  return sub_41EB0();
}

감이 안와서 소스코드봤는데 v37이 JNIEnv* 타입인가보다

 

https://pyxispub.uzuki.live/?p=1689 

 

JNI에서 RegisterNatives 사용하기

아득히 먼 예전(약 5년 이상)에는 JNI를 사용하기 위해 javah 를 사용하여 헤더 파일을 만들고, 헤더 파일에 선언된 메서드를 사용하는 일이 있었다. JNIEXPORT void JNICALL Java_com_github_windsekirun_**_bridge_Jn

pyxispub.uzuki.live

여기서

if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
    return JNI_FALSE;
}

이런 코드를 쓰는데

 

v3 = (*(__int64 (__fastcall **)(__int64, __int64, __int64 *, __int64))(*(_QWORD *)v37 + 1720LL))(
     v37,
     v6,
     &qword_D4240,
     5LL);
if ( v3 )
{
    v2 = (__int64 *)"AdSpamBot";
    __android_log_print(2LL, "AdSpamBot", "RegisterNatives failed.");

여기서 v37을 JNIEnv*로 때려봤어야됐나

 

여튼 바꾸고 타입좀 수정해주니까

jint __stdcall JNI_OnLoad(JavaVM *vm, void *reserved)
{
  JNIEnv **p_env; // rsi
  unsigned int v3; // ebx
  const char *v4; // r14
  char *v5; // rsi
  __int64 v6; // rbx
  unsigned __int64 v7; // rax
  char *v9; // rax
  char *v10; // rax
  char *v11; // rax
  char *v12; // rax
  int v13; // ebx
  int v14; // edx
  int v15; // ecx
  int v16; // er8
  int v17; // er9
  __m128i v18; // [rsp+0h] [rbp-C8h] BYREF
  char v19; // [rsp+18h] [rbp-B0h] BYREF
  char v20; // [rsp+19h] [rbp-AFh] BYREF
  void *v21; // [rsp+28h] [rbp-A0h]
  char v22; // [rsp+30h] [rbp-98h] BYREF
  char v23; // [rsp+31h] [rbp-97h] BYREF
  void *v24; // [rsp+40h] [rbp-88h]
  char v25; // [rsp+48h] [rbp-80h] BYREF
  char v26; // [rsp+49h] [rbp-7Fh] BYREF
  void *v27; // [rsp+58h] [rbp-70h]
  char v28; // [rsp+60h] [rbp-68h] BYREF
  char v29; // [rsp+61h] [rbp-67h] BYREF
  void *v30; // [rsp+70h] [rbp-58h]
  char v31; // [rsp+78h] [rbp-50h] BYREF
  char v32; // [rsp+79h] [rbp-4Fh] BYREF
  void *v33; // [rsp+88h] [rbp-40h]
  char v34[16]; // [rsp+90h] [rbp-38h] BYREF
  void *ptr; // [rsp+A0h] [rbp-28h]
  JNIEnv *env; // [rsp+A8h] [rbp-20h] BYREF
  unsigned __int64 v37; // [rsp+B0h] [rbp-18h]

  v37 = __readfsqword(0x28u);
  p_env = &env;
  v3 = -1;
  if ( ((unsigned int (__fastcall *)(JavaVM *, JNIEnv **, __int64))(*vm)->GetEnv)(vm, &env, 65542LL) )
    goto LABEL_13;
  sub_445A0(v34, off_D4000, (unsigned int)dword_D4008);
  v4 = &v34[1];
  v5 = &v34[1];
  if ( (v34[0] & 1) != 0 )
    v5 = (char *)ptr;
  v6 = ((__int64 (__fastcall *)(JNIEnv *, char *))(*env)->FindClass)(env, v5);
  if ( !v6 )
  {
    if ( (v34[0] & 1) != 0 )
      v4 = (const char *)ptr;
    v3 = -1;
    p_env = (JNIEnv **)"AdSpamBot";
    __android_log_print(2LL, "AdSpamBot", "Class %s not found.", v4);
LABEL_11:
    if ( (v34[0] & 1) == 0 )
      goto LABEL_13;
    goto LABEL_12;
  }
  if ( !byte_D42B8 && (unsigned int)__cxa_guard_acquire(&byte_D42B8) )
  {
    sub_445A0(&v31, off_D4010, (unsigned int)dword_D4018);
    if ( (v31 & 1) != 0 )
      v9 = (char *)v33;
    else
      v9 = &v32;
    qword_D4240 = (__int64)v9;
    xmmword_D4248 = (__int128)_mm_unpacklo_epi64(
                                (__m128i)(unsigned __int64)"([B)Ljava/lang/String;",
                                (__m128i)(unsigned __int64)sub_41EB0);
    sub_445A0(&v28, off_D4020, (unsigned int)dword_D4028);
    if ( (v28 & 1) != 0 )
      v10 = (char *)v30;
    else
      v10 = &v29;
    qword_D4258 = (__int64)v10;
    xmmword_D4260 = (__int128)_mm_unpacklo_epi64(
                                (__m128i)(unsigned __int64)"()Z",
                                (__m128i)(unsigned __int64)&loc_41F70);
    sub_445A0(&v25, off_D4030, (unsigned int)dword_D4038);
    if ( (v25 & 1) != 0 )
      v11 = (char *)v27;
    else
      v11 = &v26;
    qword_D4270 = (__int64)v11;
    v18 = (__m128i)(unsigned __int64)"([B)[B";
    xmmword_D4278 = (__int128)_mm_unpacklo_epi64(
                                (__m128i)(unsigned __int64)"([B)[B",
                                (__m128i)(unsigned __int64)sub_41F80);
    sub_445A0(&v22, off_D4040, (unsigned int)dword_D4048);
    if ( (v22 & 1) != 0 )
      v12 = (char *)v24;
    else
      v12 = &v23;
    qword_D4288 = (__int64)v12;
    xmmword_D4290 = (__int128)_mm_unpacklo_epi64(_mm_load_si128(&v18), (__m128i)(unsigned __int64)sub_41FE0);
    sub_445A0(&v19, off_D4050, (unsigned int)dword_D4058);
    if ( (v19 & 1) != 0 )
    {
      qword_D42A0 = (__int64)v21;
      xmmword_D42A8 = (__int128)_mm_unpacklo_epi64(_mm_load_si128(&v18), (__m128i)(unsigned __int64)sub_42040);
      operator delete(v21);
      if ( (v22 & 1) == 0 )
        goto LABEL_35;
    }
    else
    {
      qword_D42A0 = (__int64)&v20;
      xmmword_D42A8 = (__int128)_mm_unpacklo_epi64(_mm_load_si128(&v18), (__m128i)(unsigned __int64)sub_42040);
      if ( (v22 & 1) == 0 )
      {
LABEL_35:
        if ( (v25 & 1) != 0 )
        {
          operator delete(v27);
          if ( (v28 & 1) == 0 )
          {
LABEL_37:
            if ( (v31 & 1) == 0 )
            {
LABEL_39:
              __cxa_guard_release(&byte_D42B8);
              goto LABEL_6;
            }
LABEL_38:
            operator delete(v33);
            goto LABEL_39;
          }
        }
        else if ( (v28 & 1) == 0 )
        {
          goto LABEL_37;
        }
        operator delete(v30);
        if ( (v31 & 1) == 0 )
          goto LABEL_39;
        goto LABEL_38;
      }
    }
    operator delete(v24);
    goto LABEL_35;
  }
LABEL_6:
  LODWORD(p_env) = v6;
  v3 = ((__int64 (__fastcall *)(JNIEnv *, __int64, __int64 *, __int64))(*env)->RegisterNatives)(
         env,
         v6,
         &qword_D4240,
         5LL);
  if ( v3 )
  {
    p_env = (JNIEnv **)"AdSpamBot";
    __android_log_print(2LL, "AdSpamBot", "RegisterNatives failed.");
    goto LABEL_11;
  }
  v3 = 65542;
  if ( (v34[0] & 1) != 0 )
LABEL_12:
    operator delete(ptr);
LABEL_13:
  v7 = __readfsqword(0x28u);
  if ( v7 == v37 )
    return v3;
  v13 = v7;
  if ( (v22 & 1) != 0 )
  {
    operator delete(v24);
    if ( (v25 & 1) == 0 )
    {
LABEL_45:
      if ( (v28 & 1) == 0 )
        goto LABEL_46;
      goto LABEL_50;
    }
  }
  else if ( (v25 & 1) == 0 )
  {
    goto LABEL_45;
  }
  operator delete(v27);
  if ( (v28 & 1) == 0 )
  {
LABEL_46:
    if ( (v31 & 1) == 0 )
      goto LABEL_52;
    goto LABEL_51;
  }
LABEL_50:
  operator delete(v30);
  if ( (v31 & 1) != 0 )
LABEL_51:
    operator delete(v33);
LABEL_52:
  __cxa_guard_abort(&byte_D42B8);
  if ( (v34[0] & 1) != 0 )
    operator delete(ptr);
  sub_A6F38(v13, (_DWORD)p_env, v14, v15, v16, v17, v18.m128i_i8[0]);
  return sub_41EB0();
}

FindClass, RegisterNatives같은게 보임

 

sub_445A0(v34, off_D4000, (unsigned int)dword_D4008);
v4 = &v34[1];
v5 = &v34[1];
if ( (v34[0] & 1) != 0 )
v5 = (char *)ptr;
v6 = ((__int64 (__fastcall *)(JNIEnv *, char *))(*env)->FindClass)(env, v5);

FindClass 두번째 인자로는 클래스 이름이 들어감 -> &v34[1]

 

sub_445a0 -> decode임

char *__fastcall sub_445A0(_QWORD *a1, char *a2, unsigned int a3)
{
  __int64 v3; // r12
  unsigned __int8 v4; // al
  __int64 i; // rbp
  char v6; // r15
  __int64 v7; // r13
  unsigned __int64 v8; // rcx
  char *v9; // rax

  *(_OWORD *)a1 = 0LL;
  a1[2] = 0LL;
  if ( (int)a3 > 0 )
  {
    v3 = a3 - 1LL;
    v4 = 0;
    for ( i = 0LL; ; ++i )
    {
      v6 = a2[i];
      if ( (v4 & 1) != 0 )
      {
        v7 = a1[1];
        v8 = (*a1 & 0xFFFFFFFFFFFFFFFELL) - 1;
        if ( v7 != v8 )
          goto LABEL_5;
      }
      else
      {
        v7 = v4 >> 1;
        v8 = 22LL;
        if ( v7 != 22 )
        {
LABEL_5:
          if ( (v4 & 1) != 0 )
            goto LABEL_6;
          goto LABEL_9;
        }
      }
      sub_43520(a1, v8, 1LL, v8, v8, 0LL, 0LL);
      if ( (*(_BYTE *)a1 & 1) != 0 )
      {
LABEL_6:
        v9 = (char *)a1[2];
        a1[1] = v7 + 1;
        goto LABEL_10;
      }
LABEL_9:
      *(_BYTE *)a1 = 2 * v7 + 2;
      v9 = (char *)a1 + 1;
LABEL_10:
      v9[v7] = aCfugdw9zigv2au[i & 0x1F] ^ v6;
      v9[v7 + 1] = 0;
      if ( v3 == i )
        return (char *)a1;
      v4 = *(_BYTE *)a1;
    }
  }
  return (char *)a1;
}

char형 a1[1] ~ : 클래스 이름이 들어가야됨

a2 : 인코딩된 클래스 이름

 

v3 = a3 - 1LL;
...
if ( v3 == i )
    return (char *)a1;

에서 a3은 length로 추정

 

a3으로는 dword_D4018, dword_D4028, ... , dword_D4058이 들어감

0x15, 9, 7, 7, 7, 9

 

...
    v4 = 0;
    for ( i = 0LL; ; ++i )
    {
      v6 = a2[i];
      if ( (v4 & 1) != 0 )
      {
        v7 = a1[1];
        v8 = (*a1 & 0xFFFFFFFFFFFFFFFELL) - 1;
        if ( v7 != v8 )
          goto LABEL_5;
      }
...
LABEL_9:
      *(_BYTE *)a1 = 2 * v7 + 2; // *(_BYTE *)a1는 짝수
      v9 = (char *)a1 + 1;
LABEL_10:
      ...
      v4 = *(_BYTE *)a1; // v4는 짝수
}

v4는 짝수라 if ( (v4 & 1) != 0 )로 못갈듯

 

...
        v7 = v4 >> 1; // (1)
        v8 = 22LL;
        if ( v7 != 22 )
        {
LABEL_5:
          if ( (v4 & 1) != 0 )
            goto LABEL_6;
          goto LABEL_9;
        }
      }
...
LABEL_9:
      *(_BYTE *)a1 = 2 * v7 + 2; // *(_BYTE *)a1 = 2*(v7+1)
      v9 = (char *)a1 + 1;
LABEL_10:
      ...
      v4 = *(_BYTE *)a1; // (2) v4 = 2*(v7+1)

(2)에서 2*(v7+1)을 넣은걸

(1)에서 >>1 하는거라

그냥 다음 인덱스가 22인지 확인하는거랑 같음

제일 긴게 0x15라 신경 안써도 됨 (뒤에 알았는데 더 긴게 있긴 하다)

 

정리하면

def decode(str, len):
    res = [0]*len
    arr = b'cFUgdW9ZIGV2aUcgYW5ub0cgcmV2ZU4='
    for i in range(len):
        res = arr[i & 0x1f] ^ str[i]

    return res

 

# decode.py

from pwn import *

def decode(str, len):
    res = [0]*len
    arr = b'cFUgdW9ZIGV2aUcgYW5ub0cgcmV2ZU4='
    for i in range(len):
        res[i] = arr[i & 0x1f] ^ str[i]

    return ''.join(map(chr, res))

with open("./app/resources/lib/x86_64/libnative-lib.so", 'rb') as f:
    e = f.read()

start = [0xAD884, 0xAD89A, 0xAD8A4, 0xAD8AC, 0xAD8B4, 0xAD8BC]
length = [0x15, 9, 7, 7, 7, 9]
for i in range(6):
    print(decode(e[start[i]:start[i]+22], length[i]))
$ python3 decode.py
ad/spam/NativeAdapter
transform
oktorun
encrypt
decrypt
declicstr

 

encrypt(0x41f80)부터 보자

__int64 __fastcall encrypt(__int64 a1, __int64 a2, __int64 a3)
{
  __int64 v4; // rax

  v4 = ((__int64 (__fastcall *)(__int64, __int64))loc_439D0)(a1, 1LL);
  if ( v4 )
    return sub_43E10(a1, v4, a3);
  __android_log_print(2LL, "AdSpamBot", "Couldn't get cipher.");
  return 0LL;
}

 

loc_439D0을 보면

decode(v43, (char *)off_D40C8, dword_D40D0);
if ( ((__int64)v43[0] & 1) != 0 )
  v11 = (char *)ptr;
else
  v11 = (char *)v43 + 1;
v16 = ((__int64 (__fastcall *)(JNIEnv *, char *))(*env)->FindClass)(env, v11);
...

여기도 decode 많음

 

start = [0xAD9DC, 0xAD9F7, 0xADA50, 0xAD9B4, 0xADA03, 0xADA7A]
length = [0x13, 0xb, 0x29, 3, 4, 0x17]
for i in range(6):
    print(decode(e[start[i]:start[i]+0x29], length[i]))
javax/crypto/Cipher
getInstance
(Ljava/lang/String;)Ljavax/crypto/Cipher;
AES
init
(ILjava/security/Key;)V

길이 신경안써도 잘나오네

 

sub_436F0를 호출하는데 여기도 decode가 있다

같은 방법으로 decode 해주면

javax/crypto/spec/SecretKeySpec
<init>
([BLjava/lang/String;)V
AES

 

일단 java에서 AES를 어케쓰는지 찾아봄

Cipher cipher = Cipher.getInstance('AES');
SecretKey key = new SecretKeySpec(keyData,"AES");
IvParameterSpec iv = ~~;
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
byte[] cipherText = cipher.doFinal(input.getBytes());

대충 이런식

 

https://docs.microsoft.com/en-us/dotnet/api/java.interop.jnienvironment.arrays.setbytearrayregion?view=xamarin-android-sdk-12 

 

JniEnvironment.Arrays.SetByteArrayRegion(JniObjectReference, Int32, Int32, SByte*) Method (Java.Interop)

Learn more about the Java.Interop.JniEnvironment.Arrays.SetByteArrayRegion in the Java.Interop namespace.

docs.microsoft.com

public static void SetByteArrayRegion (Java.Interop.JniObjectReference array, int start, int length, sbyte* buffer);

 배열 초기화 함수

 

bytearray_ = ((__int64 (__fastcall *)(JNIEnv *, __int64))(*env)->NewByteArray)(env, 16LL);
((void (__fastcall *)(JNIEnv *, __int64, _QWORD, __int64, const char *))(*env)->SetByteArrayRegion)(
env,
bytearray_,
0LL,
16LL,
"eaW~IFhnvlIoneLl");
decode(v22, (char *)off_D4098, dword_D40A0);
if ( ((__int64)v22[0] & 1) != 0 )
v10 = (char *)ptr;
str_aes = ((__int64 (__fastcall *)(JNIEnv *, char *))(*env)->NewStringUTF)(env, v10);// AES
v1 = (char *)v6;
v15 = sub_43910(env, v6, init_, bytearray_, str_aes);// v6 : javax/crypto/spec/SecretKeySpec

sub_436F0이 SecretKeySpec("eaW~IFhnvlIoneLl", "AES") 인 냄새가 난다

 

https://stackoverflow.com/questions/6258047/java-default-crypto-aes-behavior

 

Java default Crypto/AES behavior

Does anyone know what the default Java crypto behavior is for: SecretKeySpec localSecretKeySpec = new SecretKeySpec(arrayOfByte, "AES"); Cipher localCipher = Cipher.getInstance("AES"); Specifical...

stackoverflow.com

AES/ECB/PKCS5Padding 사용

 

import base64
from Crypto.Cipher import AES
from pwn import *

key = b'eaW~IFhnvlIoneLl'

p = remote("adspam.2021.ctfcompetition.com", 1337)

cipher = AES.new(key, AES.MODE_ECB)
p.sendlineafter(b'\n', base64.b64encode(cipher.encrypt(b'\x00'*16)))

encrypted = base64.b64decode(p.recvline()[:-1])
cipher = AES.new(key, AES.MODE_ECB)
print(cipher.decrypt(encrypted))

p.close()
[+] Opening connection to adspam.2021.ctfcompetition.com on port 1337: Done
b'{"cmd": -1, "data": "Expecting value: line 1 column 1 (char 0)"}'
[*] Closed connection to adspam.2021.ctfcompetition.com port 1337

테스트로 해봤는데 decrypt가 된다

 

Expecting value: line 1 column 1 ~~ 에러는 찾아보니까 JSONDecodeError가 많이 나옴

import base64
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from pwn import *
import json

def encrypt(pt):
    cipher = AES.new(key, AES.MODE_ECB)
    print(pad(json.dumps(pt).encode(), 16))
    return base64.b64encode(cipher.encrypt(pad(json.dumps(pt).encode(), 16)))

def decrypt(ct):
    cipher = AES.new(key, AES.MODE_ECB)
    return cipher.decrypt(base64.b64decode(ct))

key = b'eaW~IFhnvlIoneLl'

p = remote("adspam.2021.ctfcompetition.com", 1337)

payload = {"cmd":"asdf"}
p.sendlineafter(b'\n', encrypt(payload))

encrypted = p.recvline()[:-1]
print(decrypt(encrypted))

p.close()
[+] Opening connection to adspam.2021.ctfcompetition.com on port 1337: Done
b'{"cmd": -1, "data": "\'license\'"}'
[*] Closed connection to adspam.2021.ctfcompetition.com port 1337

json으로 날리니까 다르게옴

 

license 문자열을 찾으니까 declicstr에서 씀

decrypt license인가?

 

sub_43FB0을 보니까 RSA를 씀

bytearray_ = ((__int64 (__fastcall *)(JNIEnv *, __int64))(*env)->NewByteArray)(env, 162LL);
v12 = 0LL;
((void (__fastcall *)(JNIEnv *, __int64, _QWORD, __int64, void *))(*env)->SetByteArrayRegion)(
env,
bytearray_,
0LL,
162LL,
&unk_AF190);
v3 = (char *)v6;
v13 = sub_43910(env, v6, v10, bytearray_);

얘도 뭔가 key 설정하는 부분이 있다

 

https://zzang9iu.tistory.com/39

 

JAVA(RSA 암호화,복호화)

RSA 암호화는 공개키 암호화 방식이다. PRIVATE KEY PUBLIC KEY 두 키를 생성하여 . 암호화, 복호화 하는데 사용한다. PUBLIC KEY를 이용하여 암호화하고 PRIVATE KEY를 이용하여 복호화한다. JAVA CODE로 알아보.

zzang9iu.tistory.com

X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(bytePublicKey);
PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);

rsa는 대충 이런식

 

따라가면서 보면

public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeySpecException {
    String encoded = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCwAb8x28aiR8rMqNJ5VQcgoL+TV0NeRlUsZeU2znF6tgmdSq/Hn/0Z5GREhGt2EzaO1r9HhnEzaR816rjp5TaIIu7Mc8QA2CciqPOW0iH+/kVUN/pLWbBkXOtdHkLdl+uoGCs3RFKvxBrBm7RZsNAtvNXpO3z7UMzoqK5N3QbBdwIDAQAB";
    byte[] key = Base64.getDecoder().decode(encoded.getBytes());
    X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(key);
    KeyFactory keyFactory = KeyFactory.getInstance("RSA");
    PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);
    System.out.println(publicKey);
}
Sun RSA public key, 1024 bits
  params: null
  modulus: 123596194752125979169492805898717739603063620825567453811699517077883880522042319510992511000584675998759365763564255380979966851592871801417286328813040909357944964680643572428136557634939634304256208583286122710159831699682677545905872084440840115646364525758632063374092374969271133111500901869640594407799
  public exponent: 65537

key가 이거로 설정됨 (python에서 RSA.import_key(encoded) 한거랑 같나보다)

 

sub_43D50(env, cipher, init, 2LL, pubkey);

DECRYPT_MODE가 2임

공개키로 복호화하네

 

고러면 declicstr는

def declicstr(encrypted):
    modulus = 123596194752125979169492805898717739603063620825567453811699517077883880522042319510992511000584675998759365763564255380979966851592871801417286328813040909357944964680643572428136557634939634304256208583286122710159831699682677545905872084440840115646364525758632063374092374969271133111500901869640594407799
    exponent = 65537
    encrypted = bytes_to_long(base64.b64decode(encrypted))
    return long_to_bytes(pow(encrypted, exponent, modulus))

 

이제 다시 jadx로 돌아가보면

a/a/d.java에 transform 겁나많네

 

transform은 그냥 인자로 들어간애를 decode 해줌

a/a/d.java에 있는거 decode 하면

os_version
api_level
device
name
is_admin
device_info
license

 

이제 writeup에서 본 문자열들이 보인다

 

a.a.f.a()

public void a() {
            ...
            JSONObject jSONObject = new JSONObject();
            BufferedReader lic = new BufferedReader(new InputStreamReader(context.getResources().openRawResource(R.raw.lic)));
            StringBuilder sb = new StringBuilder();
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            while (true) {
                String line = lic.readLine();
                if (line == null) {
                    break;
                }
                byteArrayOutputStream.write(NativeAdapter.declicstr(Base64.decode(line, 0)));
                sb.append(line);
                sb.append("::");
            }
            byte[] byteArray = byteArrayOutputStream.toByteArray(); // dclicstr(R.raw.lic 디코딩된 line) 연결한거
            String name = new String(dVar.a(byteArray, 0));
            int length = name.length() + 1;
            int is_admin = Integer.parseInt(new String(dVar.a(byteArray, new String(dVar.a(byteArray, length)).length() + 1 + length)));
            String license = sb.toString(); // R.raw.lic line1::line2:: ...
            try {
                jSONObject.put(d.str_name, name);
                jSONObject.put(d.str_is_admin, is_admin);
                String str_device_info = d.str_device_info;
                JSONObject json_device_info = new JSONObject();
                // {"name":<string>, "is_admin":<int>, "device_info":<json object>}
                try {
                    json_device_info.put(d.str_os_version, System.getProperty("os.version"));
                    json_device_info.put(d.str_api_level, Build.VERSION.SDK_INT);
                    json_device_info.put(d.str_device, Build.DEVICE);
                    // {"os_version":System.getProperty("os.version"), "api_level":Build.VERSION.SDK_INT, "device":Build.DEVICE}
                } catch (JSONException e) {
                    e.printStackTrace();
                }
                jSONObject.put(str_device_info, json_device_info);
                jSONObject.put(d.str_license, license);
                // {"name":<string>, "is_admin":<int>, "device_info":{"os_version":System.getProperty("os.version"), "api_level":Build.VERSION.SDK_INT, "device":Build.DEVICE}, "license":"line1::line2::..."}
            } catch (JSONException e2) {
                e2.printStackTrace();
            }
            new a(this, this.f16c).execute(ip, port, Base64.encodeToString(NativeAdapter.encrypt(jSONObject.toString().getBytes(StandardCharsets.UTF_8)), 2));
        }
    }

{"name":<string>, "is_admin":<int>, "device_info":{"os_version":System.getProperty("os.version"), "api_level":Build.VERSION.SDK_INT, "device":Build.DEVICE}, "license":"line1::line2::..."} 을 서버로 날리는거로 보임

 

declicstr 결과부터 보면

def declicstr(encrypted):
    modulus = 123596194752125979169492805898717739603063620825567453811699517077883880522042319510992511000584675998759365763564255380979966851592871801417286328813040909357944964680643572428136557634939634304256208583286122710159831699682677545905872084440840115646364525758632063374092374969271133111500901869640594407799
    exponent = 65537
    encrypted = bytes_to_long(base64.b64decode(encrypted))
    return long_to_bytes(pow(encrypted, exponent, modulus))

# R.raw.lic
licenses = '''QIknTsIjeUEF9yJjeZ/kPPfTlSm8vzMU4LWjzfSXvN+OSqBu3iNgZJgeW7fc8oltH9MprO9nI8vxgsjO/VA4t7YuNm16a7elPVAHqD4dXtzngnZPpsbek3Rc/We/WQ5YxXHgUt7YJ6tcd4wH3fhduC9tl/E5elwJL/YAcbD4mT8=
o9kjqYWCBKMgodl1JvDiscUeRjh9Ip9HcC7tHskoYqNQfAPE0XvSAKBSOFgleNHzVY9BVkfxmutgn/kVXUs3yl/qAurc4jokg0eA/v3flnnkWxqTOh4vv0yfr7PGXqwHk4qUFK1SldZ4VsLhd8PAb0aHj22E5b4U5jeJ16z187E=
gpDbCb0BmUZfdKVIZgF08lQ80K9SeUsRadZG+UUjE7wI1NRZ1evLk2GQ3sqskGHFKlPg8cTR2Xy69WedNu4QLboOWm/w13ocOvHwCoiQ1ZdmibgnhMQBznqpjpBnL083YMRYskcUX68R2PFaXY3taV7MoG1DyQWFRfdr/CnLyS8=
ZBLhwMu0DbgpUANm2ukYldrppJERiH1Tgp02CRB5I4dDP8n4+ZCv33ScspELtgAKHhiwIVksQVsnwDLsQRi6nqq9nrIwqSHMR0TwOe6UKTpAegbH53FXtriopPHfLuI2M45SzJ88GFjXy7wfOOjwDYe4KKO9KU8+LGD15Au73EM=
Hygv+bTtsnI9IBf44GkvoF38r3g5zBB7uyYT7PTlbjhCdgYRwRayutI3vY+n66xM7GOFgUFVIBI5+OBDnvazLNttjGomPED/OXlImndWvrZxYcaKaE3vYGPezorV0xwPahGGq/DWafPKdYxLxwICq1GXKYNAckCZIqfpGbJRRwg=
GARMZAX7fQN7i7Wnp4J6HxMTLe9+VM/wGJs+zN6b9IOmynh2gIkGjmssfOA9KdYydqBLEOJymayH8HeyrtInhhQNR3el8A5n8GMEMkyF1gUFAiSEPyhNeWWOj2IAHGNNwccmF7QywdfOUGjsTNFbrW6Yl5QLLAmMbA95qF0IERk=
YWlx8Cok1x/3ZsW9JKIsKj9UpBaCNkXSPiVXUrNX1IDZE0B8iNr3iliOr90TW0BvsIaFEwvDTlcESXJ8kLc3iZq0fm1lgujfM7Z156VdxEPjr9LplcEZ9ZVhYGNtVyGIRcouUDJHu3FVfXQ1XesaNlNHOb50hADprsw3RnTAGbU=
I3dsx2vSfXxZ1/QlMbwYPRFEZBtOuB8qLEY8cqFVtYjMluNWSkbHAYB+kwCBEv3yuoOjkdQEfqq4pS+K0ka1+pFDyss8sSbV3OiZdpRf40SS/pZxw2duJr9uDd1DdX8mST7fdjqj0V1a2ZBMpqaEI2gFlCwzXlfZBC47LKNiM+8=
ow7r5VJMGfSf0odNKxzBpUtSJdj8gHdt+Z7Xu54MAdsnUParSjrtRI4yJYzcW4toOFmDdSs5SERR289yohYI5hHSWLElv/44O+g4M08F5qpwCmOp5otW32qRG1RnhqR95evH44nOyK24UnpvWlebNwVhniSu4A7znjluGRrao/U=
TeGqGWv8ZmsY/rFq1puW9N+01TWTKJm8qzUuY/7JUCPDJ1AR6Y3XsPb73FuSVHPL63sjiuCTiKTRSUDzBE0VBfo59rtOKI05k64Jrz88nODD7BiK7ssacsOr2dAFGQKgBaWV2jitSAdxtCmh9sDpYsfs0/vXBBfVLqfVZDfAVGQ=
Al3QWY+nNFoLezt+rSdbWmqp7iZ+rR9pnM35IJNZ63bLQeM3CUvULVczhrM3toXNLCY7xmAT4jg+u0uDAjanaKMB+T1Tmym7aaCqwCfHYVFn5nw+tw54e13CLxj7OO+e847+XH8DtK/BiA+n03vPnt/cEDPvIM59sPsjHThJvpk=
VOGr60qxiO1r0YlKnrIWbQu7UhBmtBeNw2NDQnoNU3H1mjVEs/ji3AYuEGc2HGKINByq7Mpb4mWKD2oH5ii/UZDpxbzCFlJrjvjEG25c9Hhf2fiQHvRXmJd8iA8YdffBii3csCjaydLFSX6Vn7XPg+/PF/TdM1zUiLTJZX4LXRw=
ELL9maLDpdmmEgaT76qtw9IugtaQX2r7V7QVqMKXQcbwq7o0dvaO3+yMt6m5K5Milm4JSNwX/810YUaoAsHNuaIavuLRsxbP3b6KnKxaKz3EDgyhye2en3U1EZouiLljBB0bKz8rAtyGdolWDdNoKjvLhv7x2edc05HQZOt3aiA='''.split()

license = b''
for l in licenses:
    license += declicstr(l)

print(license)
b'\x0b1337_hacker$798b7dd4-d171-11eb-5149-1fa59603ced5\x010\x00'

여기서 name이랑 is_admin을 뽑을 수 있음

 

/*
dVar.a
public final byte[] a(byte[] bArr, int i) {
    int i2 = bArr[i];
    int i3 = i + 1;
    return Arrays.copyOfRange(bArr, i3, i2 + i3);
}
*/
String name = new String(dVar.a(byteArray, 0));
int length = name.length() + 1;
int is_admin = Integer.parseInt(new String(dVar.a(byteArray, new String(dVar.a(byteArray, length)).length() + 1 + length)));
def a(arr, i):
    return arr[i+1:arr[i]+i+1]

name = a(license, 0)
is_admin = a(license, len(a(license, len(name)+1)) + 1 + len(name) + 1)

print(name, is_admin)
b'1337_hacker' b'0'

name : 1337_hacker

is_admin : 0

 

{"name":f"{name.decode()}", "is_admin":0, "device_info":{"os_version":123, "api_level":30, "device":"generic_x86"}, "license":f"{'::'.join(licenses)}"}
{"cmd": 2, "data": "https://youtu.be/ZC42DTBb4Rk"}

 

?

 

license[50]~ : is_admin 결정

declicstr 결과가 4바이트씩 나옴

 

이것저것 테스트해보니까

declicstr 이후 is_admin에 저장되는 값이 0이 아니게 라이센스를 조작하면 플래그가 나온다

 

declicstr 결과가

license[0] : b'\x0b133'
license[1] : b'7_ha'
license[2] : b'cker'
license[3] : b'$798'
license[4] : b'b7dd'
license[5] : b'4-d1'
license[6] : b'71-1'
license[7] : b'1eb-'
license[8] : b'5149'
license[9] : b'-1fa'
license[10] : b'5960'
license[11] : b'3ced'
license[12] : b'5\x010\x00'

이렇게 나오는데

 

license[license[50]:(license[49] + 50)] 에 숫자만 집어넣으면

int is_admin = Integer.parseInt(~);

에서 에러가 안남

-> 숫자로만 구성된 블록(8 or 10)을 겁나많이 붙이면 is_admin이 0이 아니게 만들 수 있음

 

import base64
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from pwn import *
import json
from Crypto.Util.number import *

def decode(str, len):
    res = [0]*len
    arr = b'cFUgdW9ZIGV2aUcgYW5ub0cgcmV2ZU4='
    for i in range(len):
        res[i] = arr[i & 0x1f] ^ str[i]

    return ''.join(map(chr, res))

def encrypt(pt):
    cipher = AES.new(key, AES.MODE_ECB)
    return base64.b64encode(cipher.encrypt(pad(json.dumps(pt).encode(), 16)))

def decrypt(ct):
    cipher = AES.new(key, AES.MODE_ECB)
    return unpad(cipher.decrypt(base64.b64decode(ct)), 16)

def declicstr(encrypted):
    modulus = 123596194752125979169492805898717739603063620825567453811699517077883880522042319510992511000584675998759365763564255380979966851592871801417286328813040909357944964680643572428136557634939634304256208583286122710159831699682677545905872084440840115646364525758632063374092374969271133111500901869640594407799
    exponent = 65537
    encrypted = bytes_to_long(base64.b64decode(encrypted))
    return long_to_bytes(pow(encrypted, exponent, modulus))

key = b'eaW~IFhnvlIoneLl'

# R.raw.lic
licenses = '''QIknTsIjeUEF9yJjeZ/kPPfTlSm8vzMU4LWjzfSXvN+OSqBu3iNgZJgeW7fc8oltH9MprO9nI8vxgsjO/VA4t7YuNm16a7elPVAHqD4dXtzngnZPpsbek3Rc/We/WQ5YxXHgUt7YJ6tcd4wH3fhduC9tl/E5elwJL/YAcbD4mT8=
o9kjqYWCBKMgodl1JvDiscUeRjh9Ip9HcC7tHskoYqNQfAPE0XvSAKBSOFgleNHzVY9BVkfxmutgn/kVXUs3yl/qAurc4jokg0eA/v3flnnkWxqTOh4vv0yfr7PGXqwHk4qUFK1SldZ4VsLhd8PAb0aHj22E5b4U5jeJ16z187E=
gpDbCb0BmUZfdKVIZgF08lQ80K9SeUsRadZG+UUjE7wI1NRZ1evLk2GQ3sqskGHFKlPg8cTR2Xy69WedNu4QLboOWm/w13ocOvHwCoiQ1ZdmibgnhMQBznqpjpBnL083YMRYskcUX68R2PFaXY3taV7MoG1DyQWFRfdr/CnLyS8=
ZBLhwMu0DbgpUANm2ukYldrppJERiH1Tgp02CRB5I4dDP8n4+ZCv33ScspELtgAKHhiwIVksQVsnwDLsQRi6nqq9nrIwqSHMR0TwOe6UKTpAegbH53FXtriopPHfLuI2M45SzJ88GFjXy7wfOOjwDYe4KKO9KU8+LGD15Au73EM=
Hygv+bTtsnI9IBf44GkvoF38r3g5zBB7uyYT7PTlbjhCdgYRwRayutI3vY+n66xM7GOFgUFVIBI5+OBDnvazLNttjGomPED/OXlImndWvrZxYcaKaE3vYGPezorV0xwPahGGq/DWafPKdYxLxwICq1GXKYNAckCZIqfpGbJRRwg=
GARMZAX7fQN7i7Wnp4J6HxMTLe9+VM/wGJs+zN6b9IOmynh2gIkGjmssfOA9KdYydqBLEOJymayH8HeyrtInhhQNR3el8A5n8GMEMkyF1gUFAiSEPyhNeWWOj2IAHGNNwccmF7QywdfOUGjsTNFbrW6Yl5QLLAmMbA95qF0IERk=
YWlx8Cok1x/3ZsW9JKIsKj9UpBaCNkXSPiVXUrNX1IDZE0B8iNr3iliOr90TW0BvsIaFEwvDTlcESXJ8kLc3iZq0fm1lgujfM7Z156VdxEPjr9LplcEZ9ZVhYGNtVyGIRcouUDJHu3FVfXQ1XesaNlNHOb50hADprsw3RnTAGbU=
I3dsx2vSfXxZ1/QlMbwYPRFEZBtOuB8qLEY8cqFVtYjMluNWSkbHAYB+kwCBEv3yuoOjkdQEfqq4pS+K0ka1+pFDyss8sSbV3OiZdpRf40SS/pZxw2duJr9uDd1DdX8mST7fdjqj0V1a2ZBMpqaEI2gFlCwzXlfZBC47LKNiM+8=
ow7r5VJMGfSf0odNKxzBpUtSJdj8gHdt+Z7Xu54MAdsnUParSjrtRI4yJYzcW4toOFmDdSs5SERR289yohYI5hHSWLElv/44O+g4M08F5qpwCmOp5otW32qRG1RnhqR95evH44nOyK24UnpvWlebNwVhniSu4A7znjluGRrao/U=
TeGqGWv8ZmsY/rFq1puW9N+01TWTKJm8qzUuY/7JUCPDJ1AR6Y3XsPb73FuSVHPL63sjiuCTiKTRSUDzBE0VBfo59rtOKI05k64Jrz88nODD7BiK7ssacsOr2dAFGQKgBaWV2jitSAdxtCmh9sDpYsfs0/vXBBfVLqfVZDfAVGQ=
Al3QWY+nNFoLezt+rSdbWmqp7iZ+rR9pnM35IJNZ63bLQeM3CUvULVczhrM3toXNLCY7xmAT4jg+u0uDAjanaKMB+T1Tmym7aaCqwCfHYVFn5nw+tw54e13CLxj7OO+e847+XH8DtK/BiA+n03vPnt/cEDPvIM59sPsjHThJvpk=
VOGr60qxiO1r0YlKnrIWbQu7UhBmtBeNw2NDQnoNU3H1mjVEs/ji3AYuEGc2HGKINByq7Mpb4mWKD2oH5ii/UZDpxbzCFlJrjvjEG25c9Hhf2fiQHvRXmJd8iA8YdffBii3csCjaydLFSX6Vn7XPg+/PF/TdM1zUiLTJZX4LXRw=
ELL9maLDpdmmEgaT76qtw9IugtaQX2r7V7QVqMKXQcbwq7o0dvaO3+yMt6m5K5Milm4JSNwX/810YUaoAsHNuaIavuLRsxbP3b6KnKxaKz3EDgyhye2en3U1EZouiLljBB0bKz8rAtyGdolWDdNoKjvLhv7x2edc05HQZOt3aiA='''.split()

license = b''
for l in licenses:
    license += declicstr(l)

# print(license)

def a(arr, i):
    return arr[i+1:arr[i]+i+1]

p = remote("adspam.2021.ctfcompetition.com", 1337)

license = b''
go = '::'.join(licenses[:-1] + [licenses[10]]*100)
for l in go.split("::"):
    license += declicstr(l)

name = a(license, 0)
is_admin = a(license, len(a(license, len(name)+1)) + 1 + len(name) + 1)
print(license[49] + 50, len(license))
print(name, is_admin)

payload = {"name":f"{name.decode()}", "is_admin":0, "device_info":{"os_version":123, "api_level":30, "device":"generic_x86"}, "license":f"{go}"}
# print(payload)
p.sendlineafter(b'\n', encrypt(payload))

encrypted = p.recvline()[:-1]
print(decrypt(encrypted))

p.close()
$ python3 ans.py
[+] Opening connection to adspam.2021.ctfcompetition.com on port 1337: Done
107 448
b'1337_hacker' b'605960596059605960596059605960596059605960596059605960596'
b'{"cmd": 1, "data": "CTF{n0w_u_kn0w_h0w_n0t_t0_l1c3n53_ur_b0t}\\n"}'
[*] Closed connection to adspam.2021.ctfcompetition.com port 1337

'CTF > ㅁㄹ' 카테고리의 다른 글

[GoogleCTF quals 2021] weather  (0) 2022.04.09