PWN出題小記

ve1kcon發表於2024-04-11

記錄一下 PWN 出題的原始碼、環境部署。

PWN題環境部署

圖方便的話可以使用 pwn_deploy_chroot 這個專案。

如何安全快速地部署多道ctf pwn比賽題目

也可以自己寫 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]					#重啟容器

進入對應容器的 shell。

$ sudo docker exec -it [CONTAINER NAME] /bin/bash

很顯然,透過上述方法來生成容器的話會有些繁瑣,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"

然後啟動服務。

$ docker-compose up -d

PWN出題

編譯時設定 elf 檔案的保護機制:

NX:-z execstack / -z noexecstack (關閉 / 開啟) 棧上資料不可執行。

Canary:-fno-stack-protector /-fstack-protector / -fstack-protector-all (關閉 / 開啟 / 全開啟) 棧裡插入 canary。

PIE:-no-pie / -pie (關閉 / 開啟) 地址隨機化。

RELRO:-z norelro / -z lazy / -z now (關閉 / 部分開啟 / 完全開啟) 對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、棧遷移。

SCUCTF 2020新生賽 PWN部分出題筆記-Pwn

這篇文章有介紹如何給 pwn 題佈置沙箱。

Seccomp從0到1

相關文章