看到題目文件:
從圖标來看明顯是python打包成的exe程序,使用pyinstxtractor.py來對這個exe程序解包。
Python pyinstxtractor.py main.exe
然後在目錄下得到一個main.exe_extracted文件夾,找到其中與程序同名的main文件。因為解包得到的核心pyc文件是去除了文件頭的,所以還要找到目錄下的struct文件來得到文件頭。
從struct得到文件頭:55 0D 0D 0A 00 00 00 00 70 79 69 30 10 01 00 00 将其填充到main文件的開頭16字節。對上面填充好的main文件重命名為main.pyc文件,用uncompyle6進行反編譯:
uncompyle6 -o main.py main.pyc
打開main.py文件:
# uncompyle6 version 3.7.4
# Python bytecode 3.8 (3413)
# Decompiled from: Python 3.8.6 (tags/v3.8.6:db45529, Sep 23 2020, 15:52:53) [MSC v.1927 64 bit (AMD64)]
# Embedded file name: main.py
# Compiled at: 1995-09-28 00:18:56
# Size of source mod 2**32: 272 bytes
import brainfuck
brainfuck.main_check()
到之前解包的目錄下找到brainfuck.cp38-win_amd64.pyd,原來這個題給的核心部分在pyd文件,這類似于winodws下的動态鍊接庫。
将該pyd文件與main.py文件放在同一目錄下然後執行main.py,随便輸入後反饋nonono
接着ida分析該pyd文件,正如模塊名字,從其中找到了brainfuck代碼。
用idapython導出該brainfuck代碼:
from ida_bytes import *
addr = 0x18000B740
a = ""
while addr < 0x18000E8D8:
a = chr(get_byte(addr))
addr = 1
f = open("code.txt", "w")
f.write(a)
f.close()
print('*'*100)
再簡單的按照brainfuck代碼的運算來解析成C代碼:
#include <stdio.h>
#include <iostream>
#include <stdlib.h>
#include <string.h>
using namespace std;
string translate(char c)
{
switch (c)
{
case '>':
return "p ";
case '<':
return "p--";
case ' ':
return "*p = *p 1";
case '-':
return "*p = *p - 1";
case '.':
return "cout<<char(*p)";
case ',':
return "*p=getchar()";
case '[':
return "while(*p){";
case ']':
return "}";
default:
return "";
}
}
int main()
{
FILE *fp = fopen("code.txt", "rb");
FILE *fp1 = fopen("ans.txt", "wb");
char c;
while ((c = fgetc(fp)) != EOF)
{
fputs(translate(c).c_str(), fp1);
if (c != '[')
fputs(";\n", fp1);
}
return 0;
}
得到1w多行指針運算代碼,這也是brainfuck代碼的特性,維護幾個變量做加減法運算完成程序所有的功能。将得到的C代碼處理一下後編譯成exe程序:
#include <stdio.h>
#include <string.h>
#include <iostream>
using namespace std;
char a[1000];
char *p = a;
int main(void)
{
p ;
p ;
p ;
p ;
p ;
p ;
p ;
p ;
p ;
p ;
p ;
...
...
...
p--;
p--;
p--;
p--;
p--;
}
在ida中調試編譯得到exe程序。技巧就看它維護幾個變量的内存值的變化吧。不斷調試可以知道這個程序在比較2個值是否相同用的減法,就是對要比較的2個數依次做減法,看最後他們是否同時為0,若是則相等,否則反之。
還是調試的時候看内存,得到以下信息:首先将輸入存入程序中的一塊内存區域,然後依次判斷開始的幾個字符是否是flag{和偏移 0x25的位置是否是}(如下圖的内存區域)
如果上面比較成功的話就開始對flag{}中的32字節開始進行運算。
發現第一個字節0x61變成0x50,其實就是當前字節和後一個字節異或運算的結果(0x61^0x31)。
繼續調試發現這一串密文除了最後一個都變成了0x50
所以從我的輸入與密文結果可以得出加密邏輯:flag[i] ^= flag[i 1],且在附近的區域找到密文:
最後異或回去得到flag:
>>> s = [0x53, 0x0F, 0x5A, 0x54, 0x50, 0x55, 0x03, 0x02, 0x00, 0x07, 0x56, 0x07, 0x07, 0x5B, 0x09, 0x00, 0x50, 0x05, 0x02, 0x03, 0x5D, 0x5C, 0x50, 0x51, 0x52, 0x54, 0x5A, 0x5F, 0x02, 0x57, 0x07, 0x34]
>>> for i in range(31):
... s[30-i] ^= s[31-i]
...
>>> bytes(s)
b'd78b6f30225cdc811adfe8d4e7c9fd34'
從題目名稱看是使用uni-app這個前端框架。jeb中看了看并沒有發現什麼關鍵點,想到題目的提示:JS不隻能寫網頁哦!然後從\assets\appsUNI14D1880\www目錄下找到了很多js文件。接着安裝好app運行來搜集一下app的字符串信息,發現有Please input... 與Try again,再使用notepad 的文件夾搜索功能來搜索上面得到的字符串信息,開始的Please input...并沒有搜到,但搜到了Try again,也通過這找到關鍵js文件:app-service.js
定位到app-service.js文件中的關鍵點:輸入先與108異或後再經過f["encrypt"]加密,最後與p密文對比。
來看到加密函數:就是一個異或運算,關鍵就是獲取這個_keystream
找到_keystream生成的地方:從[1634760805, 857760878, 2036477234, 1797285236]定位到這其實是一個chacha20序列密碼。
從github找到一份python實現的chacha20,适當的修改後再修改key,Nonce及position為js文件中的。(python實現chacha20的key是32字節,iv為8字節,position為0;js中的key同樣為32字節但iv為12字節,position為1。這個從對比參數的填充很容易發現)
key:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31]
iv:[0, 0, 0, 0, 0, 0, 0, 74, 0, 0, 0, 0]
position:1
python生成chacha20異或序列代碼:
import struct
def yield_chacha20_xor_stream(key, iv, position=0):
"""Generate the xor stream with the ChaCha20 cipher."""
if not isinstance(position, int):
raise TypeError
if position & ~0xffffffff:
raise ValueError('Position is not uint32.')
if not isinstance(key, bytes):
raise TypeError
if not isinstance(iv, bytes):
raise TypeError
if len(key) != 32:
raise ValueError
if len(iv) != 12:
raise ValueError
def rotate(v, c):
return ((v << c) & 0xffffffff) | v >> (32 - c)
def quarter_round(x, a, b, c, d):
x[a] = (x[a] x[b]) & 0xffffffff
x[d] = rotate(x[d] ^ x[a], 16)
x[c] = (x[c] x[d]) & 0xffffffff
x[b] = rotate(x[b] ^ x[c], 12)
x[a] = (x[a] x[b]) & 0xffffffff
x[d] = rotate(x[d] ^ x[a], 8)
x[c] = (x[c] x[d]) & 0xffffffff
x[b] = rotate(x[b] ^ x[c], 7)
ctx = [0] * 16
ctx[:4] = (1634760805, 857760878, 2036477234, 1797285236)
ctx[4 : 12] = struct.unpack('<8L', key)
ctx[12] = position
ctx[13 : 16] = struct.unpack('<3L', iv)
while 1:
x = list(ctx)
for i in range(10):
quarter_round(x, 0, 4, 8, 12)
quarter_round(x, 1, 5, 9, 13)
quarter_round(x, 2, 6, 10, 14)
quarter_round(x, 3, 7, 11, 15)
quarter_round(x, 0, 5, 10, 15)
quarter_round(x, 1, 6, 11, 12)
quarter_round(x, 2, 7, 8, 13)
quarter_round(x, 3, 4, 9, 14)
for c in struct.pack('<16L', *(
(x[i] ctx[i]) & 0xffffffff for i in range(16))):
yield c
ctx[12] = (ctx[12] 1) & 0xffffffff
if ctx[12] == 0:
ctx[13] = (ctx[13] 1) & 0xffffffff
def chacha20_encrypt(data, key, iv=None, position=1):
"""Encrypt (or decrypt) with the ChaCha20 cipher."""
if not isinstance(data, bytes):
raise TypeError
if iv is None:
iv = b'\0' * 8
if isinstance(key, bytes):
if not key:
raise ValueError('Key is empty.')
if len(key) < 32:
# TODO(pts): Do key derivation with PBKDF2 or something similar.
key = (key * (32 // len(key) 1))[:32]
if len(key) > 32:
raise ValueError('Key too long.')
return yield_chacha20_xor_stream(key, iv, position)
def run_tests():
import binascii
uh = lambda x: binascii.unhexlify(bytes(x, 'ascii'))
key = bytes([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31])
iv = bytes([0, 0, 0, 0, 0, 0, 0, 74, 0, 0, 0, 0])
ans = chacha20_encrypt(b'\0' * 32, key, iv)
for i, data in enumerate(ans):
if i == 38:
break
print(data, end = ', ')
if __name__ == "__main__":
run_tests()
34, 79, 81, 243, 64, 27, 217, 225, 47, 222, 39, 111, 184, 99, 29, 237, 140, 19, 31, 130, 61, 44, 6, 226, 126, 79, 202, 236, 158, 243, 207, 120, 138, 59, 10, 163, 114, 96
異或密文與108後得到flag:
>>> p
[34, 69, 86, 242, 93, 72, 134, 226, 42, 138, 112, 56, 189, 53, 77, 178, 223, 76, 78, 221, 63, 40, 86, 231, 121, 29, 154, 189, 204, 243, 205, 44, 141, 100, 13, 164, 35, 123]
>>> a = [34, 79, 81, 243, 64, 27, 217, 225, 47, 222, 39, 111, 184, 99, 29, 237, 140, 19, 31, 130, 61, 44, 6, 226, 126, 79, 202, 236, 158, 243, 207, 120, 138, 59, 10, 163, 114, 96]
>>> ans = [p[i]^a[i]^102 for i in range(38)]
>>> bytes(ans)
b'flag{59ec211c0695979db6ca4674fd2a9aa7}'
最後說一下如何直接使用給到的js代碼生成chacha20的異或序列。對生成密鑰序列的代碼稍微改一下:
//2.js
var r = function (t, e, n) {
this._chacha = function () {
var t = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
e = 0,
n = 0
for (e = 0; e < 16; e ) t[e] = this._param[e]
for (e = 0; e < this._rounds; e = 2) {
this._quarterround(t, 0, 4, 8, 12), this._quarterround(t, 1, 5, 9, 13)
this._quarterround(t, 2, 6, 10, 14)
this._quarterround(t, 3, 7, 11, 15)
this._quarterround(t, 0, 5, 10, 15)
this._quarterround(t, 1, 6, 11, 12)
this._quarterround(t, 2, 7, 8, 13)
this._quarterround(t, 3, 4, 9, 14)
}
for (e = 0; e < 16; e ) {
t[e] = this._param[e]
this._keystream[n ] = 255 & t[e]
this._keystream[n ] = (t[e] >>> 8) & 255
this._keystream[n ] = (t[e] >>> 16) & 255
this._keystream[n ] = (t[e] >>> 24) & 255
}
}
this._quarterround = function (t, e, n, r, o) {
t[o] = this._rotl(t[o] ^ (t[e] = t[n]), 16)
t[n] = this._rotl(t[n] ^ (t[r] = t[o]), 12)
t[o] = this._rotl(t[o] ^ (t[e] = t[n]), 8)
t[n] = this._rotl(t[n] ^ (t[r] = t[o]), 7)
t[e] >>>= 0
t[n] >>>= 0
t[r] >>>= 0
t[o] >>>= 0
}
this._get32 = function (t, e) {
return t[e ] ^ (t[e ] << 8) ^ (t[e ] << 16) ^ (t[e] << 24)
}
this._rotl = function (t, e) {
return (t << e) | (t >>> (32 - e))
}
this.encrypt = function (t) {
return this._update(t)
}
this.decrypt = function (t) {
return this._update(t)
}
this._update = function (t) {
if (!(t instanceof Uint8Array) || 0 === t.length)
throw new Error(
'Data should be type of bytes (Uint8Array) and not empty!'
)
for (var e = new Uint8Array(t.length), n = 0; n < t.length; n ) {
; (0 !== this._byteCounter && 64 !== this._byteCounter) ||
(this._chacha(), this._param[12] , (this._byteCounter = 0))
e[n] = this._keystream[this._byteCounter ]
}
return e
}
if (
('undefined' === typeof n && (n = 0),
!(t instanceof Uint8Array) || 32 !== t.length)
)
throw new Error('Key should be 32 byte array!')
if (!(e instanceof Uint8Array) || 12 !== e.length)
throw new Error('Nonce should be 12 byte array!')
this._rounds = 20
this._sigma = [1634760805, 857760878, 2036477234, 1797285236]
this._param = [
this._sigma[0],
this._sigma[1],
this._sigma[2],
this._sigma[3],
this._get32(t, 0),
this._get32(t, 4),
this._get32(t, 8),
this._get32(t, 12),
this._get32(t, 16),
this._get32(t, 20),
this._get32(t, 24),
this._get32(t, 28),
n,
this._get32(e, 0),
this._get32(e, 4),
this._get32(e, 8),
]
this._keystream = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
]
this._byteCounter = 0
var plain = new Uint8Array(38)
//console.log(plain)
console.log(this.encrypt(plain).toString())
}
for (n = [], o = 0; o <= 31; o ) n[o] = o
var t = new Uint8Array(n),
e = new Uint8Array([0, 0, 0, 0, 0, 0, 0, 74, 0, 0, 0, 0]),
n = 1
r(t, e, n)
然後使用php來加載一下這個js文件:
<?php
echo '<script src="2.js"></script>';
?>
浏覽器打開即可看到生成的異或序列:将結果輸出toString()方便打印。
知白講堂是啟明星辰集團網絡空間安全學院的在線教育培訓平台。豐富的在線課程體系和專家直播講堂為每一位學員授業解惑。知白講堂,一直秉承“網絡安全,人才當先”的理念,助力夢想,提升職業素養,打造網絡安全的行業精英!
,
更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!