# SEETF_2023
Tải file [tại đây.](https://github.com/Okoonzz/07.-Rev/tree/File/SEETCTF2023)
## DECOMPILE-ME
> Đề bài:
> 
Đối với thử thách này, khi tải về nhận được một file định dạng `.pyc` và một file `output`. Có thể tham khảo thêm file với đuôi `.pyc` là gì [tại đây.](https://www.tutorialspoint.com/What-are-pyc-files-in-Python#:~:text=pyc%20files%20are%20compiled%20bytecode,time%20the%20script%20is%20run.)
Sau khi xác định được hết tất cả, công việc còn lại chỉ cần chuyển được file `.pyc` về mã nguồn ban đầu của nó là `.py`.
Đây là cách mình đã chuyển về lại file `.py` ban đầu: `$ uncompyle6 -o . decompile-me.pyc`
Sau khi có được file python ban đầu, đọc source thấy được rằng đoạn code chỉ dùng các phép toán xor đơn giản để mã hóa flag ra file `output.txt`.
Đây là script để hoàn thành thử thách này:
```python
from pwn import xor
with open (r"output.txt", "rb") as file:
enc = file.read()
print (enc)
a = enc[0:len(enc) // 3]
b = enc[len(enc) // 3:2 * len(enc) // 3]
c = enc[2 * len(enc) // 3:]
c = xor(c, int(str(len(enc))[0]) * int(str(len(enc))[1]))
c = xor(b, c)
b = xor(a, b)
a = xor(c, a)
c = xor(b, c)
b = xor(a, b)
a = xor(a, int(str(len(enc))[0]) + int(str(len(enc))[1]))
flag = a + b + c
print(flag.decode('ASCII'))
#flag: SEE{s1mP4l_D3c0mp1l3r_XDXD}
```
## DATA STRUCTURES AND ALGORITHMS
> Đề bài:
> 
Lúc mới nhận được thử thách thì khá hoảng bởi vì DSA là nỗi khiếp sợ của rất nhiều người =)))
Khi có được nội dung của thử thách, mình đã chạy thử file của thử thách này thì nhận thấy được như sau:

Rất nhiều "tree" ở đây nhưng thứ đập vào mắt mình đầu tiên đó chính là "expression tree". Nếu ai chưa biết loại cây này là gì có thể tham khảo thêm [tại đây.](https://www.geeksforgeeks.org/expression-tree/)
Tiến hành decompile file này ta sẽ thấy được như sau:

Có thể dễ dàng nhìn thấy được rằng cây này đang được duyệt theo kiểu postfix. Việc cần làm bây giờ chính là lấy được những biểu thức này ra và tiến hành tính toán nó.
Vì mình khá kém trong việc thao tác code dữ liệu lớn nên mình đã đưa ra ý tưởng cũng như code cơ bản trước cho [m3r1t168](https://github.com/m3r1t168) (teammate của mình) sau đó cả hai cùng hoàn thiện được đoạn lấy giá trị như sau:
```python
with open("dump.txt", "r") as file:
result = file.read()
numbers = []
for line in result.split("\n"):
if line.startswith(" sub_14000F050"):
number = line.split('"')[1] + " "
numbers.append(number)
if line.startswith(" v198 = *(_OWORD *)"):
result = "".join(numbers)
print(result)
# print("\n")
result = ""
numbers = []
# print(result)
```
Sau khi có được các biểu thức postfix nhập từng biểu thức vào tool https://devonsmith.github.io/cs460/hw2/demo/ sẽ trả về được các kết quả.
Cuối cùng gộp các kết quả lại sẽ có được flag
```python
res = [83,69,69,123,53,119,49,110,54,49,110,54,95,55,104,114,48,117,54,104,95,55,104,51,95,55,114,51,51,53,95,53,49,101,55,50,101,55,102,51,57,56,97,52,102,98,48,101,51,98,56,99,103,56,52,53,55,49,54,55,53,53,50,125]
flaglist = [chr(num) for num in res]
flag = ''.join(flaglist)
print(flag)
# flag: SEE{5w1n61n6_7hr0u6h_7h3_7r335_51e72e7f398a4fb0e3b8cg8457167552}
```
## WOODCHECKER
> Đề bài:
> 
Đối với bài này, khi nhận được file gồm một file `.wpk` và một file `.py` thì sai lầm của mình là quá tập trung vào xem thử file `.wpk` nó là gì. Liệu rằng có cách nào hay tool nào để thực hiện nó không. Bởi vì mình thấy khá nhiều người giải được nên cứ nghĩ rằng chắc chắn có tool. Nhưng không hề. Rồi sau khi hết cách thì quay trở lại đọc source code :)))
Khi đọc source code, điều tiếp theo làm khá rối đó là quá nhiều phép dịch bit cũng cũng như là các phép toán.
```python
class CPU:
def __init__(self):
self.mem = bytearray(1 << 29)
self.addr = 0
self.store = 0
def execute(self, instr):
match instr.strip().upper():
case 'INC': self.addr += 1
case 'INV': self.mem[self.addr // 8] ^= 1 << (self.addr % 8)
case 'LOAD': self.store = self.mem[self.addr // 8] >> (self.addr % 8) & 1
case 'CDEC': self.addr -= self.store
case other: raise ValueError(f'Unknown instruction "{other}"')
```
Tạm thời bỏ qua các ý nghĩa của `CPU`, đọc xuống hàm main yêu cầu rằng độ dài của flag phải là 20 ký tự sau khi nhập đúng thì nó sẽ thực thi CPU và cuối cùng là kiểm tra.
Ở đoạn thực thi `CPU` có thể đoán và hình dung được rằng nó như sau:
- Đầu tiên có được một array byte còn mem ở đây chính là từng bit của phần tử thứ idx của flag, sau đó là địa chỉ cũng như lưu trữ chúng lại.
- Sau đó nó sẽ thực thi những dòng lệnh đã được đặt sẵn trong file `.wpk`. Nếu như gặp `INC` thì tăng địa chỉ lên, nếu gặp `LOAD` thì sẽ tính toán store.
Thấy được rằng lúc kiểm tra cuối cùng thì chỉ kiểm tra `cpu.store` nên mình thử in ra thử xem `cpu.store` này là làm cụ thể cái gì và từng số từng chữ như nào.
```python
class CPU:
def __init__(self):
self.mem = bytearray(1 << 29)
self.addr = 0
self.store = 0
def execute(self, instr):
match instr.strip().upper():
case 'INC':
self.addr += 1
case 'INV':
self.mem[self.addr // 8] ^= 1 << (self.addr % 8)
case 'LOAD':
self.store = self.mem[self.addr // 8] >> (self.addr % 8) & 1
print(f"store mem[{self.addr//8}] >> {self.addr % 8} & 1: {self.store}")
case 'CDEC':
self.addr -= self.store
case other: raise ValueError(f'Unknown instruction "{other}"')
if __name__ == '__main__':
flag = input('Enter flag: ').encode('ascii')
#aaaaaaaaaaaaaaaaaaa}
assert len(flag) == 20, 'Incorrect length'
cpu = CPU()
cpu.mem[:len(flag)] = flag
for instr in open("woodchecker.wpk").readlines():
cpu.execute(instr)
print('Correct!' if cpu.store else 'Better luck next time')
```
Và đây chính là kết quả trả về được:
```
store mem[20] >> 0 & 1: 1
store mem[0] >> 0 & 1: 1
...
store mem[20] >> 0 & 1: 1
store mem[0] >> 1 & 1: 1
...
store mem[20] >> 0 & 1: 1
store mem[19] >> 7 & 1: 1
store mem[19] >> 6 & 1: 1
store mem[19] >> 5 & 1: 1
store mem[19] >> 4 & 1: 1
store mem[19] >> 3 & 1: 1
store mem[19] >> 2 & 1: 1
store mem[19] >> 1 & 1: 1
store mem[19] >> 0 & 1: 1
store mem[18] >> 5 & 1: 0
store mem[18] >> 5 & 1: 0
...
store mem[18] >> 5 & 1: 0
Better luck next time
```
Nhìn vào kết quả trả về thấy được rằng lưu từ mem[0] nên mem[20] mình sẽ không bận tâm đến vì nó lưu từ 0-19. Thấy được tiếp theo có một dãy 8 dòng của `mem[19] = "}"` thì được lưu là 1 hết tất cả, và sau đó là một dãy các `mem[18] = "a"` được lưu là 1.
Có một câu hỏi đặt ra lúc này là tại sao nếu kiểm tra `cpu.store` nó chỉ cần kết quả tra ra là 1 thì nó sẽ đúng, nhưng đây lại trả về nhánh sai?
Kết hợp với output debug cho thấy rằng chương trình này kiểm tra từng bit của từng char của flag. Tức là 1 chữ được biểu diễn bởi 8bit nếu như đúng nó sẽ là một chuỗi 8bit đều là 1 ở store và sẽ kiểm tra đến char tiếp theo. Và đặc biệt hơn nó sẽ kiểm tra từ đầu đến cuối của flag chứ không phải từ cuối lên đầu. Tức là ký tự đứng trước ký tự sau cùng mà sai thì dẫn đến hơn 8bit đều biểu diễn ở store là 0 nên sẽ dẫn đến sai.
> Để dễ hình dung hơn ta hiểu như sau:
>
> Khi nhập vào flag, các phép toán được thực hiện và tiến hành kiểm tra từ ký tự "}" trở về trước ký tự "S". Nếu đúng thì 8bit vừa kiểm tra (bit của ký tự "}") sẽ được đẩy lên và tiếp tục thực hiện 8bit tiếp theo cứ thế mà đẩy lên dần đến khi `store mem[0] >> {7-0} & 1: 1` (bit của ký tự "S") thì sẽ đúng hết kết quả, nếu sai một ký tự nào đó thì sẽ có hơn 8bit của 1 mem biểu diễn là 0 và dẫn đến sai.
Sau khi phân tích tất cả, giải quyết bài này chỉ còn cách brute từng chữ. Nhưng vì trong giải lúc này đoạn hiểu được cách này nó hoạt động như thế nào quá mất thời gian và đau đầu nên mình đã đưa ý tưởng code cho [m3r1t168](https://github.com/m3r1t168) (teammate) để viết script.
> Lúc tự viết script vì mình không thể nghĩ gì được nữa nên đã stuck, không biết làm sao để kiểm tra 8 bit của mỗi chữ là 1 :<<
> Mục đích của script này là brute từng chữ nhưng brute đúng theo nghĩa đen :))) vì chưa nghĩ tới phương án tự động hết tất cả. Mục đích của nó là cứ tìm từng chữ trong printable, nếu chữ đó có 8bit cuối cùng đúng hết là 1 thì lưu lại và ghi ngược lại vào pattern của flag và tìm chữ tiếp theo :))
```python
import string
arr = []
class CPU:
def __init__(self):
self.mem = bytearray(1 << 29)
self.addr = 0
self.store = 0
def execute(self, instr):
match instr.strip().upper():
case 'INC': self.addr += 1
case 'INV':
self.mem[self.addr // 8] ^= 1 << (self.addr % 8)
case 'LOAD':
self.store = self.mem[self.addr // 8] >> (self.addr % 8) & 1
# Thay thế 18 -> 0 với mỗi lần tìm chữ mới
if (self.addr // 8) == 18:
arr.append(self.store)
case 'CDEC': self.addr -= self.store
case other: raise ValueError(f'Unknown instruction "{other}"')
if __name__ == '__main__':
for chr in string.printable:
#Mỗi lần tìm được chữ thì xóa một ký tự đi và thêm vào sau đó ký tự vừa tìm được
tmp = "aaaaaaaaaaaaaaaaaa" + chr + "}"
flag = tmp.encode("ASCII")
cpu = CPU()
cpu.mem[:len(flag)] = flag
for instr in open(r"D:\CTFZONE\SeetF\woodchecker.wpk").readlines():
cpu.execute(instr)
if all (arr[-8:-1]) == 1:
print(f"P_Flag: {chr}")
#Flag: SEE{pIcKyP1CIF0rmeS}
```