NeoQUEST 2022: Задание 7 - (не)Уникальный опыт

NeoQUEST 2022: Задание 7


Выкрасть телефон – было малым делом, а вот взломать его сложнее. Но тут в дело вступает Том, который умеет взламывать такие приложения, словно это орешки!

Тома под рукой у меня не оказалось, поэтому как всегда пришлось делать все самому.

tl;dr

  • Вызвать метод AIDL интерфейса чтобы стригерить получение картинки из нативной библиотеки
  • открыть activity DraftPerArtistActivity_90vbsf45
  • посмотреть имя художника
  • взять от него sha256 и отправить в качестве ключа.

Оригинальный apk можно взять здесь

Как завещали деды - начинать нужно с манифеста. Поэтому набиваем в консоли jadx-gui NBI_ART_DEMO_ARM64.apk и смотрим, что там нафантазировали организаторы:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" android:compileSdkVersion="31" android:compileSdkVersionCodename="12" package="com.newbrainindustrial.galery" platformBuildVersionCode="31" platformBuildVersionName="12">
    <uses-sdk android:minSdkVersion="26" android:targetSdkVersion="31"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <application android:theme="@style/Theme.NBIGalery" android:label="@string/app_name" android:icon="@drawable/square" android:exported="true" android:testOnly="false" android:allowBackup="false" android:supportsRtl="true" android:extractNativeLibs="false" android:roundIcon="@drawable/square" android:appComponentFactory="androidx.core.app.CoreComponentFactory">
        <activity android:name="com.newbrainindustrial.galery.DraftPerArtistActivity_1q1f2fc3" android:exported="true"/>
        <activity android:name="com.newbrainindustrial.galery.DraftPerArtistActivity_90vbsf45" android:exported="true"/>
        <activity android:name="com.newbrainindustrial.galery.DraftPerArtistActivity_ovf8qtu3" android:exported="true"/>
        <activity android:name="com.newbrainindustrial.galery.DraftPerArtistActivity_paul043w" android:exported="true"/>
        <activity android:name="com.newbrainindustrial.galery.DraftPerArtistActivity_jqylfwvw" android:exported="true"/>
        <activity android:name="com.newbrainindustrial.galery.DraftPerArtistActivity_bcwahrju" android:exported="true"/>
        <activity android:name="com.newbrainindustrial.galery.DraftPerArtistActivity_0wa0qkg0" android:exported="true"/>
        <activity android:name="com.newbrainindustrial.galery.DraftPerArtistActivity_49l44gn8" android:exported="true"/>
        <activity android:name="com.newbrainindustrial.galery.DraftPerArtistActivity_g5fl9nmp" android:exported="true"/>
        <activity android:name="com.newbrainindustrial.galery.DraftPerArtistActivity_wh7ylkeu" android:exported="true"/>
        <activity android:name="com.newbrainindustrial.galery.DraftPerArtistActivity_hkxwmoby" android:exported="true"/>
        <activity android:name="com.newbrainindustrial.galery.DraftPerArtistActivity_84nt5kok" android:exported="true"/>
        <activity android:name="com.newbrainindustrial.galery.DraftPerArtistActivity_qnmib5kr" android:exported="true"/>
        <activity android:name="com.newbrainindustrial.galery.DraftPerArtistActivity_31wdeb2a" android:exported="true"/>
        <activity android:name="com.newbrainindustrial.galery.DraftPerArtistActivity_usdgetpi" android:exported="true"/>
        <activity android:name="com.newbrainindustrial.galery.DraftPerArtistActivity_lqbxrhlc" android:exported="true"/>
        <activity android:name="com.newbrainindustrial.galery.DraftPerArtistActivity_y083njcu" android:exported="true"/>
        <activity android:name="com.newbrainindustrial.galery.DraftPerArtistActivity_8xv29el1" android:exported="true"/>
        <activity android:name="com.newbrainindustrial.galery.DraftPerArtistActivity_rasrwsms" android:exported="true"/>
        <activity android:name="com.newbrainindustrial.galery.DraftPerArtistActivity_dtn5b304" android:exported="true"/>
        <activity android:name="com.newbrainindustrial.galery.DraftPerArtistActivity_5bkym4u2" android:exported="true"/>
        <activity android:name="com.newbrainindustrial.galery.DraftPerArtistActivity_67gm6w8s" android:exported="true"/>
        <activity android:theme="@style/Theme.NBIGalery.NoActionBar" android:label="@string/menu_name" android:name="com.newbrainindustrial.galery.ScrollingActivity" android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
        <activity android:label="ZoomedPhoto" android:name="com.newbrainindustrial.galery.FullScreenImage"/>
        <service android:name="com.newbrainindustrial.galery.LogoDecoderAIDL" android:exported="true">
            <intent-filter>
                <action android:name="com.newbrainindustrial.galery.REMOTE_COMMAND"/>
            </intent-filter>
        </service>
    </application>
</manifest>

Куча экспортированных activity с хитрыми именами и непонятным назначением, пара с более-менее понятным назначением и очень интересный экспортированный сервис, который так и говорит нам - напихай в меня чего-нибудь запрещенного. С него и начнем.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
public class LogoDecoderAIDL extends Service {

    public class BinderC0595a extends AbstractBinderC0641a {
        public BinderC0595a() {
        }
    }

    static {
        System.loadLibrary("galery");
    }

    public native byte[] getData();

    @Override // android.app.Service
    public IBinder onBind(Intent intent) {
        return new BinderC0595a();
    }
}

Говорят, что однажды автор таски для android CTF не сделал нативную либу и у него отвалилась Android Studio… Ладно, с библиотекой разберемся потом. Судя по говорящему названию класса - где-то здесь должна быть реализация AIDL интерфейса.

The Android Interface Definition Language (AIDL) is similar to other IDLs you might have worked with. It allows you to define the programming interface that both the client and service agree upon in order to communicate with each other using interprocess communication (IPC). On Android, one process cannot normally access the memory of another process. So to talk, they need to decompose their objects into primitives that the operating system can understand, and marshall the objects across that boundary for you. The code to do that marshalling is tedious to write, so Android handles it for you with AIDL. https://developer.android.com/guide/components/aidl

Лежит она в абстрактном классе AbstractBinderC0641a, от которого наследуется BinderC0595a.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public abstract class AbstractBinderC0641a extends Binder implements IInterface {
    public AbstractBinderC0641a() {
        attachInterface(this, "com.newbrainindustrial.aidl.ILogoDecoderAIDL");
    }

    @Override // android.os.IInterface
    public IBinder asBinder() {
        return this;
    }

    @Override // android.os.Binder
    public boolean onTransact(int code, Parcel parcel, Parcel parcel2, int flags) {
        if (code == 1) {
            parcel.enforceInterface("com.newbrainindustrial.aidl.ILogoDecoderAIDL");
            int readInt = parcel.readInt();
            int readInt2 = parcel.readInt();
            int readInt3 = parcel.readInt();
            Log.i("LogoDecoderAIDL", "com.newbrainindustrial.aidl.ILogoDecoderAIDL.ShowTheLogoOfTruth(args): called by some app!");
            C0642a.f2694a = LogoDecoderAIDL.this.getData();
            parcel2.writeNoException();
            parcel2.writeInt(readInt + readInt2 + readInt3);
            return true;
        } else if (code != Binder.INTERFACE_TRANSACTION) {
            return super.onTransact(code, parcel, parcel2, flags);
        } else {
            parcel2.writeString("com.newbrainindustrial.aidl.ILogoDecoderAIDL");
            return true;
        }
    }
}

Интерес для нас представляют две строчки - 18 и 19. Первая любезно раскрывает нам название метода, который нужно дернуть, а вторая запускает получение данных из нативной библиотеки. Судя по остальному коду, метод можно вызывать с любыми параметрами, т.к. они нигде не используются и ни на что не влияют. Отлично. Записываем этот факт в блокнотик и смотрим, как полученный из метода getData() байтовый массив используется дальше. Для этого ищем все ссылки на поле f2694a.

/img/neoquest2022-writeup-task7/find-usages-menu.png

/img/neoquest2022-writeup-task7/find-usages-list.png

А вот и одна из тех activity с хитрыми именами, которые мы видели в манифесте. Посмотрим, что там есть.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class DraftPerArtistActivity_90vbsf45 extends ActivityC0680e {
    @Override // r0.ActivityC0995f, androidx.activity.ComponentActivity, p025z.ActivityC1237d, android.app.Activity
    public void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        setContentView(R.layout.activity_copyright);
        m1081s();
    }

    @Override // r0.ActivityC0995f, android.app.Activity
    public void onResume() {
        super.onResume();
        m1081s();
    }

    /* renamed from: s */
    public final void m1081s() {
        byte[] bArr = C0642a.f2694a;
        if (bArr != null) {
            try {
                ((ImageView) findViewById(R.id.imageViewLogo)).setImageBitmap(Bitmap.createBitmap(BitmapFactory.decodeByteArray(bArr, 0, bArr.length)));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

Судя по всему байтовый массив содержит какую-то картинку. Поиск ссылок на эту activity ничего не дает, поэтому придется вызывать руками. Итого, на текущем этапе можно составить такой алгоритм атаки на это приложение:

  1. Создать свое приложение с AIDL интерфейсом
  2. Вызвать метод ShowTheLogoOfTruth чтобы библиотека отдалась нам байтовый массив с картинкой
  3. Запустить activity c помощью adb и посмотреть на результат

С приложением все просто. Создаем AIDL интерфейс c именем ILogoDecoderAIDL через меню File->New->AIDL->AIDL File

/img/neoquest2022-writeup-task7/aidl-menu.png

// ILogoDecoderAIDL.aidl
package com.newbrainindustrial.aidl;

interface ILogoDecoderAIDL {
    void ShowTheLogoOfTruth(String args);
}

И пишем обвязки для его вызова:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
var logoDecoder: ILogoDecoderAIDL? = null

val serviceConnection = object : ServiceConnection {
    override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
        logoDecoder = ILogoDecoderAIDL.Stub.asInterface(service)
        logoDecoder?.ShowTheLogoOfTruth("zahek!")
    }
    override fun onServiceDisconnected(name: ComponentName?) {
        logoDecoder = null
    }
}

...

binding.sendIntentButton.setOnClickListener {
    val cmdIntent = Intent("com.newbrainindustrial.galery.REMOTE_COMMAND")
    val services = activity?.packageManager?.queryIntentServices(intent, 0) ?: listOf()
    if (services.isEmpty()) {
        throw IllegalStateException("Not found")
    }
    val payload = Intent(cmdIntent).apply {
        val resolveInfo = services.first()
        val packageName = resolveInfo.serviceInfo.packageName
        val className = resolveInfo.serviceInfo.name
        component = ComponentName(packageName, className)
    }
    requireActivity().bindService(payload, serviceConnection, Context.BIND_AUTO_CREATE)
}

Суть эксплойта в том, чтобы забиндить удаленный сервис с нужной командой, что приведет к срабатыванию метода onServiceConnected, в котором вызывается метод ShowTheLogoOfTruth(). При вызове этого метода, также будет вызван метод onTransact(), который описан выше, и будет заполнен байтовый массив.

Чтобы убедиться в успешности операции, включим сбор логов приложения и запустим код на выполнение.

/img/neoquest2022-writeup-task7/logs.png

Судя по логам - эксплойт сработал. Теперь можно запустить activity чтобы посмотреть изображение:

1
adb shell am start -W -n com.newbrainindustrial.galery/com.newbrainindustrial.galery.DraftPerArtistActivity_90vbsf45

А вот и флаг!

/img/neoquest2022-writeup-task7/flag.png

Нас интересует надпись на картинке: Joey Welch. Для получения ключа нужно взять от этого имени sha256 хэш и соединить его с префиксом NQ2022

$ echo -n "JoeyWelch" | shasum -a 256 | awk '{print "NQ2022" $1}'
NQ20228209e04eb110c19a5752bfe801fac0fd7ffbbcf6d419a60fbfe3ff5e1f9529bb

Бонус №1

Все описанное выше мне показалось явно недостаточным, и ружье в виде нативной библиотеки повешенное в самом начале акта должно же было когда-то выстрелить… Поэтому недолго думая, я загрузил библиотеку в гидру и решил посмотреть, что там еще напрятано.

В секции экспорта сразу была найдена функция Java_com_newbrainindustrial_galery_LogoDecoderAIDL_getData

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
undefined8 * Java_com_newbrainindustrial_galery_LogoDecoderAIDL_getData(long *param_1)

{
  long lVar1;
  char *__ptr;
  undefined8 *puVar2;
  ulong uVar3;
  long lVar4;
  ulong uVar5;
  undefined8 uVar6;
  void *local_88;
  void *local_80;
  void *local_70;
  void *local_68;
  undefined8 *local_58;
  undefined8 *local_50;
  undefined8 *local_48;
  ulong local_40;
  long local_38;
  
  lVar1 = cRead_8(tpidr_el0);
  local_38 = *(long *)(lVar1 + 0x28);
  __ptr = (char *)pathHelperGetPath();
  if (__ptr == (char *)0x0) {
    puVar2 = (undefined8 *)0x0;
  }
  else {
    local_40 = 0;
    puVar2 = (undefined8 *)unzipHelperGetEntryHash(__ptr,&local_40);
    if (puVar2 == (undefined8 *)0x0) {
      free(__ptr);
    }
    else {
      local_50 = (undefined8 *)0x0;
      local_48 = (undefined8 *)0x0;
      local_58 = (undefined8 *)0x0;
      local_58 = (undefined8 *)operator.new(0x10);
      local_50 = local_58 + 2;
      uVar6 = *puVar2;
      local_58[1] = puVar2[1];
      *local_58 = uVar6;
      local_48 = local_50;
      std::__ndk1::vector<unsigned_char,std::__ndk1::allocator<unsigned_char>>::vector
                ((vector<unsigned_char,std::__ndk1::allocator<unsigned_char>> *)&local_70,
                 (vector *)g_dirty);
      uVar3 = is_under_frida();
      if ((uVar3 & 1) == 0) {
        restore_png((vector *)&local_58,(vector *)g_dirty);
        if (local_70 != (void *)0x0) {
          local_68 = local_70;
          operator.delete(local_70);
        }
        local_70 = local_88;
        local_68 = local_80;
      }
      puVar2 = (undefined8 *)(**(code **)(*param_1 + 0x580))(param_1,(int)local_68 - (int)local_70);
      lVar4 = (**(code **)(*param_1 + 0x5c0))(param_1,puVar2,0);
      if (local_68 == local_70) {
        uVar5 = 0;
      }
      else {
        uVar3 = 0;
        do {
          *(undefined *)(lVar4 + uVar3) = *(undefined *)((long)local_70 + uVar3);
          uVar3 = uVar3 + 1;
          uVar5 = (long)local_68 - (long)local_70;
        } while (uVar3 < uVar5);
      }
      (**(code **)(*param_1 + 0x680))(param_1,puVar2,0,uVar5,lVar4);
      (**(code **)(*param_1 + 0x600))(param_1,puVar2,lVar4,0);
      if (local_70 != (void *)0x0) {
        local_68 = local_70;
        operator.delete(local_70);
      }
      if (local_58 != (undefined8 *)0x0) {
        local_50 = local_58;
        operator.delete(local_58);
      }
    }
  }
  if (*(long *)(lVar1 + 0x28) != local_38) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return puVar2;
}

Ну вот! Начинается интересное! Оказывается в приложении была защита от фриды, которую я благополучно не заметил ;) Не будем расстраивать организаторов и героически наступим на эти грабли! Для простоты проведения эксперимента заведем приложение под objection и посмотрим что будет при получении флага.

/img/neoquest2022-writeup-task7/objection.png

/img/neoquest2022-writeup-task7/image-with-frida.png

Так-так-так =) Решили испортить картинку значит. Давайте посмотрим поближе как реализованы проверки на фриду.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
undefined4 is_under_frida(void)

{
  long lVar1;
  bool bVar2;
  ...
  long local_70;
  
  lVar1 = cRead_8(tpidr_el0);
  local_70 = *(long *)(lVar1 + 0x28);
  pDVar4 = opendir("/proc/self/task");
  if (pDVar4 != (DIR *)0x0) {
    pdVar5 = readdir(pDVar4);
    if (pdVar5 == (dirent *)0x0) {
      closedir(pDVar4);
    }
    else {
      bVar2 = false;
      do {
        uStack136 = 0;
        ...
        uStack352 = 0;
        iVar3 = strcmp(pdVar5->d_name,".");
        if ((iVar3 != 0) && (iVar3 = strcmp(pdVar5->d_name,".."), iVar3 != 0)) {
          FUN_00161350(&local_170);
          iVar3 = openat(-100,(char *)&local_170,0x80000,0);
          if (iVar3 != 0) {
            lVar9 = 0;
            lStack616 = 0;
            ...
            uStack384 = 0;
            do {
              sVar6 = read(iVar3,local_2f0,1);
              if ((sVar6 != 1) || (local_2f0[0] == '\n')) break;
              *(char *)((long)&local_270 + lVar9) = local_2f0[0];
              lVar9 = lVar9 + 1;
            } while (lVar9 != 0xff);
            pcVar7 = strstr((char *)&local_270,"gum-js-loop");
            if ((pcVar7 != (char *)0x0) ||
               (pcVar7 = strstr((char *)&local_270,"gmain"), pcVar7 != (char *)0x0)) {
              bVar2 = true;
            }
            close(iVar3);
          }
        }
        pdVar5 = readdir(pDVar4);
      } while (pdVar5 != (dirent *)0x0);
      iVar3 = closedir(pDVar4);
      if (bVar2) {
        uVar8 = 1;
        goto LAB_00161318;
      }
    }
  }
  pDVar4 = opendir("/proc/self/fd");
  if (pDVar4 == (DIR *)0x0) {
    uVar8 = 0;
  }
  else {
    pdVar5 = readdir(pDVar4);
    uVar8 = 0;
    while (pdVar5 != (dirent *)0x0) {
      _Stack648 = 0;
      ...
      uStack608 = 0;
      FUN_00161350(&local_270);
      lstat((char *)&local_270,(stat *)local_2f0);
      if (((uint)local_2e0 & 0xf000) == 0xa000) {
        readlinkat(-100,(char *)&local_270,(char *)&local_170,0x100);
        pcVar7 = strstr((char *)&local_170,"linjector");
        if (pcVar7 != (char *)0x0) {
          uVar8 = 1;
        }
      }
      pdVar5 = readdir(pDVar4);
    }
  }
  iVar3 = closedir(pDVar4);
LAB_00161318:
  if (*(long *)(lVar1 + 0x28) == local_70) {
    return uVar8;
  }
                    /* WARNING: Subroutine does not return */
  __stack_chk_fail(iVar3);
}

Нуууууу… ничего такого, с чем бы мы не встречались ранее. Тут должен сработать самый простой и топорный хак с подменой возвращаемого значения функции strstr

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
Interceptor.attach(Module.findExportByName("libc.so", "strstr"), {
    onEnter: function(args) {

        this.haystack = Memory.readUtf8String(args[0])
        this.isFrida = Boolean(0)

        if (this.haystack.indexOf("gmain") !== -1 || this.haystack.indexOf("gum-js-loop") !== -1 || this.haystack.indexOf("linjector") !== -1) {
            this.frida = Boolean(1);
        }
    },

    onLeave: function(retval) {

        if (this.frida) {
            retval.replace(0);
        }
        return retval;
    }
});

Запускаем приложение с этим скриптом и убеждаемся, что детектор фриды пал смертью храбрых, а при запуске activity отображается актуальная картинка.

Бонус №2

Стоит сказать, что изначальная формулировка задания была довольно неоднозначной и догадаться о том, что от текста на этой картинке нужно было взять хэш, да еще и в определенном формате написания - было решительно невозможно. После добавления уточнения от организаторов, все встало на свои места:

ПРИМЕЧАНИЕ: некая компания NewBrainIndustrial-E выпустила приложение, представляющее собой галерею цифровых произведений искусства, которые сгенерировал разработанный в этой компании искусственный интеллект. Приложение используется для рекламы компании и содержит также коммерческие предложения по приобретению подписки на ИИ для решения собственных задач. Есть подозрение, что у NewBrainIndustrial-E нет никакой ИИ-разработки, а картины принадлежат настоящему художнику. Необходимо найти доказательства и сообщить имя художника. Ключ – это NQ2022+SHA256(ИмяФамилия)

Спасибо ребята =) Без этого примечания вы меня заставили вспомнить еще и стеганографию. Но обовсем по порядку.

Получив картинку на экране я предположил, что вся самая ценная информация зашита в самой картинке. Нужно было как-то ее извлечь и препарировать. Собственно в этот момент и возникла необходимость сделать обход фриды, о котором я писал выше. Для того чтобы достать картинку нам нужно перехватить вызов функции Java_com_newbrainindustrial_galery_LogoDecoderAIDL_getData и сохранить полученный массив как картинку. Для этого напишем немного замороченный скрипт на python:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
import frida, sys

def on_message(message, data):
    if message['type'] == 'send':
        print("[*] {0}".format(message['payload']))
    else:
        print(message)

jscode = """
'use strict';
let imageBuffer = new Uint8Array(1024);
let wastetime = true

rpc.exports = {
    getimagebuffer: function() {
        return imageBuffer;
    },
    wastingtime: function() {
        return wastetime;
    }
}

function bytes2hex(array) {
    var result = '';
    for(var i = 0; i < array.length; ++i)
        result += ('0' + (array[i] & 0xFF).toString(16)).slice(-2);
    return result;
}

Interceptor.attach(Module.findExportByName("libc.so", "strstr"), {
    onEnter: function(args) {

        this.haystack = Memory.readUtf8String(args[0])
        this.isFrida = Boolean(0)

        if (this.haystack.indexOf("gmain") !== -1 || this.haystack.indexOf("gum-js-loop") !== -1 || this.haystack.indexOf("linjector") !== -1) {
            this.frida = Boolean(1);
        }
    },

    onLeave: function(retval) {

        if (this.frida) {
            retval.replace(0);
        }
        return retval;
    }
});

Interceptor.attach(Module.findExportByName("libgalery.so", "Java_com_newbrainindustrial_galery_LogoDecoderAIDL_getData"), {
    onEnter: function(args) {

    },

    onLeave: function(retval) {
        var b = Java.use('[B')
        var buffer = Java.cast(retval, b);
        var result = Java.array('byte', buffer);
        
        imageBuffer = bytes2hex(result);
        wastetime = false
        
        return retval;
    }
});
"""

process = frida.get_usb_device().attach('NBI-E Gallery')
script = process.create_script(jscode)
script.on('message', on_message)
print('[*] Wasting time...')
script.load()

jsexports = script.exports

while (jsexports.wastingtime()):
    pass

buff = jsexports.getimagebuffer()

with open("result.png", "wb") as f:
    f.write(bytearray.fromhex(buff))

print("Complete") 

Чтобы вся эта добрая магия сработала, нам нужно убедиться, что весь нужный нам код уже находится в памяти. Для этого нужно один раз вызвать метод AIDL интерфейса чтобы “прогреть” всю машинерию. Потом не закрывая приложение запускаем этот скрипт и тригерим AIDL еще раз. В результате получим желанную картинку:

/img/neoquest2022-writeup-task7/result.png

А потом я творил с этой картинкой такое, что я б не позволил печатать на месте цензуры… Естественно все было бесполезно :D Но один приемчик на будущее я вам все же покажу. В реальной жизни пригодится вряд-ли, а вот на CTF-ах вполне. Называется эта штука Steganography Toolkit и работать с ней нужно так:

$ docker run -it --rm -v $(pwd):/data dominicbreuker/stego-toolkit /bin/bash
root@c03002797574:/data# check_png.sh result.png

#################################
########## PNG CHECKER ##########
#################################
Checking file result.png
...

Утилита проверит картинку кучей инструментов и покажет довольно подробный отчет. Как правило этого хватает для базовых CTF задач.

На этом все. Удачи в киберсражениях! 😎


Смотрите также

comments powered by Disqus