---
# System prepended metadata

title: CrewCTF - Misc - pyf❤️ck

---

# CrewCTF - Misc - pyf❤️ck

## Tổng quan đề bài
- Đề bài cho phép ta thực thi python code thông qua một sandbox, trong đó ta chỉ được sử dụng các ký tự: `a-z`,`()`, và tối đa 12 ký tự `+`.
- Các hàm cũng được sandbox, ta chỉ được phép sử dụng: `next`, `chr`, `ord`, `max`, `min`, `bin`, `int`, `len`, `str`, `set`, `hex`, `print`, `range`, `open`.
- Vì không được sử dụng `.` hay `[]`, việc ta xâu chuỗi đến các thuộc tính nội gần như là bất khả thi.
- Đề bài không cho phép viết trực tiếp `print`.
## Hướng tiếp cận
### Khôi phục lại chuỗi `'Flag.txt'
- Đầu tiên, một điều rõ ràng là ta phải sử dụng `open('Flag.txt')` để đọc file flag.
- Tuy nhiên ta không thể viết trực tiếp xâu `'Flag.txt'` vì không có các ký tự `'` hay `"`.
- vậy ta phải sử dụng các hàm để xây dựng chuỗi.
- Với các hàm được cung cấp, việc cắt đoạn chuỗi dường như là không thể, vì cậy khả dĩ nhất là ta đi xây dựng từng chữ cái một sau đó nối chúng lại với nhau bằng phép `+`.
- Ta tốn 7 phép `+` cho việc nối chuỗi, vậy quá trình xây dựng các ký tự tổng cộng không được tốn quá 5 phép `+`.
- Tham khảo `https://pywtf.seall.dev/`, ta có các chữ như sau:
        - `F`: `min(str(()in()))`
        - `t`: `max(str(set))`
        - `x`: `max(str(hex))`
Các chữ cái khác việc xây dựng trong pywtf rất phức tạp, quan trọng hơn, rất nhiều phép cộng.
- Ta thấy, đối với các chữ cái khác không phải `min` hay `max` trong các chuỗi khi ta ép kiểu các hàm được cho thành `str`, thì ta sẽ dùng hàm `chr` để thực hiện.
- Cụ thể, ta sẽ dùng hàm `len` và `ord` để có các dữ liệu kiểu số, thực hiện phép `+` với chúng và chuyển ngược lại giá trị kiểu chuỗi.
- Dưới đây là payload của mình cho các chữ cái:
```python
str_map = {
"F": "min(str(()in()))",
'l': "chr(ord(min(str(not(()))))+len(str(open)))",
'a': "chr(ord(min(str(not(()))))+len(str(set)))",
'g': "chr(int(str(len(set(str(str))))+max(str(len(str(set))))))",
'.': "chr(len(str(chr)+str(chr)))",
't': "max(str(set))",
'x': "max(str(hex))",
}
```
Tổng cộng mình đã sử dụng 7 dấu `+` cho việc nối chuỗi, 4 dấu `+` cho việc tạo ký tự, vậy tức mình còn 1 dấu `+` nữa có thể sử dụng trong code về sau.

### Mở file và đọc file
Sau khi tạo được `open('Flag.txt')`, ta không thể dùng `.read()` vì không có ký tự `.`, vậy phải làm sao?
hàm trả về một kiểu `TextIOWrapper`, một kiểu thuộc loại `iterable`. Mà trong các hảm ta dược sử dụng, có các hàm sau có thể tương tác với kiểu `iterable`: 
- `min`, `max`: Lấy giá trị lớn nhất, nhỏ nhất trong các giá trị được duyệt.
- `len`: Lấy số lượng các phần tử trong `iterable`.
- `next`: Lấy phần tử trong `iterable` và đưa con trỏ `iter` tới vị trí tiếp .
- `set`: Biến iterable thành một `set`.
*Note*: *Đối với `print` và `str`, mặc dù nhận vào iterable, nhưng bản chất chỉ là in ra thể hiện của chúng dưới dạng string, không có quá nghĩa với việc quyệt qua một iterable object.*
File flag của ta được trải dài trên nhiều dòng, mỗi dòng 1 ký tự. Vì vậy dùng `next` là không đáng kể. (Bởi `next` trả về giá trị của phần tử tiếp theo chứ không phải là chính `iterable`, vì vậy ta không thể xâu chuỗi nó được).
Trong các hàm trên chỉ có  hàm `set` là đọc được toàn bộ dữ liệu của file. 
:::info
Khi dùng `set` thì thứ tự ban đầu của file bị xáo trộn, và tìm lại hoán vị của chúng cũng không phải là việc dễ dàng.
:::

### Đánh dấu thứ tự ban đầu
Làm sao để giải quyết việc này, chà, nhắc lại một chút, ngoài các hàm cho sẵn, ta còn dùng được các từ khóa sau:`for`, `in`.
Vì có `for in` và dấu `()`, ta có thể xây dựng được một `generator`, mà về bản chất cũng thuộc loại `iterable`.
:::info
Python khi phân tích cú pháp, cụ thể là công đoạn `lexical analysis`, ta chỉ cần đảm bảo các ký tự alphabet không dính liền nhau là python sẽ tự dộng hiểu, không cần phải có ` `(space).
Đơn giản nhất là ta bao các toán hạng của cú pháp ở trong ngoặc đơn là được.
Ví dụ: `for(i)in(range(n))` 
:::
Vậy `for in` thì sao?
Ý tưởng của ta là đánh dấu thứ tự ban đầu, vậy ta cần một biến trạng thái thay đổi theo số lần thực thi, đúng không nào?
Và bằng cách sử dụng `for in`, ta có thể tạo biến!
Và ta chỉ có duy nhất một cách để thay đổi trạng thái của biến: đó là dùng hàm `next`. Tức ta cần một biến cũng kiểu `iterable`.
Vậy ý tưởng của ta có thể trông như thế này:
```python
set(str(set(str(next(i))+j for j in open("Flag.txt")) for i in ...)
```
Ta sử dụng dấu `+` cuối cùng ở dây.

Giờ điền gì trong dấu ... để i là kiểu `iterable` nhỉ?
Chà chà, với for in, nó sẽ duyệt qua các giá trị trong một object `iterable`. Vậy nghĩa là dấu `...` ta phải điền vào đó một object `iterable` có giá trị cũng là một `iterable object`.
Vậy làm sao để tạo một `iterable` có giá trị kiểu `iterable`?
Ta đơn giản dùng lại cú pháp `(for in)` như sau:
```python
((for j in range(100)) for i in range(1))
```
giá trị kiểu iterable (`j`) phải có số đếm lớn, để khi dùng `next` thì không bị cạn kiệt.
Vì không có kiểu số, ta có thể thay thế như sau:
`for i in range(1)` $\Rightarrow$ `for i in str(())`
`for j in range(100)` $\Rightarrow$ `for j in range(ord(i))`

### Xây dựng payload hoàn chỉnh:
Ok, vậy giờ ta có các payload:
`'Flag.txt'` $\Rightarrow$ 
```python
min(str(()in()))+
chr(ord(min(str(not(()))))+len(str(open)))+
chr(ord(min(str(not(()))))+len(str(set)))+
chr(int(str(len(set(str(str))))+
max(str(len(str(set))))))+
chr(len(str(chr)+str(chr)))+
max(str(set))+
max(str(hex))+
max(str(set))
``` 
`[i for i in open('Flag.txt')]` $\Rightarrow$
```python
set(str(next(i))+j for j in open('Flag.txt')) 
```
`i: Iterable` $\Rightarrow$
```python
for i in ((for j in range(ord(i)) for i in str(()))
```
Nối các payload lại với nhau và khử ` ` bằng `()`, ta có payload cuối cùng, lưu ý rằng các for in của ta đều dưới dạng generator, để thực thi chúng thì ta phải đưa chúng vào hàm set:
```python!
set(str(set((str(next(l))+i)for(i)in(open(min(str(()in()))+chr(ord(min(str(not(()))))+len(str(open)))+chr(ord(min(str(not(()))))+len(str(set)))+chr(int(str(len(set(str(str))))+max(str(len(str(set))))))+chr(len(str(chr)+str(chr)))+max(str(set))+max(str(hex))+max(str(set))))))for(l)in(((i)for(i)in(range(ord(j))))for(j)in(str(()))))
```
Độ dài payload: $331/340$ (Phew)

Cuối cùng, ta có một cái kết quả như sau:
```json!
{'0JUNK\\n', '2r\\n', '183\\n', '28e\\n', '9t\\n', '19@\\n', '15_\\n', '13a\\n', '3e\\n', '11f\\n', '22_\\n', '25n\\n', '29}', '12l\\n', '24g\\n', '8s\\n', '6t\\n', '17l\\n', '7e\\n', '1c\\n', '4w\\n', '23i\\n', '16p\\n', '26o\\n', '10_\\n', '14g\\n', '27r\\n', '5{\\n', '21e\\n', '20s\\n'}
```
Dễ thấy các ký tự được đánh số tương ứng với số dòng của chúng, ghép lại, ta có flag như sau (local test flag):
`crew{test_flag_pl3@se_ignore}`

---
Note:
:::info
Hiện tại mình vẫn chưa hiểu ý đổ của tác giả khi để lệnh print vào chuỗi, mong được mọi người góp ý và giải đáp thêm.
:::