2022. 4. 23. 18:28ㆍCTF/ㅁㄹ
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());
대충 이런식
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 |
---|