mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
Translated ['src/mobile-pentesting/android-app-pentesting/android-task-h
This commit is contained in:
parent
d9442aa5a3
commit
bfb200b686
@ -4,7 +4,7 @@
|
||||
|
||||
## Görev, Geri Yığın ve Ön Plan Aktiviteleri
|
||||
|
||||
Android'de, bir **görev**, kullanıcıların belirli bir işi tamamlamak için etkileşimde bulunduğu aktivitelerin bir setidir ve **geri yığın** içinde organize edilir. Bu yığın, aktiviteleri açıldıkları zamana göre sıralar; en son açılan aktivite en üstte **ön plan aktivitesi** olarak görüntülenir. Her an, yalnızca bu aktivite ekranda görünür, bu da onu **ön plan görevi**nin bir parçası yapar.
|
||||
Android'de, bir **görev**, kullanıcıların belirli bir işi tamamlamak için etkileşimde bulunduğu aktivitelerin bir setidir ve bir **geri yığın** içinde organize edilir. Bu yığın, aktiviteleri açıldıkları zamana göre sıralar; en son açılan aktivite en üstte **ön plan aktivitesi** olarak görüntülenir. Her an, yalnızca bu aktivite ekranda görünür, bu da onu **ön plan görevi**nin bir parçası yapar.
|
||||
|
||||
Aktivite geçişlerinin hızlı bir özeti:
|
||||
|
||||
@ -15,29 +15,35 @@ Aktivite geçişlerinin hızlı bir özeti:
|
||||
|
||||
.png>)
|
||||
|
||||
## Görev Affinitesi Saldırısı
|
||||
## Görev bağlılığı saldırısı
|
||||
|
||||
### Görev Affinitesi ve Başlatma Modları Genel Bakış
|
||||
### Görev Bağlılığı ve Başlatma Modları Genel Bakış
|
||||
|
||||
Android uygulamalarında, **görev affinitesi**, bir aktivitenin tercih edilen görevini belirtir ve genellikle uygulamanın paket adıyla uyumludur. Bu yapı, saldırıyı göstermek için bir kanıt konsepti (PoC) uygulaması oluşturmakta önemlidir.
|
||||
Android uygulamalarında, **görev bağlılığı**, bir aktivitenin tercih edilen görevini belirtir ve genellikle uygulamanın paket adıyla uyumludur. Bu yapı, saldırıyı göstermek için bir kanıt konsepti (PoC) uygulaması oluşturmakta önemlidir.
|
||||
|
||||
### Başlatma Modları
|
||||
|
||||
`launchMode` niteliği, görevler içindeki aktivite örneklerinin nasıl işleneceğini yönlendirir. **singleTask** modu, mevcut aktivite örnekleri ve görev affinitesi eşleşmelerine dayalı üç senaryo belirleyerek bu saldırı için kritik öneme sahiptir. Saldırı, bir saldırganın uygulamasının hedef uygulamanın görev affinitesini taklit etme yeteneğine dayanır ve Android sistemini saldırganın uygulamasını başlatmaya yönlendirir.
|
||||
`launchMode` niteliği, görevler içindeki aktivite örneklerinin nasıl işleneceğini yönlendirir. **singleTask** modu, mevcut aktivite örnekleri ve görev bağlılığı eşleşmelerine dayanan üç senaryo belirleyerek bu saldırı için kritik öneme sahiptir. Saldırı, bir saldırganın uygulamasının hedef uygulamanın görev bağlılığını taklit etme yeteneğine dayanır ve Android sistemini saldırganın uygulamasını başlatmaya yönlendirir.
|
||||
|
||||
### Ayrıntılı Saldırı Adımları
|
||||
|
||||
1. **Kötü Amaçlı Uygulama Kurulumu**: Kurban, saldırganın uygulamasını cihazına kurar.
|
||||
2. **İlk Aktivasyon**: Kurban, önce kötü amaçlı uygulamayı açarak cihazı saldırıya hazırlar.
|
||||
3. **Hedef Uygulama Başlatma Girişimi**: Kurban, hedef uygulamayı açmaya çalışır.
|
||||
4. **Ele Geçirme Uygulaması**: Eşleşen görev affinitesi nedeniyle, kötü amaçlı uygulama hedef uygulamanın yerine başlatılır.
|
||||
4. **Ele Geçirme Uygulaması**: Bir noktada uygulama **singleTask** görünümünü açmaya çalışır. Eşleşen görev bağlılığı nedeniyle, kötü amaçlı uygulama hedef uygulamanın yerine başlatılır.
|
||||
5. **Aldatma**: Kötü amaçlı uygulama, hedef uygulamaya benzeyen sahte bir giriş ekranı sunarak kullanıcıyı hassas bilgileri girmeye kandırır.
|
||||
|
||||
Bu saldırının pratik bir uygulaması için GitHub'daki Görev Ele Geçirme Strandhogg deposuna bakın: [Task Hijacking Strandhogg](https://github.com/az0mb13/Task_Hijacking_Strandhogg).
|
||||
> [!TIP]
|
||||
> Bu saldırının çalışması için, savunmasız görünümün **true olarak dışa aktarılmış olması gerekmez** ve ana aktivite olması da gerekmez.
|
||||
|
||||
Bu saldırının pratik bir uygulaması için GitHub'daki Görev Ele Geçirme Strandhogg deposuna bakın: [Görev Ele Geçirme Strandhogg](https://github.com/az0mb13/Task_Hijacking_Strandhogg).
|
||||
|
||||
### Önleme Önlemleri
|
||||
|
||||
Bu tür saldırıları önlemek için geliştiriciler, `taskAffinity`'yi boş bir dize olarak ayarlayabilir ve uygulamalarının diğerlerinden izole olmasını sağlamak için `singleInstance` başlatma modunu tercih edebilir. `onBackPressed()` fonksiyonunu özelleştirmek, görev ele geçirmeye karşı ek koruma sağlar.
|
||||
Bu tür saldırıları önlemek için geliştiriciler:
|
||||
- **`**taskAffinity`**'yi **singleTask** görünümü için boş bir dize olarak ayarlayabilir (`android:taskAffinity=""`)
|
||||
- Uygulamalarının diğerlerinden izole olmasını sağlamak için **`singleInstance`** başlatma modunu tercih edebilir.
|
||||
- Görev ele geçirmeye karşı ek koruma sağlayan **`onBackPressed()`** fonksiyonunu özelleştirebilir.
|
||||
|
||||
## **Kaynaklar**
|
||||
|
||||
|
@ -10,19 +10,27 @@ Uygulamanın React Native framework'ü üzerinde inşa edilip edilmediğini doğ
|
||||
|
||||
3. JavaScript dosyasını aramak için `find . -print | grep -i ".bundle$"` komutunu kullanın.
|
||||
|
||||
JavaScript kodunu daha fazla analiz etmek için, aynı dizinde `index.html` adında bir dosya oluşturun ve aşağıdaki kodu ekleyin:
|
||||
## Javascript Kodu
|
||||
|
||||
Eğer `index.android.bundle` dosyasının içeriğini kontrol ettiğinizde uygulamanın JavaScript kodunu (minify edilmiş olsa bile) bulursanız, **hassas bilgileri ve güvenlik açıklarını bulmak için analiz edebilirsiniz**.
|
||||
|
||||
Bundle, aslında uygulamanın tüm JS kodunu içerdiğinden, **onu farklı dosyalara ayırmak mümkündür** (tersine mühendisliğini kolaylaştırabilir) **[react-native-decompiler](https://github.com/numandev1/react-native-decompiler)** aracını kullanarak.
|
||||
|
||||
### Webpack
|
||||
|
||||
JavaScript kodunu daha fazla analiz etmek için dosyayı [https://spaceraccoon.github.io/webpack-exploder/](https://spaceraccoon.github.io/webpack-exploder/) adresine yükleyebilir veya şu adımları izleyebilirsiniz:
|
||||
|
||||
1. Aynı dizinde `index.html` adında bir dosya oluşturun ve aşağıdaki kodu ekleyin:
|
||||
```html
|
||||
<script src="./index.android.bundle"></script>
|
||||
```
|
||||
Dosyayı [https://spaceraccoon.github.io/webpack-exploder/](https://spaceraccoon.github.io/webpack-exploder/) adresine yükleyebilir veya şu adımları izleyebilirsiniz:
|
||||
2. `index.html` dosyasını Google Chrome'da açın.
|
||||
|
||||
1. `index.html` dosyasını Google Chrome'da açın.
|
||||
3. **OS X için Command+Option+J** veya **Windows için Control+Shift+J** tuşlarına basarak Geliştirici Araç Çubuğunu açın.
|
||||
|
||||
2. **OS X için Command+Option+J** veya **Windows için Control+Shift+J** tuşlarına basarak Geliştirici Araçları'nı açın.
|
||||
4. Geliştirici Araç Çubuğunda "Sources" sekmesine tıklayın. Ana paketi oluşturan klasörler ve dosyalar halinde bölünmüş bir JavaScript dosyası görmelisiniz.
|
||||
|
||||
3. Geliştirici Araçları'nda "Sources" sekmesine tıklayın. Ana paketi oluşturan klasörler ve dosyalar halinde bölünmüş bir JavaScript dosyası görmelisiniz.
|
||||
|
||||
Eğer `index.android.bundle.map` adında bir dosya bulursanız, kaynak kodunu minify edilmemiş formatta analiz edebilirsiniz. Harita dosyaları, minify edilmiş tanımlayıcıları eşleştirmenizi sağlayan kaynak eşleştirmesi içerir.
|
||||
Eğer `index.android.bundle.map` adında bir dosya bulursanız, kaynak kodunu unminified formatında analiz edebilirsiniz. Harita dosyaları, minified tanımlayıcıları eşleştirmenizi sağlayan kaynak eşleştirmesi içerir.
|
||||
|
||||
Hassas kimlik bilgileri ve uç noktaları aramak için şu adımları izleyin:
|
||||
|
||||
@ -30,10 +38,44 @@ Hassas kimlik bilgileri ve uç noktaları aramak için şu adımları izleyin:
|
||||
|
||||
2. Bu özel durumda, uygulamanın Dialogflow hizmetini kullandığı gözlemlendi. Yapılandırmasıyla ilgili bir desen arayın.
|
||||
|
||||
3. Recon süreci sırasında JavaScript kodunda hassas sabit kodlanmış kimlik bilgileri bulunduğu için şanslıydık.
|
||||
3. Recon süreci sırasında JavaScript kodunda hassas hard-coded kimlik bilgileri bulunduğu için şanslıydınız.
|
||||
|
||||
## References
|
||||
### JS kodunu değiştirin ve yeniden oluşturun
|
||||
|
||||
Bu durumda kodu değiştirmek kolaydır. Uygulamayı `.zip` uzantısıyla yeniden adlandırmanız ve çıkarmanız yeterlidir. Ardından, **bu paketin içindeki JS kodunu değiştirip uygulamayı yeniden oluşturabilirsiniz**. Bu, test amaçları için uygulamaya **kod enjekte etmenizi** sağlamak için yeterli olmalıdır.
|
||||
|
||||
## Hermes bytecode
|
||||
|
||||
Eğer paket **Hermes bytecode** içeriyorsa, uygulamanın Javascript koduna **erişemezsiniz** (minified versiyonuna bile).
|
||||
|
||||
Paketin Hermes bytecode içerip içermediğini kontrol etmek için aşağıdaki komutu çalıştırabilirsiniz:
|
||||
```bash
|
||||
file index.android.bundle
|
||||
index.android.bundle: Hermes JavaScript bytecode, version 96
|
||||
```
|
||||
Ancak, **[hbctool](https://github.com/bongtrop/hbctool)**, **[hermes-dec](https://github.com/P1sec/hermes-dec)** veya **[hermes_rs](https://github.com/Pilfer/hermes_rs)** araçlarını kullanarak **bytecode'u ayrıştırabilir** ve ayrıca **bunu bazı pseudo JS kodlarına decompile edebilirsiniz**. Bunu yapmak için, örneğin bu komutlar:
|
||||
```bash
|
||||
hbc-disassembler ./index.android.bundle /tmp/my_output_file.hasm
|
||||
hbc-decompiler ./index.android.bundle /tmp/my_output_file.js
|
||||
```
|
||||
### Kod Değiştirme ve Yeniden Derleme
|
||||
|
||||
İdeal olarak, ayrıştırılmış kodu (bir karşılaştırmayı, bir değeri veya değiştirmeniz gereken her şeyi değiştirmek) değiştirebilmelisiniz ve ardından **bytecode'u yeniden derlemeli** ve uygulamayı yeniden inşa etmelisiniz.
|
||||
|
||||
Araç **[hbctool](https://github.com/bongtrop/hbctool)**, paketi ayrıştırmayı ve değişiklikler yapıldıktan sonra geri inşa etmeyi destekler, ancak **sadece eski sürümleri** destekler. Hermes bytecode'un.
|
||||
|
||||
Araç **[hermes-dec](https://github.com/P1sec/hermes-dec)** bytecode'u yeniden derlemeyi desteklemez.
|
||||
|
||||
Araç **[hermes_rs](https://github.com/Pilfer/hermes_rs)** bytecode'u yeniden derlemeyi destekler, ancak aslında bir kütüphanedir ve bir CLI aracı değildir.
|
||||
|
||||
## Dinamik Analiz
|
||||
|
||||
Uygulamayı dinamik olarak analiz etmeye çalışmanın bir yolu, Frida'yı kullanarak React uygulamasının geliştirici modunu etkinleştirmek ve **`react-native-debugger`** ile ona bağlanmaktır. Ancak, bunun için uygulamanın kaynak koduna ihtiyacınız var gibi görünüyor. Bununla ilgili daha fazla bilgi bulabilirsiniz [https://newsroom.bedefended.com/hooking-react-native-applications-with-frida/](https://newsroom.bedefended.com/hooking-react-native-applications-with-frida/).
|
||||
|
||||
## Referanslar
|
||||
|
||||
- [https://medium.com/bugbountywriteup/lets-know-how-i-have-explored-the-buried-secrets-in-react-native-application-6236728198f7](https://medium.com/bugbountywriteup/lets-know-how-i-have-explored-the-buried-secrets-in-react-native-application-6236728198f7)
|
||||
- [https://www.assetnote.io/resources/research/expanding-the-attack-surface-react-native-android-applications](https://www.assetnote.io/resources/research/expanding-the-attack-surface-react-native-android-applications)
|
||||
- [https://payatu.com/wp-content/uploads/2023/02/Mastering-React-Native-Application-Pentesting-A-Practical-Guide-2.pdf](https://payatu.com/wp-content/uploads/2023/02/Mastering-React-Native-Application-Pentesting-A-Practical-Guide-2.pdf)
|
||||
|
||||
{{#include ../../banners/hacktricks-training.md}}
|
||||
|
@ -4,22 +4,24 @@
|
||||
|
||||
## **Temel Bilgiler**
|
||||
|
||||
**Tapjacking**, **kötü niyetli** bir **uygulamanın** başlatıldığı ve **bir kurban uygulamanın üzerine yerleştiği** bir saldırıdır. Kurban uygulamayı görünür şekilde gizlediğinde, kullanıcı arayüzü kullanıcıyı onunla etkileşime girmeye kandıracak şekilde tasarlanmıştır, bu arada etkileşimi kurban uygulamaya iletmektedir.\
|
||||
**Tapjacking**, **kötü niyetli** bir **uygulamanın** başlatıldığı ve **bir kurban uygulamanın üzerine yerleştirildiği** bir saldırıdır. Kurban uygulamayı görünür şekilde gizlediğinde, kullanıcı arayüzü, kullanıcının onunla etkileşimde bulunmasını sağlamak için tasarlanmıştır, bu arada etkileşimi kurban uygulamaya iletmektedir.\
|
||||
Sonuç olarak, bu durum **kullanıcının aslında kurban uygulamasında eylem gerçekleştirdiğini bilmesini engellemektedir**.
|
||||
|
||||
### Tespit
|
||||
|
||||
Bu saldırıya karşı savunmasız uygulamaları tespit etmek için, android manifestinde **dışa aktarılan aktiviteleri** aramalısınız (bir intent-filter ile bir aktivitenin varsayılan olarak otomatik olarak dışa aktarıldığını unutmayın). Dışa aktarılan aktiviteleri bulduktan sonra, **herhangi bir izin gerektirip gerektirmediğini kontrol edin**. Bunun nedeni, **kötü niyetli uygulamanın da o izne ihtiyaç duymasıdır**.
|
||||
Bu saldırıya karşı savunmasız uygulamaları tespit etmek için, android manifestinde **dışa aktarılan aktiviteleri** aramalısınız (bir intent-filter ile bir aktivitenin varsayılan olarak otomatik olarak dışa aktarıldığını unutmayın). Dışa aktarılan aktiviteleri bulduktan sonra, **herhangi bir izin gerektirip gerektirmediğini kontrol edin**. Bunun nedeni, **kötü niyetli uygulamanın da bu izne ihtiyaç duymasıdır**.
|
||||
|
||||
Ayrıca uygulamanın minimum SDK sürümünü kontrol edebilirsiniz, **`AndroidManifest.xml`** dosyasında **`android:minSdkVersion`** değerine bakarak. Değer **30'dan düşükse**, uygulama Tapjacking'e karşı savunmasızdır.
|
||||
|
||||
### Koruma
|
||||
|
||||
#### Android 12 (API 31,32) ve üzeri
|
||||
|
||||
[**Bu kaynağa göre**](https://www.geeksforgeeks.org/tapjacking-in-android/)**,** tapjacking saldırıları Android 12 (API 31 & 30) ve üzeri sürümlerde Android tarafından otomatik olarak engellenmektedir. Bu nedenle, uygulama savunmasız olsa bile **onu istismar edemezsiniz**.
|
||||
[**Bu kaynağa göre**](https://www.geeksforgeeks.org/tapjacking-in-android/)**,** tapjacking saldırıları Android 12 (API 31 & 30) ve üzeri sürümlerde otomatik olarak engellenmektedir. Yani, uygulama savunmasız olsa bile **onu istismar edemezsiniz**.
|
||||
|
||||
#### `filterTouchesWhenObscured`
|
||||
|
||||
Eğer **`android:filterTouchesWhenObscured`** **`true`** olarak ayarlandıysa, `View`, görünür bir başka pencere tarafından gizlendiğinde dokunuşları almayacaktır.
|
||||
Eğer **`android:filterTouchesWhenObscured`** **`true`** olarak ayarlandıysa, `View`, görünür bir pencere tarafından gizlendiğinde dokunuşları almayacaktır.
|
||||
|
||||
#### **`setFilterTouchesWhenObscured`**
|
||||
|
||||
@ -33,28 +35,28 @@ android:layout_height="wrap_content"
|
||||
android:filterTouchesWhenObscured="true">
|
||||
</Button>
|
||||
```
|
||||
## Sömürü
|
||||
## Exploitation
|
||||
|
||||
### Tapjacking-ExportedActivity
|
||||
|
||||
En **son Android uygulaması** olan Tapjacking saldırısını gerçekleştiren (ve saldırıya uğrayan uygulamanın dışa aktarılan bir aktivitesinden önce çağrılan) uygulama şu adreste bulunabilir: [**https://github.com/carlospolop/Tapjacking-ExportedActivity**](https://github.com/carlospolop/Tapjacking-ExportedActivity).
|
||||
En son **Android uygulaması** olan Tapjacking saldırısını gerçekleştiren (+ saldırıya uğrayan uygulamanın dışa aktarılan bir aktivitesinden önce çağırma) uygulama şu adreste bulunabilir: [**https://github.com/carlospolop/Tapjacking-ExportedActivity**](https://github.com/carlospolop/Tapjacking-ExportedActivity).
|
||||
|
||||
**Kullanmak için README talimatlarını izleyin**.
|
||||
**Kullanım için README talimatlarını** takip edin.
|
||||
|
||||
### FloatingWindowApp
|
||||
|
||||
**FloatingWindowApp**'i uygulayan bir örnek proje, tıklama saldırısı gerçekleştirmek için diğer aktivitelerin üzerine yerleştirilebilecek bir proje olarak [**FloatingWindowApp**](https://github.com/aminography/FloatingWindowApp) adresinde bulunabilir (biraz eski, apk oluşturma konusunda iyi şanslar).
|
||||
Diğer aktivitelerin üzerine yerleştirilerek bir clickjacking saldırısı gerçekleştirmek için kullanılabilecek **FloatingWindowApp**'i uygulayan bir örnek proje [**FloatingWindowApp**](https://github.com/aminography/FloatingWindowApp) adresinde bulunabilir (biraz eski, apk oluşturma konusunda iyi şanslar).
|
||||
|
||||
### Qark
|
||||
|
||||
> [!DİKKAT]
|
||||
> [!CAUTION]
|
||||
> Bu projenin artık bakımsız olduğu ve bu işlevselliğin düzgün çalışmadığı görünmektedir.
|
||||
|
||||
Olası **Tapjacking** zafiyetlerini test etmek için kötü niyetli bir uygulama oluşturmak üzere [**qark**](https://github.com/linkedin/qark) kullanabilirsiniz; `--exploit-apk` --sdk-path `/Users/username/Library/Android/sdk` parametreleri ile.
|
||||
Olası **Tapjacking** zafiyetlerini test etmek için `--exploit-apk` --sdk-path `/Users/username/Library/Android/sdk` parametreleri ile [**qark**](https://github.com/linkedin/qark) kullanabilirsiniz.\
|
||||
|
||||
Zafiyetin giderilmesi oldukça basittir, çünkü geliştirici bir görünüm başka bir görünüm tarafından kaplandığında dokunma olaylarını almamayı seçebilir. [Android Geliştirici Referansı](https://developer.android.com/reference/android/view/View#security) kullanarak:
|
||||
|
||||
> Bazen bir uygulamanın, bir izin talebini onaylama, bir satın alma yapma veya bir reklama tıklama gibi bir eylemin kullanıcının tam bilgisi ve rızasıyla gerçekleştirildiğini doğrulaması önemlidir. Ne yazık ki, kötü niyetli bir uygulama, görünümün niyet edilen amacını gizleyerek kullanıcının bu eylemleri bilmeden gerçekleştirmesini sağlamak için kullanıcıyı kandırmaya çalışabilir. Bir çözüm olarak, çerçeve, hassas işlevselliğe erişim sağlayan görünümlerin güvenliğini artırmak için kullanılabilecek bir dokunma filtreleme mekanizması sunar.
|
||||
> Bazen bir uygulamanın, bir izin isteğini onaylama, bir satın alma yapma veya bir reklama tıklama gibi bir eylemin kullanıcının tam bilgisi ve rızasıyla gerçekleştirildiğini doğrulaması önemlidir. Ne yazık ki, kötü niyetli bir uygulama, görünümün niyet edilen amacını gizleyerek kullanıcının bu eylemleri bilmeden gerçekleştirmesini sağlamaya çalışabilir. Bir çözüm olarak, çerçeve, hassas işlevselliğe erişim sağlayan görünümlerin güvenliğini artırmak için kullanılabilecek bir dokunma filtreleme mekanizması sunar.
|
||||
>
|
||||
> Dokunma filtrelemeyi etkinleştirmek için [`setFilterTouchesWhenObscured(boolean)`](https://developer.android.com/reference/android/view/View#setFilterTouchesWhenObscured%28boolean%29) çağrısını yapın veya android:filterTouchesWhenObscured düzenleme özniteliğini true olarak ayarlayın. Etkinleştirildiğinde, çerçeve, görünümün penceresi başka bir görünür pencere tarafından kaplandığında alınan dokunmaları yok sayacaktır. Sonuç olarak, bir toast, diyalog veya başka bir pencere görünümün penceresinin üzerinde belirdiğinde görünüm dokunmaları almayacaktır.
|
||||
|
||||
|
@ -471,12 +471,13 @@ window.search = window.search || {};
|
||||
showResults(true);
|
||||
}
|
||||
|
||||
fetch(path_to_root + 'searchindex.json')
|
||||
var branch = lang === "en" ? "master" : lang
|
||||
fetch(`https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/refs/heads/${branch}/searchindex.json`)
|
||||
.then(response => response.json())
|
||||
.then(json => init(json))
|
||||
.catch(error => { // Try to load searchindex.js if fetch failed
|
||||
var script = document.createElement('script');
|
||||
script.src = path_to_root + 'searchindex.js';
|
||||
script.src = `https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/refs/heads/${branch}/searchindex.js`;
|
||||
script.onload = () => init(window.search);
|
||||
document.head.appendChild(script);
|
||||
});
|
||||
|
@ -55,6 +55,7 @@
|
||||
<!-- Provide site root to javascript -->
|
||||
<script>
|
||||
var path_to_root = "{{ path_to_root }}";
|
||||
var lang = "{{ language }}";
|
||||
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "{{ preferred_dark_theme }}" : "{{ default_theme }}";
|
||||
</script>
|
||||
<!-- Start loading toc.js asap -->
|
||||
|
Loading…
x
Reference in New Issue
Block a user