Skip to content
122 changes: 119 additions & 3 deletions lab2/main_test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,122 @@
const test = require('node:test');
const assert = require('assert');
const { Application, MailSystem } = require('./main');
const fs = require('fs');
const util = require('util');

// TODO: write your tests here
// Remember to use Stub, Mock, and Spy when necessary
// 1️⃣ Stub `fs.readFile` 為 Promise 版本
fs.readFile = util.promisify((path, encoding, callback) => {
callback(null, 'Alice\nBob\nCharlie\nDavid'); // 回傳假名單
});

const { Application, MailSystem } = require('./main'); // 確保這是你的主程式

test('Application getNames should return list of names from stubbed file', async () => {
const app = new Application();
await new Promise((resolve) => setTimeout(resolve, 100)); // 確保 `getNames` 完成
assert.deepStrictEqual(app.people, ['Alice', 'Bob', 'Charlie', 'David']);
});

test('MailSystem write should generate mail content', () => {
const mailSystem = new MailSystem();
const name = 'Alice';
const result = mailSystem.write(name);
assert.strictEqual(result, 'Congrats, Alice!');
});

test('MailSystem send should return both true and false', () => {
const mailSystem = new MailSystem();
const name = 'Alice';
const context = 'Congrats, Alice!';

let seenTrue = false;
let seenFalse = false;
let attempts = 0;
const maxAttempts = 100; // 限制最大迴圈次數避免無窮迴圈

while (!(seenTrue && seenFalse) && attempts < maxAttempts) {
const result = mailSystem.send(name, context);
if (result) {
seenTrue = true;
} else {
seenFalse = true;
}
attempts++;
}

assert.strictEqual(seenTrue, true, 'MailSystem.send() should return true at least once');
assert.strictEqual(seenFalse, true, 'MailSystem.send() should return false at least once');
});


test('Application getRandomPerson should return a valid person', async () => {
const app = new Application();
await new Promise((resolve) => setTimeout(resolve, 100)); // 確保 getNames 完成
const person = app.getRandomPerson();
assert.ok(['Alice', 'Bob', 'Charlie', 'David'].includes(person));
});

test('Application selectNextPerson should return null when all are selected', async () => {
const app = new Application();
await new Promise((resolve) => setTimeout(resolve, 100));
app.people = ['Alice', 'Bob'];
app.selected = ['Alice', 'Bob'];
const result = app.selectNextPerson();
assert.strictEqual(result, null);
});

test('Application selectNextPerson should select a new person each time', async () => {
const app = new Application();
await new Promise((resolve) => setTimeout(resolve, 100));
const selected1 = app.selectNextPerson();
const selected2 = app.selectNextPerson();
assert.notStrictEqual(selected1, null);
assert.notStrictEqual(selected2, null);
assert.notStrictEqual(selected1, selected2);
});

test('Application selectNextPerson should avoid duplicate selection', async () => {
const app = new Application();
await new Promise((resolve) => setTimeout(resolve, 100));
app.people = ['Alice', 'Bob', 'Charlie', 'David'];
app.selected = ['Alice'];
const selected = new Set(app.selected);
for (let i = 0; i < 4; i++) {
const newPerson = app.selectNextPerson();
assert.ok(!selected.has(newPerson));
selected.add(newPerson);
}
});

test('Application notifySelected should send emails to selected people', async () => {
const app = new Application();
await new Promise((resolve) => setTimeout(resolve, 100));
app.selectNextPerson();
app.selectNextPerson();

// Spy: 監視方法呼叫次數
let writeCallCount = 0;
let sendCallCount = 0;

const originalWrite = app.mailSystem.write;
const originalSend = app.mailSystem.send;

// Mock: 取代方法回傳預期值
app.mailSystem.write = () => {
writeCallCount++;
return 'Mock Content';
};

app.mailSystem.send = () => {
sendCallCount++;
return true;
};

app.notifySelected();

assert.strictEqual(writeCallCount, app.selected.length);
assert.strictEqual(sendCallCount, app.selected.length);

// 還原原始方法
app.mailSystem.write = originalWrite;
app.mailSystem.send = originalSend;
});
41 changes: 38 additions & 3 deletions lab8/solve.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,46 @@
#!/usr/bin/env python3

import angr,sys
import angr
import claripy
import sys

def main():
secret_key = b""
sys.stdout.buffer.write(secret_key)
# 載入 chal 執行檔
proj = angr.Project("./chal", auto_load_libs=False)

# 建立 8 個符號位元組(每個是 8-bit),組成 secret_key
key_bytes = [claripy.BVS(f'key_{i}', 8) for i in range(8)]
secret_key = claripy.Concat(*key_bytes)

# 初始化 state,將 symbolic input 傳入 stdin
state = proj.factory.full_init_state(stdin=secret_key)

# 加入輸入長度限制(因為 chal.c 會用 strlen 判斷長度必須是 8)
for b in key_bytes:
state.solver.add(b >= 0x20) # 可列印字元
state.solver.add(b <= 0x7e)

# 建立 simulation manager
simgr = proj.factory.simgr(state)

# 設定搜尋目標:當輸出包含 "Correct!",代表成功通過 gate()
def is_successful(state):
return b"Correct!" in state.posix.dumps(1)

# 設定排除條件:當輸出包含 "Wrong key!",表示是失敗路徑
def should_abort(state):
return b"Wrong key!" in state.posix.dumps(1)

# 探索符合條件的路徑
simgr.explore(find=is_successful, avoid=should_abort)

if simgr.found:
found = simgr.found[0]
# 將求得的符號解碼為實際的字串
key = found.solver.eval(secret_key, cast_to=bytes)
sys.stdout.buffer.write(key)
else:
print("No solution found.")

if __name__ == '__main__':
main()