# Understanding Alcatraz ~ Obfuscator Analysis [TR]

Bu yazının Türkçe versiyonu özel olarak [TTMO](https://x.com/__TTMO__) için Türkiye'deki tersine mühendislik topluluğuna hazırlandı.

# Introduction

Özellikle zararlı yazılım geliştiricileri ile kaynak kodunu korumak isteyen kullanıcılar tarafından sıklıkla tercih edilen binary-to-binary (bin2bin) obfuscator’lar, sahip oldukları gelişmiş tekniklerle birlikte her geçen gün zararlı yazılım analizcilerinin ve tersine mühendislik uzmanlarının işini daha da zorlaştırmakta, analiz süreçlerini karmaşıklaştırmaktadır. Bu yazıda, bu araçlardan bir tanesi olan “Alcatraz” obfuscator’ın süreçlerinin analizi yapılmıştır.

# Alcatraz Obfuscator

Alcatraz, x64 tabanlı PE dosyalarını obfuscate edebilen bir obfuscator olup, içerdiği tekniklerle birlikte açık kaynak kodlu binary-to-binary obfuscator çözümleri arasında öne çıkmaktadır.

* Obfuscation of immediate moves
    
* Control flow flattening
    
* ADD mutation
    
* Entry-point obfuscation
    
* Lea obfuscation
    
* Anti disassembly
    

Direkt olarak bir sample üzerinde (crackme, malware etc.) analiz yapmıyorsak, kendi hazırladığımız bir dosya üzerinde hedef obfuscator'un yaptığı değişiklikleri daha rahat gözlemleyebiliriz. Bunun için analiz etmeden önce bir test uygulaması hazırladım.

```c
#include <stdio.h>

char* func1() {
    return "0xreverse.com";
}

int func2() {
    return 24;
}

int main()
{
    printf("%s\n", func1());
    printf("%d\n", func2());
    getchar();
    return 1;
}
```

Release (x64) modunda derlediğim bu uygulamayı Alcatraz üzerinde tüm özellikler açık olacak şekilde obfuscate ettim. Yazının devamında bu test uygulamasından "sample" olarak bahsedeceğim.

# Analysis

## Entry-Point Obfuscation

Sample'ı IDA üzerinde açtığımda karşıma gelen ilk fonksiyonun (Entry-Point) PE Header'lar ile bir işlem yaptığını gördüm.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1744550422248/b3fcb389-83f7-4c3f-a54e-a374d8f57e6c.png align="center")

```c
ImageBaseAddress = (char *)NtCurrentPeb()->ImageBaseAddress;
v7 = &ImageBaseAddress[*((int *)ImageBaseAddress + 15)];
v8 = *((unsigned __int16 *)v7 + 3);
v9 = (__int64)&v7[*((unsigned __int16 *)v7 + 10) + 24];
```

IDA, yapıları tanımadığı için bunları pointer işlemleriyle gösteriyor. Bu süreci iyileştirmek için Type libraries (Shift + F11 veya View &gt; Open Subviews) sekmesinden `MS SDK (Windows 7 x64)` Type'larını içeriye aktardım. Değişkenlerin tipini değiştirmek üzere Structures (Shift + F9) sekmesinden kullanacağım tipleri Ins tuşuna basarak "standard structure" olarak ekledim. Structure’ları ekledikten sonra koddaki değişkenleri uygun tiplere dönüştürdüm.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1744550458595/8fbca073-308c-4379-af7e-8964adab94d9.png align="center")

```c
dosHeader = (IMAGE_DOS_HEADER *)NtCurrentPeb()->ImageBaseAddress;
ntHeader = (IMAGE_NT_HEADERS *)((char *)dosHeader + dosHeader->e_lfanew);
NumberOfSections = ntHeader->FileHeader.NumberOfSections;
firstSectionHeader = (IMAGE_SECTION_HEADER *)((char *)&ntHeader->OptionalHeader
										  + ntHeader->FileHeader.SizeOfOptionalHeader);
if (ntHeader->FileHeader.NumberOfSections)
{
	qmemcpy(obfuscatorSection, ".0Dev", sizeof(obfuscatorSection));
	do
	{
	  v10 = obfuscatorSection;
	  v11 = *(_OWORD *)&firstSectionHeader->SizeOfRawData;
	  v12 = *(_QWORD *)&firstSectionHeader->NumberOfRelocations;
	  hasSectionName = _mm_cvtsi128_si32(*(__m128i *)firstSectionHeader->Name);
	  *(_OWORD *)selectedSectionName.Name = *(_OWORD *)firstSectionHeader->Name;
	  *(_QWORD *)&selectedSectionName.NumberOfRelocations = v12;
	  *(_OWORD *)&selectedSectionName.SizeOfRawData = v11;
	  if ( hasSectionName )
	  {
		a3 = (char *)&selectedSectionName - obfuscatorSection;
		v14 = hasSectionName;
//...
```

Bu işlemler sonrası start fonksiyonu daha kolay anlamlandırabilir hale geldi. Fonksiyon en başta ismi 0Dev olan section'u bulmaya çalışıyor. Aşağıda da bu section'ın header'ı içerisindeki bazı değerler üzerinde matematiksel operasyonlar yaparak Original Entry-Point'i hesaplıyor.

```C
return ((__int64 (__fastcall *)(_QWORD, signed __int64, signed __int64, IMAGE_SECTION_HEADER *))((char *)dosHeader + (unsigned int)__ROR4__(LODWORD(ntHeader->OptionalHeader.SizeOfStackCommit) ^ *(_DWORD *)((char *)&dosHeader->e_magic + v3->VirtualAddress), ntHeader->FileHeader.TimeDateStamp)))(
	   a2,
	   v4,
	   a3,
	   firstSectionHeader);
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1744550498550/6fb0afa6-a263-4fee-906f-79bfeb982ce5.png align="center")

[Kaynak kodu](https://github.com/weak1337/Alcatraz/blob/master/Alcatraz/obfuscator/misc/custom_entry.cpp) üzerinden de bu süreci teyit edebiliriz:

```c
__declspec(safebuffers) int obfuscator::custom_main(int argc, char* argv[]) {
	auto peb = (uint64_t)__readgsqword(0x60); // peb
	auto base = *(uint64_t*)(peb + 0x10); // peb + 0x10 = DOS Header (MZ)
	PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)(base + ((PIMAGE_DOS_HEADER)base)->e_lfanew);

	PIMAGE_SECTION_HEADER section = nullptr;
	auto first = IMAGE_FIRST_SECTION(nt);
	for (int i = 0; i < nt->FileHeader.NumberOfSections; i++) {
		auto currsec = first[i];

		char dev[5] = { '.', '0', 'D', 'e', 'v' };
		if (!_strcmp((char*)currsec.Name, dev)) {
			section = &currsec;
		}
	}

	uint32_t real_entry = *(uint32_t*)(base + section->VirtualAddress);
	real_entry ^= nt->OptionalHeader.SizeOfStackCommit;
	real_entry = _rotr(real_entry, nt->FileHeader.TimeDateStamp);
	return reinterpret_cast<int(*)(int, char**)>(base + real_entry)(argc, argv);
}
```

### Writing OEP Finder with Qiling Framework

Tüm bu işlemlerin birçok çözüm yolu olabilir (örn. statik olarak binary üzerinde [LIEF](https://lief.re/) gibi pe parser kütüphaneleri hesaplamayı yapmak). Ben tüm bu işlemin [Qiling Framework](https://github.com/qilingframework/qiling) ile nasıl çözülebileceğini göstermek istiyorum. IDAPython apilerini kullanarak sample'ın entry-point'ini ve bu adrese ait fonksiyonun başlangıç bitiş adresini alabiliriz.

```python
from qiling import Qiling
from qiling.const import QL_VERBOSE  

# Qiling Version: 1.4.8 f4464b95
# IDA Pro Version 7.7

ROOTFS_PATH = r"ROOTFS_PATH"

def find_oep(sample_path, func) -> int:
    # I added the log_devices parameter because I got the following error.
    # TypeError: unexpected logging device type: IDAPythonStdOut
    ql = Qiling([sample_path], rootfs=ROOTFS_PATH, verbose=QL_VERBOSE.OFF, log_devices=[])
    # To prevent Qiling from continuing emulation, the RAX value
    # must be taken one instruction before the jmp opcode is executed.
    ql.run(begin=func.start_ea, end=func.end_ea - 0x4) # - jmp opcode size
    original_entry_point = ql.arch.regs.read("RAX")
    print(f"[+] Found Original Entry Point: 0x{original_entry_point:x}")
    return original_entry_point


def main():
    print("[~] 0xReverse - Alcatraz Deobfuscator IDA Script [~]")
    sample_path = idaapi.get_input_file_path()
    # IDAPython CheatSheet
    # https://gist.github.com/icecr4ck/7a7af3277787c794c66965517199fc9c
    info = idaapi.get_inf_structure()
    entrypoint = info.start_ea  # EntryPoint start address
    if func := ida_funcs.get_func(entrypoint):
        function_name = idc.get_func_name(entrypoint)
        print(f"[+] Emulating Function Name: {function_name}, Address: {entrypoint:x}")        
        original_entry_point = find_oep(sample_path, func)
        # Rename OEP address to original_entry_point
        idc.set_name(original_entry_point, "original_entry_point", idc.SN_NOWARN)
    else:
        print(f"[-] This address is not a function")
        exit(-1)

main()
```

Yazdığım bu scripti `File > Script File... (Alt + F7)` ile çalıştırdıktan sonra aldığım çıktı bu:

```plaintext
[~] 0xReverse - Alcatraz Deobfuscator IDA Script [~]
[+] Emulating Function Name: start, Address: 14000e2ba
[+] Found Original Entry Point: 0x140001340
```

Hedef adrese ait fonksiyonun ismini de `original_entry_point` olarak değiştirdiğimiz için `Jump to Address (G)` yapmak yerine direkt Functions sekmesinde bu fonksiyon ismiyle arama yaptığımızda gerçek entry-point noktasını görebiliyoruz.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1744550480096/7f77b026-ac83-4134-9f41-24770097178c.png align="center")

## LEA Obfuscation

```apache
lea rdx, cs:7FF7D996FE33h
pushf
sub rdx, 421FDBD3h
popf
lea rcx, cs:7FF7FD145F19h
pushf
sub rcx, 659D3CA9h
popf
call loc_7FF797778E4A
```

[Load Effective Address (LEA)](https://www.felixcloutier.com/x86/lea) komutu hedef adresi belirlenen register’a yükleme işlemini gerçekleştiriyor. pushf ve popf ile sarmalanmış sub komutu ise yüklenen rdx register’ına yüklenen adresten belirli bir değeri çıkartarak gerçek adresi açığa çıkartıyor.

> pushf ve popf ile sarmalanmasının sebebi CPU üzerindeki RFLAGS’lerin sub işleminden etkilenmemesini sağlamak. pushf ile RFLAGS hafızaya yüklenir ve popf ile korunan RFLAGS geri yüklenir.

Dolayısıyla yukardaki işlemin gösterimi:

$$RDX = 0x7FF7D996FE33 - 0x421FDBD3$$

$$RCX = 0x7FF7FD145F19 - 0x659D3CA9$$

Alcatraz Codebase içerisinde [lea.cpp](https://github.com/weak1337/Alcatraz/blob/master/Alcatraz/obfuscator/passes/lea.cpp) dosyası içerisinde de bunu nasıl hesapladığını görebilirsiniz:

```cpp
auto rand_add_val = distribution(generator);
instruction->location_of_data += rand_add_val;
assm.pushf();
assm.sub(x86_register_map->second, rand_add_val);
assm.popf();
void* fn;
auto err = rt.add(&fn, &code);
```

Debugger üzerindeki gösterimi:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1744565729999/35d597a6-ee8f-484f-b418-38096d9b8860.png align="center")

## Anti-Disassembly

[Buradaki açıklamaya](https://github.com/weak1337/Alcatraz?tab=readme-ov-file#anti-disassembly) göre `0xFF` ile başlayan talimatı `0xEB 0xFF` haline çevirmesi **lineer disassembler**’ları şaşırtıyor. Buradaki tekniği (Impossible Disassembly) ve diğer anti-\* teknikleri farklı bir blog yazısında anlatmayı planladığım için burada detayına girmeyeceğim. Kaynak kodu üzerinden bunu nasıl yaptığını açıkça görebiliyoruz.

```cpp
bool obfuscator::obfuscate_ff(std::vector<obfuscator::function_t>::iterator& function, std::vector<obfuscator::instruction_t>::iterator& instruction) {

	instruction_t conditional_jmp{}; conditional_jmp.load(function->func_id, { 0xEB });
	conditional_jmp.isjmpcall = false;
	conditional_jmp.has_relative = false;
	instruction = function->instructions.insert(instruction, conditional_jmp);
	instruction++;

	return true;
}
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1745424051069/ce554a5c-6615-4ec1-99d5-8004a0c1b6ba.png align="center")

## MOV & ADD Obfuscation

LEA obfuscation’da bahsettiğim gibi pushf ve popf ile sarmalanmış birtakım işlemleri burada da yapıyor.

```cpp
mov     edx, 0AA045890h
pushf
not     edx
add     edx, 0E6CAF175h
xor     edx, 0B7625898h
rol     edx, 0C4h
popf
pushf
not     edx
add     edx, 811E9B28h
xor     edx, 0C6E2935Fh
rol     edx, 0Fh
popf
```

## Control Flow Obfuscation

Fonksiyon içerisindeki normal akışı yeniden oluşturulan bloklar içerisine yerleştirerek akış okumayı zorlaştırma işlemi gerçekleştiren kısım. Main fonksiyonun normal akışı bu iken:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1745428515891/941aee50-ee12-4bc5-9ba6-65ad97f51d72.png align="center")

Buna çeviriyor (graph view):

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1745428538007/e64359a8-5af6-4720-99bf-7cc6d861db5a.png align="center")

Pseudo-Code gösterimi:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1745428621706/d148986b-3034-41a2-9d58-9ca8cf6b76fa.png align="center")

# Writing MOV Calculator Script with IDAPython

3.4 başlığında bahsettiğim MOV Obfuscation işlemine yönelik bir hesaplayıcı yazmak gerekiyor. Bu script’te kullanılacak olan fonksiyonlar Control Flow Unflattening işlemi için de gerekli olacak.

Scriptin main fonksiyonunda IDA üzerinde seçilen adres alınarak `analyze_mov_obfuscation` fonksiyonu çağırılıyor.

```python
def main():
   print("[~] 0xReverse - Alcatraz Deobfuscator IDA Script [~]")
   # Select a mutated function on IDA Screen
   start_address = idaapi.get_screen_ea()
   if selected_function := ida_funcs.get_func(start_address):
      function_name = idc.get_func_name(start_address)
      print(f"[+] Analyzing Function Name: {function_name}, Address: {start_address:x}")
      deobfuscated_mov_ops = analyze_mov_obfuscation(func=selected_function)
      print(f"[+] Deobfuscated {deobfuscated_mov_ops} MOV obfuscation")
   else:
        print(f"[-] This address is not a function")
        exit(-1)
```

`analyze_mov_obfuscation` fonksiyonu, kısaca hedef fonksiyon içerisinde dolaşıp aşağıdaki pattern’i bulduktan sonra gerekli değeri hesaplıyor.

```c
pattern:
{
	mov     eax, VALUE
	pushf
	not     eax
	add     eax, VALUE
	xor     eax, VALUE
	rol     eax, VALUE
	popf
}
```

analyze\_mov\_obfuscation fonksiyonunu yazmadan önce ROL, ROR ve pushf&popf ile sarmalanmış operasyonları gerçekleştiren fonksiyonları da script içerisine eklemek gerekiyor:

```python
rol = lambda val, r_bits, max_bits: ((val << (r_bits % max_bits)) & (2**max_bits - 1)) | ((val & (2**max_bits - 1)) >> (max_bits - (r_bits % max_bits)))
ror = lambda val, r_bits, max_bits: ((val & (2**max_bits - 1)) >> (r_bits % max_bits)) | ((val << (max_bits - (r_bits % max_bits))) & (2**max_bits - 1))
MASK = 0xFFFFFFFF

def calculate_mov_value(init_value, add_value, xor_value, rol_value):
   # mov     eax, VALUE
   calculated_value = init_value
   # not     eax         -> bitwise NOT
   calculated_value = (~calculated_value) & MASK
   # add     eax, VALUE
   calculated_value = (calculated_value + add_value) & MASK
   # xor     eax, VALUE
   calculated_value = calculated_value ^ xor_value
   # rol     eax, VALUE
   calculated_value = rol(calculated_value, rol_value, 32)
   return calculated_value
```

Bu pattern için sadece baştaki mov ve pushf değerlerini kontrol etmek yeterli. Bu yüzden current\_address değerinin bulunduğu instruction ve bir sonraki instruction’u kontrol ediyoruz:

```python
# Get function start address
current_address = func.start_ea
while current_address < func.end_ea:
  instruction = idautils.DecodeInstruction(current_address)
  if not instruction:
	 break
   
  if instruction.itype == idaapi.NN_mov:
	 # Check if the next opcode is pushf
	 next_instruction = idautils.DecodeInstruction(current_address + instruction.size)
	 if next_instruction.itype == idaapi.NN_pushf:
		print("[+] MOV Obfuscation pattern found!")
		...
	 else:
		current_address += instruction.size
  else:
	 current_address += instruction.size
```

Initial adres bir register’a atıldıktan sonra özel hesaplama işleminin kaç kere yapıldığı random şekilde seçildiğinden dolayı bu pattern’i bir kere hesaplayamıyoruz. Bu yüzden yine bir döngü içerisinde belirli opcode’lar dışında farklı bir opcode gelene kadar devam etmek gerekiyor. Bu işlemin 3 kere yapılan bir örneği de aynı fonksiyon içerisinde bulunuyor:

```c
mov     ecx, 5600B3EBh
pushf
not     ecx
add     ecx, 84DD6D12h
xor     ecx, 84F2EEE7h
rol     ecx, 2Bh
popf
pushf
not     ecx
add     ecx, 0B4156550h
xor     ecx, 0B460F07Dh
rol     ecx, 5Bh
popf
pushf
not     ecx
add     ecx, 0E02D13C8h
xor     ecx, 0C483568Bh
rol     ecx, 66h
popf
```

Script içerisinde hesaplama işlemini de bu şekilde yapıyoruz:

```python
calculated_value = 0
add_value = xor_value = rol_value = 0
# Get value from {mov eax, VALUE} and MASK for 32bit
initial_value = instruction.ops[1].value & MASK
# Add current_address and mov and pushf
current_address += instruction.size + next_instruction.size
while True:
   current_instruction = idautils.DecodeInstruction(current_address)
   if current_instruction.itype == idaapi.NN_not:
	  current_address += current_instruction.size
   elif current_instruction.itype == idaapi.NN_add:
	  add_value = current_instruction.ops[1].value & MASK
	  current_address += current_instruction.size
   elif current_instruction.itype == idaapi.NN_xor:
	  xor_value = current_instruction.ops[1].value & MASK
	  current_address += current_instruction.size 
   elif current_instruction.itype == idaapi.NN_rol:
	  rol_value = current_instruction.ops[1].value & MASK
	  current_address += current_instruction.size
   elif current_instruction.itype == idaapi.NN_popf:
	  calculated_value = calculate_mov_value(
		 init_value=initial_value,
		 add_value=add_value,
		 xor_value=xor_value,
		 rol_value=rol_value
	  )
	  current_address += current_instruction.size
   elif current_instruction.itype == idaapi.NN_pushf:
	  # The pattern we are looking for can be repetitive,
	  # so we need to keep the process going.
	  initial_value = calculated_value
	  current_address += current_instruction.size
   else:
	  break
print(f"[+] Calculated MOV value: {hex(calculated_value)}")
```

[Github Repo](https://github.com/0xReverse/idascripts/tree/main/alcatraz)

# YARA Rule

```c
import "pe"

rule SUSP_EXE_Alcatraz_Obfuscator_April_23 { 
    meta:
        description = "This rule detects samples obfuscated with Alcatraz."
        author      = "Utku Corbaci (rhotav) / 0xReverse"
        date        = "2025-04-23"
        sharing     = "TLP:CLEAR"
        tags        = "windows,exe,suspicious,obfuscator"
        os          = "Windows"

    strings:
        // B8 41 CD A8 27   mov     eax, 27A8CD41h
        // 66 9C            pushf
        // F7 D0            not     eax
        // 05 AB FD E1 DD   add     eax, 0DDE1FDABh
        // 35 CA 3C 0F BF   xor     eax, 0BF0F3CCAh
        // C1 C0 62         rol     eax, 62h
        // 66 9D            popf
        $obfuscation_mov = {B8 ?? ?? ?? ?? 66 9C F7 D0 05 ?? ?? ?? ?? 35 ?? ?? ?? ?? C1 C0 ?? 66 9D}

        // 48 8D 05 74 81 47 77	    lea     rax, cs:1B748621Eh
        // 66 9C               	    pushf
        // 48 2D A6 2B 48 77   	    sub     rax, 77482BA6h
        // 66 9D               	    popf
        $obfuscation_lea = {48 8D ?? ?? ?? ?? ?? 66 9C 48 2D ?? ?? ?? ?? 66 9D}
    condition: 
        pe.is_pe
        and for any i in (0..pe.number_of_sections - 1): (
		    (pe.sections[i].name == ".0Dev")
        )
        and (all of ($obfuscation_*))
}
```

[unpac.me Hunting Results](https://www.unpac.me/yara/hunt/results/3886a8a9-997c-4b71-b65b-e9000e581aa7)

# References

1. [https://keowu.re/posts/Analyzing-Mutation-Coded-VM-Protect-and-Alcatraz-English](https://keowu.re/posts/Analyzing-Mutation-Coded-VM-Protect-and-Alcatraz-English)
    
2. [https://gist.github.com/icecr4ck/7a7af3277787c794c66965517199fc9c](https://gist.github.com/icecr4ck/7a7af3277787c794c66965517199fc9c)
    
3. [https://python.docs.hex-rays.com/](https://python.docs.hex-rays.com/)
    
4. [https://grazfather.github.io/posts/2016-09-18-anti-disassembly/](https://grazfather.github.io/posts/2016-09-18-anti-disassembly/)
    
5. [https://gist.github.com/trietptm/5cd60ed6add5adad6a34098ce255949a](https://gist.github.com/trietptm/5cd60ed6add5adad6a34098ce255949a)
    
6. [https://phrack.org/issues/71/15](https://phrack.org/issues/71/15)
