mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
Translated ['src/generic-methodologies-and-resources/phishing-methodolog
This commit is contained in:
parent
f090ef106c
commit
f022071dc2
1
.gitignore
vendored
1
.gitignore
vendored
@ -11,3 +11,4 @@ book
|
||||
book/*
|
||||
hacktricks-preprocessor.log
|
||||
hacktricks-preprocessor-error.log
|
||||
searchindex.js
|
||||
|
@ -3,95 +3,109 @@
|
||||
{{#include ../../banners/hacktricks-training.md}}
|
||||
|
||||
|
||||
## iOS Exploit Mitigations
|
||||
|
||||
- **Code Signing** in iOS works by requiring every piece of executable code (apps, libraries, extensions, etc.) to be cryptographically signed with a certificate issued by Apple. When code is loaded, iOS verifies the digital signature against Apple’s trusted root. If the signature is invalid, missing, or modified, the OS refuses to run it. This prevents attackers from injecting malicious code into legitimate apps or running unsigned binaries, effectively stopping most exploit chains that rely on executing arbitrary or tampered code.
|
||||
- **CoreTrust** is the iOS subsystem responsible for enforcing code signing at runtime. It directly verifies signatures using Apple’s root certificate without relying on cached trust stores, meaning only binaries signed by Apple (or with valid entitlements) can execute. CoreTrust ensures that even if an attacker tampers with an app after installation, modifies system libraries, or tries to load unsigned code, the system will block execution unless the code is still properly signed. This strict enforcement closes many post-exploitation vectors that older iOS versions allowed through weaker or bypassable signature checks.
|
||||
- **Data Execution Prevention (DEP)** marks memory regions as non-executable unless they explicitly contain code. This stops attackers from injecting shellcode into data regions (like the stack or heap) and running it, forcing them to rely on more complex techniques like ROP (Return-Oriented Programming).
|
||||
- **ASLR (Address Space Layout Randomization)** randomizes the memory addresses of code, libraries, stack, and heap every time the system runs. This makes it much harder for attackers to predict where useful instructions or gadgets are, breaking many exploit chains that depend on fixed memory layouts.
|
||||
- **KASLR (Kernel ASLR)** applies the same randomization concept to the iOS kernel. By shuffling the kernel’s base address at each boot, it prevents attackers from reliably locating kernel functions or structures, raising the difficulty of kernel-level exploits that would otherwise gain full system control.
|
||||
- **Kernel Patch Protection (KPP)** also known as **AMCC (Apple Mobile File Integrity)** in iOS, continuously monitors the kernel’s code pages to ensure they haven’t been modified. If any tampering is detected—such as an exploit trying to patch kernel functions or insert malicious code—the device will immediately panic and reboot. This protection makes persistent kernel exploits far harder, as attackers can’t simply hook or patch kernel instructions without triggering a system crash.
|
||||
- **Kernel Text Readonly Region (KTRR)** is a hardware-based security feature introduced on iOS devices. It uses the CPU’s memory controller to mark the kernel’s code (text) section as permanently read-only after boot. Once locked, even the kernel itself cannot modify this memory region. This prevents attackers—and even privileged code—from patching kernel instructions at runtime, closing off a major class of exploits that relied on modifying kernel code directly.
|
||||
- **Pointer Authentication Codes (PAC)** use cryptographic signatures embedded into unused bits of pointers to verify their integrity before use. When a pointer (like a return address or function pointer) is created, the CPU signs it with a secret key; before dereferencing, the CPU checks the signature. If the pointer was tampered with, the check fails and execution stops. This prevents attackers from forging or reusing corrupted pointers in memory corruption exploits, making techniques like ROP or JOP much harder to pull off reliably.
|
||||
- **Privilege Access never (PAN)** is a hardware feature that prevents the kernel (privileged mode) from directly accessing user-space memory unless it explicitly enables access. This stops attackers who gained kernel code execution from easily reading or writing user memory to escalate exploits or steal sensitive data. By enforcing strict separation, PAN reduces the impact of kernel exploits and blocks many common privilege-escalation techniques.
|
||||
- **Page Protection Layer (PPL)** is an iOS security mechanism that protects critical kernel-managed memory regions, especially those related to code signing and entitlements. It enforces strict write protections using the MMU (Memory Management Unit) and additional checks, ensuring that even privileged kernel code cannot arbitrarily modify sensitive pages. This prevents attackers who gain kernel-level execution from tampering with security-critical structures, making persistence and code-signing bypasses significantly harder.
|
||||
|
||||
|
||||
## Physical use-after-free
|
||||
|
||||
This is a summary from the post from [https://alfiecg.uk/2024/09/24/Kernel-exploit.html](https://alfiecg.uk/2024/09/24/Kernel-exploit.html) moreover further information about exploit using this technique can be found in [https://github.com/felix-pb/kfd](https://github.com/felix-pb/kfd)
|
||||
|
||||
### Memory management in XNU <a href="#memory-management-in-xnu" id="memory-management-in-xnu"></a>
|
||||
|
||||
iOS의 사용자 프로세스용 **virtual memory address space**는 **0x0 to 0x8000000000** 범위를 가집니다. 하지만 이 주소들은 물리 메모리에 직접 대응하지 않습니다. 대신 **kernel**은 **page tables**를 사용해 가상 주소를 실제 **physical addresses**로 변환합니다.
|
||||
The **virtual memory address space** for user processes on iOS spans from **0x0 to 0x8000000000**. However, these addresses don’t directly map to physical memory. Instead, the **kernel** uses **page tables** to translate virtual addresses into actual **physical addresses**.
|
||||
|
||||
#### Levels of Page Tables in iOS
|
||||
|
||||
페이지 테이블은 계층적으로 세 레벨로 구성됩니다:
|
||||
Page tables are organized hierarchically in three levels:
|
||||
|
||||
1. **L1 Page Table (Level 1)**:
|
||||
* 여기의 각 엔트리는 넓은 범위의 가상 메모리를 나타냅니다.
|
||||
* **0x1000000000 bytes** (또는 **256 GB**)의 가상 메모리를 커버합니다.
|
||||
* Each entry here represents a large range of virtual memory.
|
||||
* It covers **0x1000000000 bytes** (or **256 GB**) of virtual memory.
|
||||
2. **L2 Page Table (Level 2)**:
|
||||
* 이 레벨의 엔트리는 더 작은 영역, 구체적으로 **0x2000000 bytes** (32 MB)를 나타냅니다.
|
||||
* L1 엔트리가 해당 영역 전체를 직접 매핑할 수 없으면 L2 테이블을 가리킬 수 있습니다.
|
||||
* An entry here represents a smaller region of virtual memory, specifically **0x2000000 bytes** (32 MB).
|
||||
* An L1 entry may point to an L2 table if it can't map the entire region itself.
|
||||
3. **L3 Page Table (Level 3)**:
|
||||
* 가장 세밀한 레벨로, 각 엔트리는 단일 **4 KB** 메모리 페이지를 매핑합니다.
|
||||
* 더 세밀한 제어가 필요하면 L2 엔트리가 L3 테이블을 가리킬 수 있습니다.
|
||||
* This is the finest level, where each entry maps a single **4 KB** memory page.
|
||||
* An L2 entry may point to an L3 table if more granular control is needed.
|
||||
|
||||
#### Mapping Virtual to Physical Memory
|
||||
|
||||
* **Direct Mapping (Block Mapping)**:
|
||||
* 페이지 테이블의 일부 엔트리는 가상 주소의 범위를 연속된 물리 주소 범위에 직접 **매핑**합니다(단축 경로처럼).
|
||||
* Some entries in a page table directly **map a range of virtual addresses** to a contiguous range of physical addresses (like a shortcut).
|
||||
* **Pointer to Child Page Table**:
|
||||
* 더 세밀한 제어가 필요하면 한 레벨의 엔트리(예: L1)가 다음 레벨의 **child page table**를 가리킬 수 있습니다.
|
||||
* If finer control is needed, an entry in one level (e.g., L1) can point to a **child page table** at the next level (e.g., L2).
|
||||
|
||||
#### Example: Mapping a Virtual Address
|
||||
|
||||
예를 들어 가상 주소 **0x1000000000**에 접근하려고 한다면:
|
||||
Let’s say you try to access the virtual address **0x1000000000**:
|
||||
|
||||
1. **L1 Table**:
|
||||
* 커널은 이 가상 주소에 해당하는 L1 페이지 테이블 엔트리를 확인합니다. 만약 그것이 **L2 page table**를 가리키면 L2 테이블로 이동합니다.
|
||||
* The kernel checks the L1 page table entry corresponding to this virtual address. If it has a **pointer to an L2 page table**, it goes to that L2 table.
|
||||
2. **L2 Table**:
|
||||
* 커널은 더 상세한 매핑을 위해 L2 페이지 테이블을 확인합니다. 이 엔트리가 **L3 page table**를 가리키면 그곳으로 갑니다.
|
||||
* The kernel checks the L2 page table for a more detailed mapping. If this entry points to an **L3 page table**, it proceeds there.
|
||||
3. **L3 Table**:
|
||||
* 커널은 최종 L3 엔트리를 조회하여 실제 메모리 페이지의 **physical address**를 확인합니다.
|
||||
* The kernel looks up the final L3 entry, which points to the **physical address** of the actual memory page.
|
||||
|
||||
#### Example of Address Mapping
|
||||
|
||||
만약 L2 테이블의 첫 인덱스에 물리 주소 **0x800004000**을 쓰면:
|
||||
If you write the physical address **0x800004000** into the first index of the L2 table, then:
|
||||
|
||||
* 가상 주소 **0x1000000000**부터 **0x1002000000**까지는 물리 주소 **0x800004000**부터 **0x802004000**까지에 매핑됩니다.
|
||||
* 이것은 L2 레벨에서의 **block mapping**입니다.
|
||||
* Virtual addresses from **0x1000000000** to **0x1002000000** map to physical addresses from **0x800004000** to **0x802004000**.
|
||||
* This is a **block mapping** at the L2 level.
|
||||
|
||||
대안으로, L2 엔트리가 L3 테이블을 가리키면:
|
||||
Alternatively, if the L2 entry points to an L3 table:
|
||||
|
||||
* 가상 주소 범위 **0x1000000000 -> 0x1002000000**의 각 4 KB 페이지는 L3 테이블의 개별 엔트리에 의해 매핑됩니다.
|
||||
* Each 4 KB page in the virtual address range **0x1000000000 -> 0x1002000000** would be mapped by individual entries in the L3 table.
|
||||
|
||||
### Physical use-after-free
|
||||
|
||||
A **physical use-after-free** (UAF)는 다음과 같은 상황에서 발생합니다:
|
||||
A **physical use-after-free** (UAF) occurs when:
|
||||
|
||||
1. 프로세스가 메모리를 **readable and writable**로 **할당**합니다.
|
||||
2. 해당 메모리에 대해 프로세스가 접근할 수 있도록 **page tables**가 특정 물리 주소로 이 메모리를 매핑하도록 업데이트됩니다.
|
||||
3. 프로세스가 메모리를 **할당 해제(free)** 합니다.
|
||||
4. 하지만 **버그**로 인해 커널은 해당 매핑을 페이지 테이블에서 **제거하는 것을 잊어버리며**, 실제로는 해당 물리 메모리를 자유(프리) 상태로 표시합니다.
|
||||
5. 커널은 이후 이 “프리된” 물리 메모리를 다른 용도로 **재할당**할 수 있습니다(예: **kernel data**).
|
||||
6. 매핑이 제거되지 않았기 때문에 프로세스는 여전히 이 물리 메모리를 **읽고 쓸 수** 있습니다.
|
||||
1. A process **allocates** some memory as **readable and writable**.
|
||||
2. The **page tables** are updated to map this memory to a specific physical address that the process can access.
|
||||
3. The process **deallocates** (frees) the memory.
|
||||
4. However, due to a **bug**, the kernel **forgets to remove the mapping** from the page tables, even though it marks the corresponding physical memory as free.
|
||||
5. The kernel can then **reallocate this "freed" physical memory** for other purposes, like **kernel data**.
|
||||
6. Since the mapping wasn’t removed, the process can still **read and write** to this physical memory.
|
||||
|
||||
결과적으로 프로세스는 **커널 메모리 페이지**에 접근할 수 있게 되며, 여기에는 민감한 데이터나 구조체가 포함될 수 있어 공격자가 **커널 메모리를 조작**할 수 있게 됩니다.
|
||||
This means the process can access **pages of kernel memory**, which could contain sensitive data or structures, potentially allowing an attacker to **manipulate kernel memory**.
|
||||
|
||||
### IOSurface Heap Spray
|
||||
|
||||
공격자는 어떤 특정 커널 페이지가 프리된 메모리에 할당될지 제어할 수 없으므로, **heap spray** 기법을 사용합니다:
|
||||
Since the attacker can’t control which specific kernel pages will be allocated to freed memory, they use a technique called **heap spray**:
|
||||
|
||||
1. 공격자는 커널 메모리에 많은 수의 IOSurface 객체를 **생성**합니다.
|
||||
2. 각 IOSurface 객체는 식별이 쉬운 **magic value**를 특정 필드에 포함합니다.
|
||||
3. 공격자는 프리된 페이지를 **스캔**하여 이러한 IOSurface 객체 중 일부가 프리된 페이지에 할당되었는지 확인합니다.
|
||||
4. 프리된 페이지에서 IOSurface 객체를 찾으면 이를 이용해 **커널 메모리를 읽고 쓰는** 작업을 수행할 수 있습니다.
|
||||
1. The attacker **creates a large number of IOSurface objects** in kernel memory.
|
||||
2. Each IOSurface object contains a **magic value** in one of its fields, making it easy to identify.
|
||||
3. They **scan the freed pages** to see if any of these IOSurface objects landed on a freed page.
|
||||
4. When they find an IOSurface object on a freed page, they can use it to **read and write kernel memory**.
|
||||
|
||||
자세한 내용은 [https://github.com/felix-pb/kfd/tree/main/writeups](https://github.com/felix-pb/kfd/tree/main/writeups)에서 확인하세요.
|
||||
More info about this in [https://github.com/felix-pb/kfd/tree/main/writeups](https://github.com/felix-pb/kfd/tree/main/writeups)
|
||||
|
||||
> [!TIP]
|
||||
> iOS 16+ (A12+) 장치는 PPL 또는 SPTM 같은 하드웨어 완화책을 도입하여 physical UAF 기법의 실효성을 크게 낮춥니다.
|
||||
> PPL은 코드 서명, entitlements 및 민감한 커널 데이터와 관련된 페이지에 대해 엄격한 MMU 보호를 적용하므로, 페이지가 재사용되더라도 userland 또는 손상된 커널 코드의 쓰기는 PPL로 보호되는 페이지에 대해 차단됩니다.
|
||||
> Secure Page Table Monitor (SPTM)는 PPL을 확장하여 페이지 테이블 업데이트 자체를 강화합니다. 이는 권한 있는 커널 코드조차도 안전 검증 없이 프리된 페이지를 은밀히 리맵하거나 매핑을 조작하지 못하도록 보장합니다.
|
||||
> KTRR (Kernel Text Read-Only Region)은 부팅 후 커널의 코드 섹션을 읽기 전용으로 고정합니다. 이것은 physical UAF 공격이 흔히 의존하는 주요 공격 벡터를 차단합니다.
|
||||
> 또한 `IOSurface` 할당은 예측하기 어렵고 유저 접근 가능한 영역으로 매핑하기가 더 어려워져, “magic value 스캔” 트릭의 신뢰도가 떨어집니다. 그리고 `IOSurface`는 이제 entitlements와 sandbox 제한으로 보호됩니다.
|
||||
> Be aware that iOS 16+ (A12+) devices bring hardware mitigations (like PPL or SPTM) that make physical UAF techniques far less viable.
|
||||
> PPL enforces strict MMU protections on pages related to code signing, entitlements, and sensitive kernel data, so, even if a page gets reused, writes from userland or compromised kernel code to PPL-protected pages are blocked.
|
||||
> Secure Page Table Monitor (SPTM) extends PPL by hardening page table updates themselves. It ensures that even privileged kernel code cannot silently remap freed pages or tamper with mappings without going through secure checks.
|
||||
> KTRR (Kernel Text Read-Only Region), which locks down the kernel’s code section as read-only after boot. This prevents any runtime modifications to kernel code, closing off a major attack vector that physical UAF exploits often rely on.
|
||||
> Moreover, `IOSurface` allocations are less predictable and harder to map into user-accessible regions, which makes the “magic value scanning” trick much less reliable. And `IOSurface` is now guarded by entitlements and sandbox restrictions.
|
||||
|
||||
### Step-by-Step Heap Spray Process
|
||||
|
||||
1. **Spray IOSurface Objects**: 공격자는 특수 식별자("magic value")를 가진 많은 IOSurface 객체를 생성합니다.
|
||||
2. **Scan Freed Pages**: 프리된 페이지 중 그 객체들이 할당되었는지 확인합니다.
|
||||
3. **Read/Write Kernel Memory**: IOSurface 객체의 필드를 조작함으로써 **arbitrary reads and writes**를 수행할 수 있습니다. 이를 통해:
|
||||
* 한 필드를 이용해 커널 메모리의 **임의의 32-bit 값**을 읽습니다.
|
||||
* 다른 필드를 이용해 **64-bit 값**을 쓰며, 안정적인 **kernel read/write primitive**를 확보합니다.
|
||||
1. **Spray IOSurface Objects**: The attacker creates many IOSurface objects with a special identifier ("magic value").
|
||||
2. **Scan Freed Pages**: They check if any of the objects have been allocated on a freed page.
|
||||
3. **Read/Write Kernel Memory**: By manipulating fields in the IOSurface object, they gain the ability to perform **arbitrary reads and writes** in kernel memory. This lets them:
|
||||
* Use one field to **read any 32-bit value** in kernel memory.
|
||||
* Use another field to **write 64-bit values**, achieving a stable **kernel read/write primitive**.
|
||||
|
||||
Generate IOSurface objects with the magic value IOSURFACE\_MAGIC to later search for:
|
||||
```c
|
||||
@ -114,7 +128,7 @@ io_connect_t id = result.surface_id;
|
||||
}
|
||||
}
|
||||
```
|
||||
한 개의 해제된 물리 페이지에서 **`IOSurface`** 객체를 검색:
|
||||
해제된 물리 페이지 하나에서 **`IOSurface`** 객체를 검색:
|
||||
```c
|
||||
int iosurface_krw(io_connect_t client, uint64_t *puafPages, int nPages, uint64_t *self_task, uint64_t *puafPage) {
|
||||
io_connect_t *surfaceIDs = malloc(sizeof(io_connect_t) * 0x4000);
|
||||
@ -148,25 +162,25 @@ free(surfaceIDs);
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
### IOSurface로 Kernel Read/Write 달성
|
||||
### IOSurface로 커널 읽기/쓰기 달성
|
||||
|
||||
IOSurface 객체가 kernel memory에 있고 (userspace에서 접근 가능한 freed physical page에 mapped된 상태로) 제어권을 얻으면, 이를 이용해 **arbitrary kernel read and write operations**를 수행할 수 있다.
|
||||
커널 메모리의 IOSurface 객체(사용자 공간에서 접근 가능한 해제된 물리 페이지에 매핑됨)를 제어하게 되면, 이를 사용해 **임의의 커널 읽기 및 쓰기 작업**을 수행할 수 있습니다.
|
||||
|
||||
**Key Fields in IOSurface**
|
||||
|
||||
IOSurface 객체에는 두 가지 중요한 필드가 있다:
|
||||
IOSurface 객체에는 중요한 필드가 두 개 있습니다:
|
||||
|
||||
1. **Use Count Pointer**: **32-bit read**가 가능하다.
|
||||
2. **Indexed Timestamp Pointer**: **64-bit write**가 가능하다.
|
||||
1. **Use Count Pointer**: **32-bit 읽기**를 허용합니다.
|
||||
2. **Indexed Timestamp Pointer**: **64-bit 쓰기**를 허용합니다.
|
||||
|
||||
이 포인터들을 덮어쓰면 kernel memory의 임의 주소로 리다이렉트하여 read/write 기능을 사용할 수 있다.
|
||||
이들 포인터를 덮어써서 커널 메모리의 임의 주소로 리다이렉트하면 읽기/쓰기 기능이 활성화됩니다.
|
||||
|
||||
#### 32-Bit Kernel Read
|
||||
#### 32-Bit 커널 읽기
|
||||
|
||||
읽기를 수행하려면:
|
||||
|
||||
1. **use count pointer**를 대상 주소에서 0x14-byte offset을 뺀 위치를 가리키도록 덮어쓴다.
|
||||
2. `get_use_count` 메서드를 사용해 해당 주소의 값을 읽는다.
|
||||
1. **use count pointer**를 대상 주소에서 0x14 바이트 오프셋을 뺀 위치로 덮어씁니다.
|
||||
2. 해당 주소의 값을 읽기 위해 `get_use_count` 메서드를 사용합니다.
|
||||
```c
|
||||
uint32_t get_use_count(io_connect_t client, uint32_t surfaceID) {
|
||||
uint64_t args[1] = {surfaceID};
|
||||
@ -184,12 +198,12 @@ iosurface_set_use_count_pointer(info.object, orig);
|
||||
return value;
|
||||
}
|
||||
```
|
||||
#### 64-Bit Kernel Write
|
||||
#### 64-비트 커널 쓰기
|
||||
|
||||
쓰기 수행:
|
||||
쓰기 수행하려면:
|
||||
|
||||
1. 대상 주소로 **indexed timestamp pointer**를 덮어쓴다.
|
||||
2. `set_indexed_timestamp` 메서드를 사용해 64비트 값을 쓴다.
|
||||
1. 대상 주소로 **인덱스된 타임스탬프 포인터**를 덮어쓴다.
|
||||
2. `set_indexed_timestamp` 메서드를 사용하여 64비트 값을 쓴다.
|
||||
```c
|
||||
void set_indexed_timestamp(io_connect_t client, uint32_t surfaceID, uint64_t value) {
|
||||
uint64_t args[3] = {surfaceID, 0, value};
|
||||
@ -203,13 +217,13 @@ set_indexed_timestamp(info.client, info.surface, value);
|
||||
iosurface_set_indexed_timestamp_pointer(info.object, orig);
|
||||
}
|
||||
```
|
||||
#### Exploit 흐름 요약
|
||||
#### 익스플로잇 흐름 요약
|
||||
|
||||
1. **Trigger Physical Use-After-Free**: 해제된 페이지는 재사용을 위해 이용 가능해진다.
|
||||
2. **Spray IOSurface Objects**: 커널 메모리에 고유한 "magic value"를 가진 다수의 IOSurface 객체를 할당한다.
|
||||
3. **Identify Accessible IOSurface**: 제어하는 해제된 페이지에서 IOSurface를 찾아낸다.
|
||||
4. **Abuse Use-After-Free**: IOSurface 객체의 포인터를 수정하여 IOSurface 메서드를 통해 임의의 **kernel read/write**를 가능하게 한다.
|
||||
1. **Trigger Physical Use-After-Free**: 해제된 페이지가 재사용을 위해 사용 가능해짐.
|
||||
2. **Spray IOSurface Objects**: 커널 메모리에 고유한 "magic value"를 가진 다수의 IOSurface 객체를 할당.
|
||||
3. **Identify Accessible IOSurface**: 자신이 제어하는 해제된 페이지에서 IOSurface를 찾아냄.
|
||||
4. **Abuse Use-After-Free**: IOSurface 객체의 포인터를 수정하여 IOSurface 메서드를 통해 임의의 **kernel read/write**를 수행할 수 있게 함.
|
||||
|
||||
이러한 primitives로 exploit는 커널 메모리에 대해 제어된 **32-bit reads**와 **64-bit writes**를 제공한다. 추가적인 jailbreak 단계는 더 안정적인 read/write primitives를 포함할 수 있으며, 이는 추가 보호 장치(예: 최신 arm64e 기기의 PPL)를 우회해야 할 수 있다.
|
||||
이러한 프리미티브로 익스플로잇은 커널 메모리에 대해 제어된 **32-bit reads** 및 **64-bit writes**를 제공합니다. 추가적인 탈옥 단계는 더 안정적인 읽기/쓰기 프리미티브를 필요로 할 수 있으며, 이는 추가 보호(예: 최신 arm64e 기기의 PPL)를 우회해야 할 수 있습니다.
|
||||
|
||||
{{#include ../../banners/hacktricks-training.md}}
|
||||
|
@ -4,66 +4,144 @@
|
||||
|
||||
## 소개
|
||||
|
||||
피싱 시도를 탐지하기 위해서는 **현재 사용되고 있는 피싱 기술을 이해하는 것이 중요합니다**. 이 게시물의 상위 페이지에서 이 정보를 찾을 수 있으니, 현재 어떤 기술이 사용되고 있는지 모른다면 상위 페이지로 가서 최소한 그 섹션을 읽어보는 것을 추천합니다.
|
||||
피싱 시도를 탐지하려면 **오늘날 사용되는 피싱 기술들을 이해하는 것**이 중요합니다. 이 게시물의 상위 페이지에서 해당 정보를 찾을 수 있으므로, 현재 어떤 기술이 사용되는지 모른다면 상위 페이지로 가서 적어도 그 섹션을 읽어보시길 권합니다.
|
||||
|
||||
이 게시물은 **공격자가 피해자의 도메인 이름을 어떤 식으로든 모방하거나 사용할 것이라는 아이디어를 기반으로 합니다**. 만약 귀하의 도메인이 `example.com`이고, 어떤 이유로 `youwonthelottery.com`과 같은 완전히 다른 도메인 이름으로 피싱을 당한다면, 이러한 기술로는 이를 밝혀낼 수 없습니다.
|
||||
이 게시물은 **공격자가 피해자의 도메인 이름을 어떻게든 모방하거나 사용할 것**이라는 아이디어에 기반합니다. 만약 여러분의 도메인이 `example.com`이고 공격자가 어떤 이유로 완전히 다른 도메인 이름(`youwonthelottery.com`)을 사용해 피싱을 시도했다면, 아래 기술들은 이를 밝혀내지 못할 수 있습니다.
|
||||
|
||||
## 도메인 이름 변형
|
||||
|
||||
이메일 내에서 **유사한 도메인** 이름을 사용하는 **피싱** 시도를 **발견하는 것은 꽤 쉽습니다**.\
|
||||
공격자가 사용할 수 있는 **가장 가능성이 높은 피싱 이름 목록을 생성하고** 그것이 **등록되었는지** 또는 **IP**가 사용되고 있는지 확인하는 것으로 충분합니다.
|
||||
이메일 내부에서 **유사한 도메인**을 사용하는 피싱 시도는 찾아내기 **상당히 쉽습니다**.\
|
||||
공격자가 사용할 가능성이 높은 피싱 이름들의 목록을 **생성**하고, 해당 도메인이 **등록되어 있는지** 또는 어떤 **IP**에 사용되고 있는지 확인하면 됩니다.
|
||||
|
||||
### 의심스러운 도메인 찾기
|
||||
|
||||
이 목적을 위해 다음 도구 중 하나를 사용할 수 있습니다. 이 도구들은 도메인에 IP가 할당되어 있는지 확인하기 위해 DNS 요청을 자동으로 수행합니다:
|
||||
이를 위해 다음 도구들 중 하나를 사용할 수 있습니다. 이 도구들은 도메인에 할당된 IP가 있는지 확인하기 위해 자동으로 DNS 요청도 수행합니다:
|
||||
|
||||
- [**dnstwist**](https://github.com/elceef/dnstwist)
|
||||
- [**urlcrazy**](https://github.com/urbanadventurer/urlcrazy)
|
||||
|
||||
### 비트플리핑
|
||||
팁: 후보 목록을 생성했다면 DNS 리졸버 로그에 투입하여 조직 내부에서의 **NXDOMAIN lookups**(사용자가 공격자가 실제로 등록하기 전에 오타 도메인에 접속을 시도함)을 탐지하세요. 정책이 허용한다면 이러한 도메인을 Sinkhole 또는 사전 차단하세요.
|
||||
|
||||
**이 기술에 대한 간단한 설명은 상위 페이지에서 찾을 수 있습니다. 또는** [**https://www.bleepingcomputer.com/news/security/hijacking-traffic-to-microsoft-s-windowscom-with-bitflipping/**](https://www.bleepingcomputer.com/news/security/hijacking-traffic-to-microsoft-s-windowscom-with-bitflipping/) **에서 원본 연구를 읽어보세요.**
|
||||
### Bitflipping
|
||||
|
||||
예를 들어, 도메인 microsoft.com의 1비트 수정은 _windnws.com_으로 변환될 수 있습니다.\
|
||||
**공격자는 피해자와 관련된 비트플리핑 도메인을 가능한 한 많이 등록하여 합법적인 사용자를 자신의 인프라로 리디렉션할 수 있습니다**.
|
||||
**이 기술에 대한 짧은 설명은 상위 페이지에서 확인할 수 있습니다. 또는 원본 연구를 읽어보세요:** [**https://www.bleepingcomputer.com/news/security/hijacking-traffic-to-microsoft-s-windowscom-with-bitflipping/**](https://www.bleepingcomputer.com/news/security/hijacking-traffic-to-microsoft-s-windowscom-with-bitflipping/)
|
||||
|
||||
**모든 가능한 비트플리핑 도메인 이름도 모니터링해야 합니다.**
|
||||
예를 들어, 도메인 microsoft.com에서 1비트만 변경하면 _windnws.com._으로 변할 수 있습니다.\
|
||||
**공격자들은 피해자와 관련된 가능한 한 많은 bit-flipping 도메인을 등록하여 정당한 사용자를 자신의 인프라로 리디렉션할 수 있습니다.**
|
||||
|
||||
**모든 가능한 bit-flipping 도메인 이름도 모니터링해야 합니다.**
|
||||
|
||||
동일문자(예: Latin/Cyrillic 혼합) 기반의 homoglyph/IDN lookalikes도 고려해야 한다면, 다음을 확인하세요:
|
||||
|
||||
{{#ref}}
|
||||
homograph-attacks.md
|
||||
{{#endref}}
|
||||
|
||||
### 기본 검사
|
||||
|
||||
잠재적인 의심스러운 도메인 이름 목록이 생기면 **이들을 확인해야 합니다** (주로 HTTP 및 HTTPS 포트) **피해자의 도메인과 유사한 로그인 양식을 사용하고 있는지 확인하기 위해**.\
|
||||
포트 3333이 열려 있고 `gophish` 인스턴스가 실행 중인지 확인할 수도 있습니다.\
|
||||
각 발견된 의심스러운 도메인이 **얼마나 오래되었는지** 아는 것도 흥미롭습니다. 젊을수록 위험이 더 큽니다.\
|
||||
의심스러운 웹 페이지의 **스크린샷**을 찍어 의심스러운지 확인하고, 그런 경우 **접속하여 더 자세히 살펴보세요**.
|
||||
잠재적으로 의심스러운 도메인 목록을 확보했다면 해당 도메인들을 **확인**해야 합니다(주로 HTTP 및 HTTPS 포트). 이는 해당 도메인들이 피해자 도메인의 로그인 폼과 **유사한 로그인 폼을 사용하고 있는지** 확인하기 위함입니다.\
|
||||
포트 3333이 열려 있고 `gophish` 인스턴스가 실행 중인지도 확인할 수 있습니다.\
|
||||
발견된 의심 도메인들이 **언제 생성되었는지(등록 연월)** 아는 것도 흥미로운데, 생성일이 최신일수록 위험이 큽니다.\
|
||||
의심스러운 HTTP 및/또는 HTTPS 웹페이지의 **스크린샷**을 받아 보고 의심스러우면 **접속하여 더 자세히 조사**하세요.
|
||||
|
||||
### 고급 검사
|
||||
|
||||
한 단계 더 나아가고 싶다면 **그 의심스러운 도메인을 모니터링하고 가끔 더 검색하는 것을 추천합니다** (매일? 몇 초/분밖에 걸리지 않습니다). 관련 IP의 열린 **포트**를 **확인하고 `gophish` 또는 유사한 도구의 인스턴스를 검색해야 합니다** (네, 공격자도 실수를 합니다) 그리고 **의심스러운 도메인 및 하위 도메인의 HTTP 및 HTTPS 웹 페이지를 모니터링하여 피해자의 웹 페이지에서 로그인 양식을 복사했는지 확인하세요**.\
|
||||
이 작업을 **자동화하기 위해** 피해자 도메인의 로그인 양식 목록을 가지고, 의심스러운 웹 페이지를 스파이더링하고, 의심스러운 도메인 내에서 발견된 각 로그인 양식을 피해자 도메인의 각 로그인 양식과 비교하는 것을 추천합니다. `ssdeep`과 같은 것을 사용하여.\
|
||||
의심스러운 도메인의 로그인 양식을 찾았다면 **쓰레기 자격 증명을 보내고** **피해자의 도메인으로 리디렉션되는지 확인할 수 있습니다**.
|
||||
한 단계 더 나아가려면 의심스러운 도메인들을 주기적으로(매일?) **모니터링하고 추가로 검색**할 것을 권합니다(소요 시간은 몇 초/분에 불과합니다). 관련 IP의 열린 **포트**를 확인하고 `gophish` 또는 유사 도구의 인스턴스 존재 여부를 **검색**하세요(네, 공격자들도 실수를 합니다). 또한 의심 도메인 및 서브도메인의 HTTP 및 HTTPS 웹페이지를 모니터링하여 피해자의 웹페이지에서 복제한 로그인 폼이 있는지 확인하세요.\
|
||||
이를 **자동화**하려면 피해자 도메인의 로그인 폼 목록을 보유하고, 의심스러운 웹페이지를 크롤링하여 발견된 각 로그인 폼을 `ssdeep` 같은 도구로 피해자 도메인의 각 로그인 폼과 비교하는 방식을 권장합니다.\
|
||||
의심 도메인의 로그인 폼을 찾았다면, **임의의(쓸데없는) 자격증명(junk credentials)을 전송**해 보고 **피해자 도메인으로 리디렉션되는지** 확인할 수 있습니다.
|
||||
|
||||
## 키워드를 사용하는 도메인 이름
|
||||
---
|
||||
|
||||
상위 페이지에서는 **피해자의 도메인 이름을 더 큰 도메인 안에 넣는** 도메인 이름 변형 기술도 언급합니다 (예: paypal-financial.com은 paypal.com을 위한 것입니다).
|
||||
### favicon 및 웹 지문으로 헌팅하기 (Shodan/ZoomEye/Censys)
|
||||
|
||||
### 인증서 투명성
|
||||
많은 피싱 키트는 사칭하는 브랜드의 favicon을 재사용합니다. 인터넷 전체 스캐너는 base64로 인코딩된 favicon의 MurmurHash3를 계산합니다. 해시를 생성하고 이를 기반으로 피벗할 수 있습니다:
|
||||
|
||||
이전의 "무차별 대입" 접근 방식을 취할 수는 없지만, 실제로 **이러한 피싱 시도를 밝혀내는 것이 가능합니다**. 인증서 투명성 덕분입니다. CA에 의해 인증서가 발급될 때마다 세부 사항이 공개됩니다. 이는 인증서 투명성을 읽거나 모니터링함으로써 **이름에 키워드를 사용하는 도메인을 찾는 것이 가능하다는 것을 의미합니다**. 예를 들어, 공격자가 [https://paypal-financial.com](https://paypal-financial.com)의 인증서를 생성하면, 인증서를 보면 "paypal"이라는 키워드를 찾고 의심스러운 이메일이 사용되고 있음을 알 수 있습니다.
|
||||
Python 예제 (mmh3):
|
||||
```python
|
||||
import base64, requests, mmh3
|
||||
url = "https://www.paypal.com/favicon.ico" # change to your brand icon
|
||||
b64 = base64.encodebytes(requests.get(url, timeout=10).content)
|
||||
print(mmh3.hash(b64)) # e.g., 309020573
|
||||
```
|
||||
- Shodan 쿼리: `http.favicon.hash:309020573`
|
||||
- 도구 사용: favfreak 같은 커뮤니티 도구를 확인하여 Shodan/ZoomEye/Censys용 hashes 및 dorks를 생성하세요.
|
||||
|
||||
게시물 [https://0xpatrik.com/phishing-domains/](https://0xpatrik.com/phishing-domains/)에서는 Censys를 사용하여 특정 키워드에 영향을 미치는 인증서를 검색하고 날짜(오직 "새로운" 인증서) 및 CA 발급자 "Let's Encrypt"로 필터링할 수 있다고 제안합니다:
|
||||
Notes
|
||||
- Favicons은 재사용됩니다; 일치 항목을 단서로 취급하고 조치하기 전에 콘텐츠와 certs를 검증하세요.
|
||||
- 더 높은 정확도를 위해 domain-age 및 keyword heuristics와 결합하세요.
|
||||
|
||||
### URL 텔레메트리 헌팅 (urlscan.io)
|
||||
|
||||
`urlscan.io`는 제출된 URL의 과거 스크린샷, DOM, 요청 및 TLS 메타데이터를 저장합니다. 브랜드 남용 및 클론을 찾아낼 수 있습니다:
|
||||
|
||||
Example queries (UI or API):
|
||||
- 정식 도메인을 제외한 유사 사이트 찾기: `page.domain:(/.*yourbrand.*/ AND NOT yourbrand.com AND NOT www.yourbrand.com)`
|
||||
- 자산을 hotlinking하는 사이트 찾기: `domain:yourbrand.com AND NOT page.domain:yourbrand.com`
|
||||
- 최근 결과로 제한: `AND date:>now-7d`를 덧붙이세요
|
||||
|
||||
API 예시:
|
||||
```bash
|
||||
# Search recent scans mentioning your brand
|
||||
curl -s 'https://urlscan.io/api/v1/search/?q=page.domain:(/.*yourbrand.*/%20AND%20NOT%20yourbrand.com)%20AND%20date:>now-7d' \
|
||||
-H 'API-Key: <YOUR_URLSCAN_KEY>' | jq '.results[].page.url'
|
||||
```
|
||||
JSON에서 pivot할 항목:
|
||||
- `page.tlsIssuer`, `page.tlsValidFrom`, `page.tlsAgeDays` — 유사 도메인 탐지를 위해 매우 새로 발급된 인증서를 찾아보세요
|
||||
- `task.source` 값(예: `certstream-suspicious`) — 발견을 CT 모니터링과 연계하는 데 사용
|
||||
|
||||
### RDAP로 도메인 연령 확인 (스크립트 가능)
|
||||
|
||||
RDAP는 기계 판독 가능한 생성 이벤트를 반환합니다. **새로 등록된 도메인 (NRDs)** 를 표시하는 데 유용합니다.
|
||||
```bash
|
||||
# .com/.net RDAP (Verisign)
|
||||
curl -s https://rdap.verisign.com/com/v1/domain/suspicious-example.com | \
|
||||
jq -r '.events[] | select(.eventAction=="registration") | .eventDate'
|
||||
|
||||
# Generic helper using rdap.net redirector
|
||||
curl -s https://www.rdap.net/domain/suspicious-example.com | jq
|
||||
```
|
||||
파이프라인을 보강하려면 도메인을 등록 연령 구간(예: <7 days, <30 days)으로 태그하고 트리아지 우선순위를 조정하세요.
|
||||
|
||||
### TLS/JAx fingerprints로 AiTM 인프라 탐지
|
||||
|
||||
Modern credential-phishing은 세션 토큰 탈취를 위해 **Adversary-in-the-Middle (AiTM)** 리버스 프록시(예: Evilginx)를 점점 더 많이 사용합니다. 네트워크 측 탐지를 추가할 수 있습니다:
|
||||
|
||||
- Egress에서 TLS/HTTP 지문(JA3/JA4/JA4S/JA4H)을 로그하세요. 일부 Evilginx 빌드에서는 안정적인 JA4 클라이언트/서버 값이 관찰되었습니다. 알려진 악성 지문에 대해서만 약한 신호로 경보를 설정하고 항상 콘텐츠 및 domain intel로 확인하세요.
|
||||
- CT 또는 urlscan을 통해 발견된 유사 호스트에 대해 TLS certificate metadata(issuer, SAN count, wildcard 사용 여부, validity)를 선제적으로 기록하고 DNS age 및 지리적 위치와 상관관계를 분석하세요.
|
||||
|
||||
> Note: 지문을 enrichment로 취급하고 단독 차단 근거로 보지 마세요; 프레임워크는 진화하며 무작위화하거나 난독화할 수 있습니다.
|
||||
|
||||
### Domain names using keywords
|
||||
|
||||
상위 페이지는 또한 **victim's domain name inside a bigger domain** 기법을 언급합니다(예: paypal-financial.com은 paypal.com을 겨냥).
|
||||
|
||||
#### Certificate Transparency
|
||||
|
||||
이전의 "Brute-Force" 접근법은 불가능할 수 있지만, Certificate Transparency 덕분에 이러한 피싱 시도를 찾아내는 것이 실제로 가능합니다. CA가 인증서를 발행할 때마다 세부 정보가 공개됩니다. 즉, certificate transparency를 읽거나 모니터링하면 이름 안에 키워드를 포함한 도메인을 찾아낼 수 있습니다. 예를 들어 공격자가 [https://paypal-financial.com](https://paypal-financial.com)의 인증서를 생성하면, 인증서를 통해 "paypal"이라는 키워드를 찾아 의심스러운 사용을 확인할 수 있습니다.
|
||||
|
||||
게시물 [https://0xpatrik.com/phishing-domains/](https://0xpatrik.com/phishing-domains/)은 Censys를 사용해 특정 키워드가 포함된 인증서를 검색하고 날짜(신규 인증서만)와 CA 발급자("Let's Encrypt")로 필터링할 수 있다고 제안합니다:
|
||||
|
||||
.png>)
|
||||
|
||||
그러나 무료 웹 [**crt.sh**](https://crt.sh)를 사용하여 "같은" 작업을 수행할 수 있습니다. **키워드를 검색하고** 원하시면 **날짜 및 CA로 결과를 필터링**할 수 있습니다.
|
||||
그러나 무료 웹 **crt.sh**를 사용해도 "동일한" 작업을 수행할 수 있습니다. 키워드를 검색하고 원하면 결과를 날짜와 CA로 필터링할 수 있습니다.
|
||||
|
||||
.png>)
|
||||
|
||||
이 마지막 옵션을 사용하면 Matching Identities 필드를 사용하여 실제 도메인의 어떤 아이덴티티가 의심스러운 도메인과 일치하는지 확인할 수 있습니다 (의심스러운 도메인이 잘못된 긍정일 수 있다는 점에 유의하세요).
|
||||
이 마지막 옵션을 사용하면 Matching Identities 필드를 이용해 실제 도메인의 어떤 identity가 의심 도메인과 일치하는지 확인할 수 있습니다(의심 도메인은 false positive일 수 있음에 유의).
|
||||
|
||||
**또 다른 대안**은 [**CertStream**](https://medium.com/cali-dog-security/introducing-certstream-3fc13bb98067)라는 환상적인 프로젝트입니다. CertStream은 새로 생성된 인증서의 실시간 스트림을 제공하며, 이를 사용하여 (근사) 실시간으로 지정된 키워드를 탐지할 수 있습니다. 실제로 [**phishing_catcher**](https://github.com/x0rz/phishing_catcher)라는 프로젝트가 바로 그것을 수행합니다.
|
||||
**Another alternative**는 [**CertStream**](https://medium.com/cali-dog-security/introducing-certstream-3fc13bb98067)이라는 훌륭한 프로젝트입니다. CertStream은 새로 생성된 인증서의 실시간 스트림을 제공하며, 이를 이용해 지정한 키워드를 (거의) 실시간으로 탐지할 수 있습니다. 실제로 [**phishing_catcher**](https://github.com/x0rz/phishing_catcher)라는 프로젝트가 바로 이를 수행합니다.
|
||||
|
||||
### **새로운 도메인**
|
||||
실용 팁: CT 히트를 트리아지할 때는 NRDs, 신뢰되지 않거나 알 수 없는 레지스트라, privacy-proxy WHOIS, `NotBefore` 시간이 매우 최근인 인증서를 우선순위로 두세요. 소음을 줄이려면 소유한 도메인/브랜드의 allowlist를 유지하세요.
|
||||
|
||||
**마지막 대안**은 일부 TLD에 대해 **새로 등록된 도메인 목록을 수집하는 것**입니다 ([Whoxy](https://www.whoxy.com/newly-registered-domains/)가 이러한 서비스를 제공합니다) 그리고 **이 도메인에서 키워드를 확인하는 것**입니다. 그러나 긴 도메인은 일반적으로 하나 이상의 하위 도메인을 사용하므로, 키워드는 FLD 내에 나타나지 않으며 피싱 하위 도메인을 찾을 수 없습니다.
|
||||
#### **New domains**
|
||||
|
||||
**One last alternative**는 일부 TLD에 대해 **newly registered domains** 목록을 수집하는 것입니다([Whoxy](https://www.whoxy.com/newly-registered-domains/)가 이런 서비스를 제공합니다) 그리고 이러한 도메인에서 키워드를 확인하세요. 다만, 긴 도메인은 보통 하나 이상의 서브도메인을 사용하므로 키워드가 FLD 안에 나타나지 않아 피싱 서브도메인을 찾지 못할 수 있습니다.
|
||||
|
||||
추가 휴리스틱: 특정 **file-extension TLDs**(예: `.zip`, `.mov`)는 경보 시 추가 의심 대상으로 처리하세요. 이는 미끼에서 파일명으로 혼동되는 경우가 많으므로 TLD 신호를 브랜드 키워드 및 NRD age와 결합하면 정확도를 높일 수 있습니다.
|
||||
|
||||
## References
|
||||
|
||||
- urlscan.io – Search API reference: https://urlscan.io/docs/search/
|
||||
- APNIC Blog – JA4+ network fingerprinting (includes Evilginx example): https://blog.apnic.net/2023/11/22/ja4-network-fingerprinting/
|
||||
|
||||
{{#include ../../banners/hacktricks-training.md}}
|
||||
|
@ -24,13 +24,15 @@
|
||||
/* 2 — load a single index (remote → local) */
|
||||
async function loadIndex(remote, local, isCloud=false){
|
||||
let rawLoaded = false;
|
||||
try {
|
||||
const r = await fetch(remote,{mode:'cors'});
|
||||
if (!r.ok) throw new Error('HTTP '+r.status);
|
||||
importScripts(URL.createObjectURL(new Blob([await r.text()],{type:'application/javascript'})));
|
||||
rawLoaded = true;
|
||||
} catch(e){ console.warn('remote',remote,'failed →',e); }
|
||||
if(!rawLoaded){
|
||||
if(remote){
|
||||
try {
|
||||
const r = await fetch(remote,{mode:'cors'});
|
||||
if (!r.ok) throw new Error('HTTP '+r.status);
|
||||
importScripts(URL.createObjectURL(new Blob([await r.text()],{type:'application/javascript'})));
|
||||
rawLoaded = true;
|
||||
} catch(e){ console.warn('remote',remote,'failed →',e); }
|
||||
}
|
||||
if(!rawLoaded && local){
|
||||
try { importScripts(abs(local)); rawLoaded = true; }
|
||||
catch(e){ console.error('local',local,'failed →',e); }
|
||||
}
|
||||
@ -40,13 +42,41 @@
|
||||
return data;
|
||||
}
|
||||
|
||||
async function loadWithFallback(remotes, local, isCloud=false){
|
||||
if(remotes.length){
|
||||
const [primary, ...secondary] = remotes;
|
||||
const primaryData = await loadIndex(primary, null, isCloud);
|
||||
if(primaryData) return primaryData;
|
||||
|
||||
if(local){
|
||||
const localData = await loadIndex(null, local, isCloud);
|
||||
if(localData) return localData;
|
||||
}
|
||||
|
||||
for (const remote of secondary){
|
||||
const data = await loadIndex(remote, null, isCloud);
|
||||
if(data) return data;
|
||||
}
|
||||
}
|
||||
|
||||
return local ? loadIndex(null, local, isCloud) : null;
|
||||
}
|
||||
|
||||
(async () => {
|
||||
const MAIN_RAW = 'https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/refs/heads/master/searchindex.js';
|
||||
const CLOUD_RAW = 'https://raw.githubusercontent.com/HackTricks-wiki/hacktricks-cloud/refs/heads/master/searchindex.js';
|
||||
const htmlLang = (document.documentElement.lang || 'en').toLowerCase();
|
||||
const lang = htmlLang.split('-')[0];
|
||||
const mainReleaseBase = 'https://github.com/HackTricks-wiki/hacktricks/releases/download';
|
||||
const cloudReleaseBase = 'https://github.com/HackTricks-wiki/hacktricks-cloud/releases/download';
|
||||
|
||||
const mainTags = Array.from(new Set([`searchindex-${lang}`, 'searchindex-en', 'searchindex-master']));
|
||||
const cloudTags = Array.from(new Set([`searchindex-${lang}`, 'searchindex-en', 'searchindex-master']));
|
||||
|
||||
const MAIN_REMOTE_SOURCES = mainTags.map(tag => `${mainReleaseBase}/${tag}/searchindex.js`);
|
||||
const CLOUD_REMOTE_SOURCES = cloudTags.map(tag => `${cloudReleaseBase}/${tag}/searchindex.js`);
|
||||
|
||||
const indices = [];
|
||||
const main = await loadIndex(MAIN_RAW , '/searchindex.js', false); if(main) indices.push(main);
|
||||
const cloud= await loadIndex(CLOUD_RAW, '/searchindex-cloud.js', true ); if(cloud) indices.push(cloud);
|
||||
const main = await loadWithFallback(MAIN_REMOTE_SOURCES , '/searchindex.js', false); if(main) indices.push(main);
|
||||
const cloud= await loadWithFallback(CLOUD_REMOTE_SOURCES, '/searchindex-cloud.js', true ); if(cloud) indices.push(cloud);
|
||||
|
||||
if(!indices.length){ postMessage({ready:false, error:'no-index'}); return; }
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user