PWN出题小记
记录一下 PWN 出题的源码、环境部署。
PWN题环境部署
一把梭
图方便的话可以使用 pwn_deploy_chroot
这个项目。
自己编写dockerfile
也可以自己写 dockerfile 来拉取镜像。
将题目名命名为 pwn,与 Dockerfile、ctf.xinetd、start.sh
三个文件放在同一目录下,下面提供相应配置文件的模板。
Dockerfile
FROM ubuntu:16.04
# 此处可以修改版本 直接修改版本号即可改变拉取的镜像版本 这里会直接到dockerhub拉取相应的镜像
# FROM ubuntu:22.04
RUN sed -i "s/http:\/\/archive.ubuntu.com/http:\/\/mirrors.tuna.tsinghua.edu.cn/g" /etc/apt/sources.list && \
apt-get update && apt-get -y dist-upgrade && \
apt-get install -y lib32z1 xinetd
RUN useradd -m ctf
WORKDIR /home/ctf
RUN cp -R /lib* /home/ctf && \
cp -R /usr/lib* /home/ctf
# 使用 ubuntu 20.04 及以上版本的 libc 使用下面指令
# RUN cp -R /usr/lib* /home/ctf
RUN mkdir /home/ctf/bin && \
cp /bin/sh /home/ctf/bin && \
cp /bin/ls /home/ctf/bin && \
cp /bin/cat /home/ctf/bin
COPY ./ctf.xinetd /etc/xinetd.d/ctf
COPY ./start.sh /start.sh
RUN echo "Blocked by ctf_xinetd" > /etc/banner_fail
RUN chmod +x /start.sh
COPY ./pwn /home/ctf/
RUN chown -R root:ctf /home/ctf && \
chmod -R 750 /home/ctf
CMD ["/start.sh"]
EXPOSE 9999
ctf.xinetd
service ctf
{
disable = no
socket_type = stream
protocol = tcp
wait = no
user = root
type = UNLISTED
port = 9999
bind = 0.0.0.0
server = /usr/sbin/chroot
server_args = --userspec=1000:1000 /home/ctf ./pwn
banner_fail = /etc/banner_fail
# safety options
per_source = 10 # the maximum instances of this service per source IP address
rlimit_cpu = 20 # the maximum number of CPU seconds that the service may use
#rlimit_as = 1024M # the Address Space resource limit for the service
#access_times = 2:00-9:00 12:00-24:00
}
start.sh
#!/bin/sh
# Add your startup script
export FLAG="flag{c6f3396244adadd3c53c49cf13ca864e}"
echo $FLAG > /home/ctf/flag
chown root:ctf /home/ctf/flag
chmod 740 /home/ctf/flag
# 清环境变量
export FLAG=
# DO NOT DELETE
/etc/init.d/xinetd start;
sleep infinity;
如果需要设置动态 flag 的话,这里提供一种实现思路,首先肯定是需要靶场进行配合的,在拉镜像生成容器的时候,平台生成并记录一个随机数,传递随机数到容器的环境变量,然后通过环境变量写容器里的 flag 文件。
要设置 random 值为 flag,需要删除上面的 start.sh
中对 FLAG
赋值的 export FLAG="xx"
,并修改起容器的命令。
$ sudo docker run -itd --name [CONTAINER NAME] -p [PORT]:9999 -e FLAG=random [IMAGE ID]
写好这三个文件后,在当前文件夹路径下执行相关命令,拉镜像,起容器。
$ sudo docker build -t <image_name>:<tag> . #拉取题目镜像
$ sudo docker images #获取镜像id
$ sudo docker run -id --name [CONTAINER NAME] -p [PORT]:9999 [IMAGE ID] #设置使用IMAGE ID镜像、生成的容器的容器名为CONTAINER NAME、将容器的9999端口映射主机的PORT端口
$ sudo docker ps -a #查看所有容器名
$ sudo docker ps #查看正在运行的容器名
$ sudo docker rm -f [CONTAINER NAME] #删容器
$ sudo docker images #获取镜像id
$ sudo docker rmi [IMAGE ID] #删镜像
$ sudo docker start [CONTAINER ID] #重启容器
load&save image
打包镜像,加载镜像,起容器。
「PS」
一、请自定义命令行里的 [httpd]。
二、注意 -t 会指定镜像名和标签,这样生成的 tar 包将会对镜像名有所限制。如果指定镜像名来 build 一个题并 save 为 tar 文件,后续若要对题目做修改,build 时需要指定不同的镜像名,或者在 load 新 tar 文件之前,先把名字冲突了的镜像删掉。
$ sudo docker build -t httpd:pwn .
$ sudo docker ps -a
$ sudo docker save -o httpd.tar httpd:pwn
$ sudo chmod 777 httpd.tar
$ sudo docker load -i httpd.tar
$ sudo docker images
$ sudo docker run -id --name httpd -p [PORT]:9999 [IMAGE ID]
测试docker环境
进入对应容器的 shell。
$ sudo docker exec -it [CONTAINER NAME] /bin/bash
docker-compose
很显然,通过上述方法来生成容器的话会有些繁琐,docker-compose 这个工具就十分方便,它是用于定义和运行多容器 Docker 应用程序的工具。通过 docker-compose,可以使用 yml 文件来配置应用程序需要的所有服务。然后,使用一个简短的命令,就可以从 yml 文件配置中创建并启动所有服务。docker-compose 默认的配置文件为 docker-compose.yml,其用 YAML 语言编写。
YAML 的语法和其他高级语言类似,并且可以简单表达清单、散列表,标量等数据形态。它使用空白符号缩进和大量依赖外观的特色,特别适合用来表达或编辑数据结构、各种配置文件、倾印调试内容、文件大纲(例如:许多电子邮件标题格式和 YAML 语法非常接近)。
docker-compose.yml 文件如下,也是与上述文件放在同一文件夹下。image_name 是生成的镜像名,PORT 是容器的外部端口。
version: "2"
services:
pwn:
build: .
image: <image_name>
restart: unless-stopped
ports:
- "[PORT]:9999"
然后启动服务。
$ sudo docker-compose up -d
若是直接安装 docker-compose
,可能会遇到 docker-compose
和 docker
版本不适配的问题,报错如下。
TypeError: kwargs_from_env() got an unexpected keyword argument 'ssl_version'
分别重新安装指定版本。
$ sudo pip uninstall docker-compose
$ sudo pip install pyyaml==5.3.1
$ sudo pip install docker-compose
$ sudo pip uninstall docker
$ sudo pip install docker==6.1.3
PWN出题
编译时设置 elf 文件的保护机制:
NX:-z execstack / -z noexecstack
(关闭 / 开启) 栈上数据不可执行。
Canary:-fno-stack-protector /-fstack-protector / -fstack-protector-all
(关闭 / 开启 / 全开启) 栈里插入 canary。
PIE:-no-pie / -pie
(关闭 / 开启) 地址随机化。
RELRO:-z now / -z lazy / -z norelro
(关闭 / 部分开启 / 完全开启) 对GOT表的写权限。
常见报错:
/usr/bin/ld: /tmp/ccMFw2CH.o: relocation R_X86_64_32 against `.rodata' can not be used when making a shared object; recompile with -fPIC
解决方法:在 gcc 编译时多添加一个参数 -fPIC
。
放些基础题的源码。
ret2text。
#include <stdio.h>
#include <stdlib.h>
void init()
{
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stderr, NULL, _IONBF, 0);
}
void func(void)
{
char buf[40];
read(0, buf, 60);
return 0;
}
int main(void)
{
init();
puts("welcome to ctf!");
func();
return 0;
}
void backdoor(void)
{
system("/bin/sh");
}
shellcode。
#include <stdio.h>
#include <unistd.h>
// char code[] = "\x48\x31\xd2\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x48\xc1\xeb\x08\x53\x48\x89\xe7\x50\x57\x48\x89\xe6\xb0\x3b\x0f\x05";
void init()
{
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stderr, NULL, _IONBF, 0);
}
int main()
{
init();
char code[100];
puts("execute: ");
read(0, code, 0x20);
(*(void (*)())code)();
return 0;
}
关于堆的菜单题。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define PAPER_CNT (32)
struct paper_mgr
{
char paper_time[48];
char paper_content[32];
};
struct paper_mgr **array;
void backdoor()
{
puts("Aha! Rock on!");
system("/bin/sh");
}
void init()
{
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
alarm(60); // 1 minute timeout
array = (struct paper_mgr **)malloc(PAPER_CNT * sizeof(struct paper_mgr *));
memset(array, 0, (PAPER_CNT * sizeof(struct paper_mgr *)));
}
void banner()
{
printf("Welcome.\n");
printf("\n");
}
void menu()
{
puts("You have following choices:\n [1]: add a paper\n [2]: show a paper\n [3]: edit a paper\n [4]: finish a paper\n [5]: exit\nYour chocie:");
}
void get_input_custom(char *ptr, int len)
{
if (!len)
return;
read(0, ptr, len);
}
void add_paper()
{
int i;
struct paper_mgr *paper_ptr;
for (i = 0; i < PAPER_CNT; i++)
if (!array[i])
break;
if (i == PAPER_CNT)
{
puts("paper manager is full :(");
return;
}
paper_ptr = malloc(sizeof(struct paper_mgr));
printf("creating paper with index-%d\n", i + 1);
puts("please input the paper time");
get_input_custom(paper_ptr->paper_time, 48);
puts("please input the paper content");
get_input_custom(paper_ptr->paper_content, 1024);
puts("done");
array[i] = paper_ptr;
}
void finish_paper()
{
int index;
puts("please input the paper index");
scanf("%d", &index);
index = index - 1;
if (0 <= index && index < PAPER_CNT)
{
if (array[index])
{
free(array[index]);
puts("done");
return;
}
}
puts("invalid paper index");
}
void show_paper()
{
int index;
puts("please input the paper index");
scanf("%d", &index);
index = index - 1;
if (0 <= index && index < PAPER_CNT)
{
if (array[index])
{
printf("paper time: %s\n", array[index]->paper_time);
printf("paper content: %s\n", array[index]->paper_content);
puts("done");
return;
}
}
puts("invalid paper index");
}
void edit_paper()
{
int index;
puts("please input the paper index");
scanf("%d", &index);
index = index - 1;
if (0 <= index && index < PAPER_CNT)
{
if (array[index])
{
struct paper_mgr *paper_ptr = array[index];
puts("please input the new paper time");
get_input_custom(paper_ptr->paper_time, 48);
puts("please input the new paper content");
get_input_custom(paper_ptr->paper_content, 1024);
puts("done");
return;
}
}
puts("invalid paper index");
}
int main(int argc, char *argv[])
{
int choice = 0;
init();
banner();
while (1)
{
menu();
scanf("%d", &choice);
switch (choice)
{
case 1:
add_paper();
break;
case 2:
show_paper();
break;
case 3:
edit_paper();
break;
case 4:
finish_paper();
break;
case 5:
puts("Bye!");
exit(0);
default:
puts("Wrong!");
break;
}
}
}
fmt、栈迁移。
这篇文章有介绍如何给 pwn 题布置沙箱。