看雪.騰訊TSRC 2017 CTF 秋季賽 第六題點評及解析思路

Editor發表於2017-11-06


導語

各位小夥伴們週末快樂~

第六題終於結束了,最終有兩人挑戰成功!分別是風間仁和hotwinter,恭喜!

看雪.騰訊TSRC 2017 CTF 秋季賽 第六題點評及解析思路

第六題作者以兩人攻破此題的成績,登上防守方第一名的位置。

看雪.騰訊TSRC 2017 CTF 秋季賽 第六題點評及解析思路

防守方hotwinter 逆襲至第一名,風間仁在短暫的落後之後,再次重回第三名。

看雪.騰訊TSRC 2017 CTF 秋季賽 第六題點評及解析思路

現在還剩三題,比賽愈發緊張。

究竟誰能奪取最後的勝利呢?我們翹首以望。各位選手,加油!

接下來讓我們一起來看看第六題的點評、出題思路和解析。

看雪評委netwind點評


作者採用了修改過的DES演算法對資料進行了加密。然後將加密結果轉換為大數,用miracl庫做大數計算。最後呼叫luajit指令碼執行大整數計算,把計算結果和記憶體資料進行對比驗證。破解此題要識別出正確的luajit版本號並反編譯luajit指令碼,識別作者實現的FFT大數乘法,識別DES函式的修改點並實現解密函式才能完成。


第六題作者簡介


loudy,2007年接觸看雪,熱衷程式設計、特別是逆向分析。當前從事工作出於保密規定暫不公開。愛好跑步、籃球、電影。在論壇多以潛水學習為主,也希望多認識有相同愛好的朋友。


第六題設計思路


程式設計流程清楚,未加殼,未做任何反逆向手段,但存在幾個坑點,以下一一說明:

1、輸入長度未固定,需要分析(16-24位)。

2、對輸入做DES加密,但該DES被修改。


(1)表

/*

char DES::Expand_Table[EXPAND_SIZE] = {

31, 0, 1, 2, 3, 4,

3,  4, 5, 6, 7, 8,

7,  8, 9,10,11,12,

11,12,13,14,15,16,

15,16,17,18,19,20,

19,20,21,22,23,24,

23,24,25,26,27,28,

27,28,29,30,31, 0};

*/

被修改為

char DES::Expand_Table[EXPAND_SIZE] = {

0,  0, 1, 2, 3, 3,

4,  4, 5, 6, 7, 7,

8,  8, 9,10,11,11,

12,12,13,14,15,15,

16,16,17,18,19,19,

20,20,21,22,23,23,

24,24,25,26,27,27,

28,28,29,30,31,31};

改變了加密結果,需要分析出來。


(2)塊加密時(8位元組),置換表的使用順序和子金鑰的使用順序做了改變。


IP_Transform(bitStr);

std::string halfBitStr;

halfBitStr.resize(bitStr.size() / 2);

std::string eBitStr;

eBitStr.resize(EXPAND_SIZE);

for(int i = SUBKEY_NUM-1; i >= 0; --i)

{

Expand_Transform(bitStr.substr(bitStr.size() / 2), eBitStr);

XOR(eBitStr, std::string(subKey[i],SUBKEY_LENGHT), SUBKEY_LENGHT);

SBox_Transform(eBitStr, halfBitStr);

Permute_Transform(halfBitStr);

XOR(bitStr, halfBitStr, halfBitStr.size());

if(i != 0)

LeftCycle(bitStr, 0, bitStr.size(), bitStr.size() / 2);

}

IP_1_Transform(bitStr);

(3)全文加密時,多加了一個異或操作,改變加密結果,需要分析出來。

for(size_t i = 1; i < plain.size() / 8; ++i)

{

block = plain.substr(i * 8, 8);

for(int j=0;j<8;j++)

{

block[j] = block[j]^tmpblock[7-j];

}

EncryptBlock(block, subKey);

result.append(block);

tmpblock.assign(block);

}


3、加密結果轉換為大數,用miracl庫做大數計算。


(1)呼叫函式multiply與173做乘法運算。

big bigx = mirvar(0);

bigx->len = 1;

bigx->w[0] = 173;

multiply(bigx,bigtmp1,bigx);

(2)呼叫函式fft_mult與0x719做乘法運算。

big bigy = mirvar(0);

bigy->len = 1;

bigy->w[0] = 0x719;

fft_mult(bigx,bigy,bigy);

但其中動了小手腳(在fft_mult最後做了一次乘方運算和一次減法運算,減1001)。

power(z,2,z,z);

decr(z,1001,z);


4、呼叫自己實現的FFT乘法,乘以317。

int* out1 = mull(desOut,"317");


5、呼叫luajit指令碼執行大整數計算。

--xut c程式匯入的引數

--func1加法

--func2減法

--func3乘法

local

yyy =

"1574592838300862641516215149137548264158058079230003764126382984039489925466995870724568174393389905601620735902909057604303543552180706761904"

function myst()

c = func1(xut,"1001")

d = 0

for i=1,100 do

for j=1,100 do

c = func1(c,"1001")

end

c = func1(c,"10101")

end

c = func3(c,"983751509373")

for i=1,13 do

c = func2(c,"1023")

for j=1,14 do

c = func2(c,"1203")

for k=1,15 do

c = func2(c,"1230")

for l=1,16 do

c = func2(c,"1231")

end

end

end

end

c = func1(c,"1")

c = func3(c,"2")

e = get(c)

--print(e)

lene = string.len(e)

leny = string.len(yyy)

if(lene==leny)

then

--print("same length")

--[ local variable definition --]

i = 1

d = 1

--[ while loop execution --]

while( i <= lene )

do

if(string.byte(e,i)~=string.byte(yyy,i))

then

--[ terminate the loop using break statement --]

d = 0

break

end

i = i+1

end

end

return d

end

計算結果和固定值“1574592838300862641516215149137548264158058079230003764126382984039489925466995870724568174393389905601620735902909057604303543552180706761904”比較,相等則註冊成功,不然失敗。但此處也有坑,我把luajit版本號改了,本來是luajit2.0.5,我把字串改為luajit2.1.0-beta3,反編譯難度加大。


破解思路


1、 識別正確luajit版本號,反編譯luajit指令碼,理清其中流程,通過固定值“1574592838300862641516215149137548264158058079230003764126382984039489925466995870724568174393389905601620735902909057604303543552180706761904”,得到luajit指令碼的正確輸入。

2、 識別自己實現的FFT大數乘法,反推乘法函式mull的輸入。

3、 識別miracl庫,並找到fft_mult函式的修改點(函式最後),反推fft_mult的輸入。

4、 反推multiply的輸入,並轉化為16進位制。

5、 識別DES函式的修改點,並自己實現解密函式,將上一步結果解密,得到最終的key(“KXCTF201710BYLoudy08”),破解完成。


下面選取 hotwinter 的破解分析


這題真的是做的我想砸牆……前前後後花了不下24小時……各種坑……放棄HITCON一直在剛……

這題是一道數學 + 密碼學 + lua + C++ STL的極其噁心人的一道題……大概一共分為三大部分,其中摻雜了各種不常見的庫函式和更改過的庫函式……這題經驗就是善用工具……

首先慣例直接上strings

看雪.騰訊TSRC 2017 CTF 秋季賽 第六題點評及解析思路

跟著引用找到main函式,如下

看雪.騰訊TSRC 2017 CTF 秋季賽 第六題點評及解析思路

看雪.騰訊TSRC 2017 CTF 秋季賽 第六題點評及解析思路

看雪.騰訊TSRC 2017 CTF 秋季賽 第六題點評及解析思路

看雪.騰訊TSRC 2017 CTF 秋季賽 第六題點評及解析思路

大概可以看出邏輯如下,我們的輸入會經過兩個大結構的變換,第一個是des_decrypt(之後我們會說怎麼識別他),第二個是MIRACL的大整數操作,我們輸入做了以下兩個操作以後,會扔進lua裡面進行第三次操作,最終進行比較,大致邏輯就是這樣


首先先說一下庫函式都是怎麼識別出來的吧(DES是人工扒演算法和常數扒出來的……),這裡用到了bindiff這個工具https://www.zynamics.com/software.html,他會比較兩個idb之間每個函式的相似性,並列出最相似的函式,對於像MIRACL和LuaJIT這種開源程式來說很好用。我們需要做的就是下載對應版本的原始碼,編譯成exe/dll

(因為linux下calling convention不同,所以bindiff可能識別沒那麼準確(大概))以後開IDA執行一遍初始分析,然後儲存idb後退出。注意這裡每個版本都最好建debug build,這樣大部分的函式名稱會被保留,並生成pdb。bindiff目前還沒有支援IDA7.0……也就是我為什麼換回了IDA 6.95……

LuaJIT很好編譯……直接扔vs command line  tools裡面跑他的mvcsbuild就好了,這裡檢視build的bat指令碼後發現假如第一個引數是debug的話,他就會建成debug build(即 mvsbuild.exe


debug)非常方便,因此lua庫函式立馬就標完了(不過這裡實際執行的時候遇到了問題……luaji2.1.0-beta3使用的是2.0版的Opcode按理說並不能跑起來……即使改成1.0版也還是會出錯……不知道為什麼程式裡面可以用2.1.0-beta3這個build……)。lua庫函式所在的檔案是lua51.dll,不是luajit.exe,不過lua庫函式改動不大,所以bindiff識別準確率還是挺高的。所有lua的C API函式基本上都是bindiff識別的

MIRACL這個東西就dt了……我們知道用了MIRACL是因為strings裡面的這句話

看雪.騰訊TSRC 2017 CTF 秋季賽 第六題點評及解析思路


MIRACL的build只有一個指令碼在lib/ms32doit.bat……而且寫的非常糟糕……手動加了一些編譯

flag以後終於是可以編譯了……生成檔案為miracl.lib

(用起來不是很方便,於是改編譯指令碼為dll),MIRACL的識別準確率香蕉LuaJIT來說就沒有那麼高了……不是很清楚為什麼,不過用的也不算太多,所以還好,這裡可以點開第一個mirsys_basic函式,然後照著原始碼把miracl的結構體給擼出來(因為有各種ifndef之類的……所以沒法直接看原始碼擼),這樣之後會方便很多


看雪.騰訊TSRC 2017 CTF 秋季賽 第六題點評及解析思路

這裡我們注意到一點,MIRACL其實是有stack

backtrace的,他的識別關鍵在於miracl結構體中trace這個結構(這也是為什麼我們要照著mirsys_basic擼結構體的原因),找到這個以後我們MIRACL的識別就很簡單了。stack trace主要是靠MR_IN()組成,每個主要函式都有自己的數字,所以只要對照這個就能找到大部分的函式了,這裡以power為例子進行說明

看雪.騰訊TSRC 2017 CTF 秋季賽 第六題點評及解析思路

紅框中標出的即為該函式的MR_IN的stack trace number,下面我們直接grep 原始碼目錄就可找到對應的定義

看雪.騰訊TSRC 2017 CTF 秋季賽 第六題點評及解析思路

MIRACL幾乎沒有說明文件……所有東西都是靠我自己從原始碼裡面翻出來的……dt,這裡我還用到了cscope這個工具來在本地快速查詢C和C++ project的函式定義很引用(有時候grep的結果太多了),使用方法很簡單

find . -iname "*.c" -o -iname "*.cpp" -o -iname "*.hpp" -o -iname "*.h" > cscope.files (這裡可以加任何你想要的檔案字尾,只要find函式能找到就行)

cscope -bqk (建立索引)

cscope -d (避免重複建立索引)

個人感覺cscope只適合相對較小的project……如果是linux kernel那種……最好還是自己搭一個opengrok的伺服器比較好

基本庫函式標完……下面就進入正式的逆向工作……

一。des_cbc_decrypt

這個函式是des cbc的解密(加密?)函式,作者更改了很多的常量……使得我們不得不自己找一個des的庫更改後使用。des的識別是靠了這串陣列

看雪.騰訊TSRC 2017 CTF 秋季賽 第六題點評及解析思路

以16進位制陣列搜尋(搜 0xe,0x4,0xd,0x1,0x2,0xf,0xb,0x8,0x3,0xa,0x6,0xc)可找到http://kodu.ut.ee/~lipmaa/teaching/crypto2000/des/des.C,檢視後,發現是DES的sbox,由於des並不是所有操作都需要用到table,找到一個和所給des相似的庫找了我很長時間(這裡也是剛開始覺得常量不會改……),最終選用了這個https://gist.githubusercontent.com/eigenein/1275094/raw/62d1fee5e9d19df44fab6950ecbcd9c82b9b9805/pyDes.py

根據這個演算法,對應的des的表改過來就好了(有個表只改了一個位元組……實在是很坑爹)。des的key

schedule用到的表也需要改(給的例子裡面bits是按MSB encode的這個也需要改)。同時des_decrypt裡面roundkey是從最後一輪開始的……(也就是為什麼我說他是decrypt),但cbc的演算法明顯是block

cipher的加密演算法……這裡還要翻一下iv。同時des_decrypt裡面對調Ininital

permutation和final permutation,調整後des的程式碼(3DES我懶得刪了)

#############################################################################

#                              Documentation                              #

#############################################################################

# Author:  Todd Whiteman

# Date:    16th March, 2009

# Verion:  2.0.0

# License:  Public Domain - free to do as you wish

# Homepage: http://twhiteman.netfirms.com/des.html

#

# This is a pure python implementation of the DES encryption algorithm.

# It's pure python to avoid portability issues, since most DES

# implementations are programmed in C (for performance reasons).

#

# Triple DES class is also implemented, utilising the DES base. Triple DES

# is either DES-EDE3 with a 24 byte key, or DES-EDE2 with a 16 byte key.

#

# See the README.txt that should come with this python module for the

# implementation methods used.

#

# Thanks to:

#  * David Broadwell for ideas, comments and suggestions.

#  * Mario Wolff for pointing out and debugging some triple des CBC errors.

#  * Santiago Palladino for providing the PKCS5 padding technique.

#  * Shaya for correcting the PAD_PKCS5 triple des CBC errors.

#

"""A pure python implementation of the DES and TRIPLE DES encryption algorithms.

Class initialization

--------------------

pyDes.des(key, [mode], [IV], [pad], [padmode])

pyDes.triple_des(key, [mode], [IV], [pad], [padmode])

key    -> Bytes containing the encryption key. 8 bytes for DES, 16 or 24 bytes

for Triple DES

mode    -> Optional argument for encryption type, can be either

pyDes.ECB (Electronic Code Book) or pyDes.CBC (Cypher Block Chaining)

IV      -> Optional Initial Value bytes, must be supplied if using CBC mode.

Length must be 8 bytes.

pad    -> Optional argument, set the pad character (PAD_NORMAL) to use during

all encrypt/decrpt operations done with this instance.

padmode -> Optional argument, set the padding mode (PAD_NORMAL or PAD_PKCS5)

to use during all encrypt/decrpt operations done with this instance.

I recommend to use PAD_PKCS5 padding, as then you never need to worry about any

padding issues, as the padding can be removed unambiguously upon decrypting

data that was encrypted using PAD_PKCS5 padmode.

Common methods

--------------

encrypt(data, [pad], [padmode])

decrypt(data, [pad], [padmode])

data    -> Bytes to be encrypted/decrypted

pad    -> Optional argument. Only when using padmode of PAD_NORMAL. For

encryption, adds this characters to the end of the data block when

data is not a multiple of 8 bytes. For decryption, will remove the

trailing characters that match this pad character from the last 8

bytes of the unencrypted data block.

padmode -> Optional argument, set the padding mode, must be one of PAD_NORMAL

or PAD_PKCS5). Defaults to PAD_NORMAL.

Example

-------

from pyDes import *

data = "Please encrypt my data"

k = des("DESCRYPT", CBC, "\0\0\0\0\0\0\0\0", pad=None, padmode=PAD_PKCS5)

# For Python3, you'll need to use bytes, i.e.:

#  data = b"Please encrypt my data"

#  k = des(b"DESCRYPT", CBC, b"\0\0\0\0\0\0\0\0", pad=None, padmode=PAD_PKCS5)

d = k.encrypt(data)

print "Encrypted: %r" % d

print "Decrypted: %r" % k.decrypt(d)

assert k.decrypt(d, padmode=PAD_PKCS5) == data

See the module source (pyDes.py) for more examples of use.

You can also run the pyDes.py file without and arguments to see a simple test.

Note: This code was not written for high-end systems needing a fast

implementation, but rather a handy portable solution with small usage.

"""

import sys

# _pythonMajorVersion is used to handle Python2 and Python3 differences.

_pythonMajorVersion = sys.version_info[0]

# Modes of crypting / cyphering

ECB =  0

CBC =  1

# Modes of padding

PAD_NORMAL = 1

PAD_PKCS5 = 2

# PAD_PKCS5: is a method that will unambiguously remove all padding

#            characters after decryption, when originally encrypted with

#            this padding mode.

# For a good description of the PKCS5 padding technique, see:

# http://www.faqs.org/rfcs/rfc1423.html

# The base class shared by des and triple des.

class _baseDes(object):

def __init__(self, mode=ECB, IV=None, pad=None, padmode=PAD_NORMAL):

if IV:

IV = self._guardAgainstUnicode(IV)

if pad:

pad = self._guardAgainstUnicode(pad)

self.block_size = 8

# Sanity checking of arguments.

if pad and padmode == PAD_PKCS5:

raise ValueError("Cannot use a pad character with PAD_PKCS5")

if IV and len(IV) != self.block_size:

raise ValueError("Invalid Initial Value (IV), must be a multiple of " + str(self.block_size) + " bytes")

# Set the passed in variables

self._mode = mode

self._iv = IV

self._padding = pad

self._padmode = padmode

def getKey(self):

"""getKey() -> bytes"""

return self.__key

def setKey(self, key):

"""Will set the crypting key for this object."""

key = self._guardAgainstUnicode(key)

self.__key = key

def getMode(self):

"""getMode() -> pyDes.ECB or pyDes.CBC"""

return self._mode

def setMode(self, mode):

"""Sets the type of crypting mode, pyDes.ECB or pyDes.CBC"""

self._mode = mode

def getPadding(self):

"""getPadding() -> bytes of length 1. Padding character."""

return self._padding

def setPadding(self, pad):

"""setPadding() -> bytes of length 1. Padding character."""

if pad is not None:

pad = self._guardAgainstUnicode(pad)

self._padding = pad

def getPadMode(self):

"""getPadMode() -> pyDes.PAD_NORMAL or pyDes.PAD_PKCS5"""

return self._padmode

def setPadMode(self, mode):

"""Sets the type of padding mode, pyDes.PAD_NORMAL or pyDes.PAD_PKCS5"""

self._padmode = mode

def getIV(self):

"""getIV() -> bytes"""

return self._iv

def setIV(self, IV):

"""Will set the Initial Value, used in conjunction with CBC mode"""

if not IV or len(IV) != self.block_size:

raise ValueError("Invalid Initial Value (IV), must be a multiple of " + str(self.block_size) + " bytes")

IV = self._guardAgainstUnicode(IV)

self._iv = IV

def _padData(self, data, pad, padmode):

# Pad data depending on the mode

if padmode is None:

# Get the default padding mode.

padmode = self.getPadMode()

if pad and padmode == PAD_PKCS5:

raise ValueError("Cannot use a pad character with PAD_PKCS5")

if padmode == PAD_NORMAL:

if len(data) % self.block_size == 0:

# No padding required.

return data

if not pad:

# Get the default padding.

pad = self.getPadding()

if not pad:

raise ValueError("Data must be a multiple of " + str(self.block_size) + " bytes in length. Use padmode=PAD_PKCS5 or set the pad character.")

data += (self.block_size - (len(data) % self.block_size)) * pad

elif padmode == PAD_PKCS5:

pad_len = 8 - (len(data) % self.block_size)

if _pythonMajorVersion < 3:

data += pad_len * chr(pad_len)

else:

data += bytes([pad_len] * pad_len)

return data

def _unpadData(self, data, pad, padmode):

# Unpad data depending on the mode.

if not data:

return data

if pad and padmode == PAD_PKCS5:

raise ValueError("Cannot use a pad character with PAD_PKCS5")

if padmode is None:

# Get the default padding mode.

padmode = self.getPadMode()

if padmode == PAD_NORMAL:

if not pad:

# Get the default padding.

pad = self.getPadding()

if pad:

data = data[:-self.block_size] + \

data[-self.block_size:].rstrip(pad)

elif padmode == PAD_PKCS5:

if _pythonMajorVersion < 3:

pad_len = ord(data[-1])

else:

pad_len = data[-1]

data = data[:-pad_len]

return data

def _guardAgainstUnicode(self, data):

# Only accept byte strings or ascii unicode values, otherwise

# there is no way to correctly decode the data into bytes.

if _pythonMajorVersion < 3:

if isinstance(data, unicode):

raise ValueError("pyDes can only work with bytes, not Unicode strings.")

else:

if isinstance(data, str):

# Only accept ascii unicode values.

try:

return data.encode('ascii')

except UnicodeEncodeError:

pass

raise ValueError("pyDes can only work with encoded strings, not Unicode.")

return data

#############################################################################

#                                  DES                                    #

#############################################################################

class des(_baseDes):

"""DES encryption/decrytpion class

Supports ECB (Electronic Code Book) and CBC (Cypher Block Chaining) modes.

pyDes.des(key,[mode], [IV])

key  -> Bytes containing the encryption key, must be exactly 8 bytes

mode -> Optional argument for encryption type, can be either pyDes.ECB

(Electronic Code Book), pyDes.CBC (Cypher Block Chaining)

IV  -> Optional Initial Value bytes, must be supplied if using CBC mode.

Must be 8 bytes in length.

pad  -> Optional argument, set the pad character (PAD_NORMAL) to use

during all encrypt/decrpt operations done with this instance.

padmode -> Optional argument, set the padding mode (PAD_NORMAL or

PAD_PKCS5) to use during all encrypt/decrpt operations done

with this instance.

"""

# Permutation and translation tables for DES

__pc1 = [56, 48, 40, 32, 24, 16,  8,

0, 57, 49, 41, 33, 25, 17,

9,  1, 58, 50, 42, 34, 26,

18, 10,  2, 59, 51, 43, 35,

62, 54, 46, 38, 30, 22, 14,

6, 61, 53, 45, 37, 29, 21,

13,  5, 60, 52, 44, 36, 28,

20, 12,  4, 27, 19, 11,  3

]

# number left rotations of pc1

# changed!

__left_rotations = [

0x17, 0xa, 2, 5, 9, 2, 3, 2, 3, 2, 5, 7, 2, 9, 2, 7

]

# permuted choice key (table 2)

# changed!

__pc2 = [

13, 16, 10, 23,  0,  4,

2, 27, 14,  5, 20,  9,

22, 18, 11,  3, 25,  7,

15,  6, 26, 19, 12,  1,

40, 51, 30, 36, 46, 54,

29, 39, 50, 44, 32, 46,

43, 48, 38, 55, 33, 52,

45, 41, 49, 35, 28, 31

]

# initial permutation IP

__ip = [57, 49, 41, 33, 25, 17, 9,  1,

59, 51, 43, 35, 27, 19, 11, 3,

61, 53, 45, 37, 29, 21, 13, 5,

63, 55, 47, 39, 31, 23, 15, 7,

56, 48, 40, 32, 24, 16, 8,  0,

58, 50, 42, 34, 26, 18, 10, 2,

60, 52, 44, 36, 28, 20, 12, 4,

62, 54, 46, 38, 30, 22, 14, 6

]

# Expansion table for turning 32 bit blocks into 48 bits

__expansion_table = [

0,  0,  1,  2,  3,  3,

4,  4,  5,  6,  7,  7,

8,  8,  9, 10, 11, 11,

12, 12, 13, 14, 15, 15,

16, 16, 17, 18, 19, 19,

20, 20, 21, 22, 23, 23,

24, 24, 25, 26, 27, 27,

28, 28, 29, 30, 31, 31

]

# The (in)famous S-boxes

__sbox = [

# S1

[14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7,

0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8,

4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0,

15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13],

# S2

[15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10,

3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5,

0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15,

13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9],

# S3

[10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8,

13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1,

13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7,

1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12],

# S4

[7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15,

13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9,

10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4,

3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14],

# S5

[2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9,

14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6,

4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14,

11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3],

# S6

[12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11,

10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8,

9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6,

4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13],

# S7

[4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1,

13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6,

1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2,

6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12],

# S8

[13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7,

1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2,

7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8,

2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11],

]

# 32-bit permutation function P used on the output of the S-boxes

__p = [

15, 6, 19, 20, 28, 11,

27, 16, 0, 14, 22, 25,

4, 17, 30, 9, 1, 7,

23,13, 31, 26, 2, 8,

18, 12, 29, 5, 21, 10,

3, 24

]

# final permutation IP^-1

__fp = [

39,  7, 47, 15, 55, 23, 63, 31,

38,  6, 46, 14, 54, 22, 62, 30,

37,  5, 45, 13, 53, 21, 61, 29,

36,  4, 44, 12, 52, 20, 60, 28,

35,  3, 43, 11, 51, 19, 59, 27,

34,  2, 42, 10, 50, 18, 58, 26,

33,  1, 41,  9, 49, 17, 57, 25,

32,  0, 40,  8, 48, 16, 56, 24

]

# Type of crypting being done

ENCRYPT =      0x00

DECRYPT =      0x01

# Initialisation

def __init__(self, key, mode=ECB, IV=None, pad=None, padmode=PAD_NORMAL):

# Sanity checking of arguments.

if len(key) != 8:

raise ValueError("Invalid DES key size. Key must be exactly 8 bytes long.")

_baseDes.__init__(self, mode, IV, pad, padmode)

self.key_size = 8

self.L = []

self.R = []

self.Kn = [ [0] * 48 ] * 16    # 16 48-bit keys (K1 - K16)

self.final = []

self.setKey(key)

def setKey(self, key):

"""Will set the crypting key for this object. Must be 8 bytes."""

_baseDes.setKey(self, key)

self.__create_sub_keys()

def __String_to_BitList(self, data):

"""Turn the string data, into a list of bits (1, 0)'s"""

if _pythonMajorVersion < 3:

# Turn the strings into integers. Python 3 uses a bytes

# class, which already has this behaviour.

data = [ord(c) for c in data]

l = len(data) * 8

result = [0] * l

pos = 0

for ch in data:

i = 0

while i <= 7:

result[pos] = (ch >> i) & 1

pos += 1

i += 1

return result

def __BitList_to_String(self, data):

"""Turn the list of bits -> data, into a string"""

result = []

pos = 0

c = 0

while pos < len(data):

c += data[pos] << (pos % 8)

if (pos % 8) == 7:

result.append(c)

c = 0

pos += 1

if _pythonMajorVersion < 3:

return ''.join([ chr(c) for c in result ])

else:

return bytes(result)

def __permutate(self, table, block):

"""Permutate this block with the specified table"""

return list(map(lambda x: block[x], table))

# Transform the secret key, so that it is ready for data processing

# Create the 16 subkeys, K[1] - K[16]

def __create_sub_keys(self):

"""Create the 16 subkeys K[1] to K[16] from the given key"""

key = self.__permutate(des.__pc1, self.__String_to_BitList(self.getKey()))

i = 0

# Split into Left and Right sections

self.L = key[:28]

self.R = key[28:]

while i < 16:

j = 0

# Perform circular left shifts

while j < des.__left_rotations[i]:

self.L.append(self.L[0])

del self.L[0]

self.R.append(self.R[0])

del self.R[0]

j += 1

# Create one of the 16 subkeys through pc2 permutation

self.Kn[i] = self.__permutate(des.__pc2, self.L + self.R)

i += 1

# Main part of the encryption algorithm, the number cruncher :)

def __des_crypt(self, block, crypt_type):

"""Crypt the block of data through DES bit-manipulation"""

block = self.__permutate(des.__fp, block)

self.L = block[:32]

self.R = block[32:]

# Encryption starts from Kn[1] through to Kn[16]

if crypt_type == des.ENCRYPT:

iteration = 0

iteration_adjustment = 1

# Decryption starts from Kn[16] down to Kn[1]

else:

iteration = 15

iteration_adjustment = -1

i = 0

while i < 16:

# Make a copy of R[i-1], this will later become L[i]

tempR = self.R[:]

# Permutate R[i - 1] to start creating R[i]

self.R = self.__permutate(des.__expansion_table, self.R)

# Exclusive or R[i - 1] with K[i], create B[1] to B[8] whilst here

self.R = list(map(lambda x, y: x ^ y, self.R, self.Kn[iteration]))

B = [self.R[:6], self.R[6:12], self.R[12:18], self.R[18:24], self.R[24:30], self.R[30:36], self.R[36:42], self.R[42:]]

# Optimization: Replaced below commented code with above

#j = 0

#B = []

#while j < len(self.R):

#      self.R[j] = self.R[j] ^ self.Kn[iteration][j]

#      j += 1

#      if j % 6 == 0:

#              B.append(self.R[j-6:j])

# Permutate B[1] to B[8] using the S-Boxes

j = 0

Bn = [0] * 32

pos = 0

while j < 8:

# Work out the offsets

m = (B[j][0] << 1) + B[j][5]

n = (B[j][1] << 3) + (B[j][2] << 2) + (B[j][3] << 1) + B[j][4]

# Find the permutation value

v = des.__sbox[j][(m << 4) + n]

#print hex(j * 48 + (m << 4 + n))

# Turn value into bits, add it to result: Bn

Bn[pos] = (v & 8) >> 3

Bn[pos + 1] = (v & 4) >> 2

Bn[pos + 2] = (v & 2) >> 1

Bn[pos + 3] = v & 1

pos += 4

j += 1

# Permutate the concatination of B[1] to B[8] (Bn)

self.R = self.__permutate(des.__p, Bn)

# Xor with L[i - 1]

self.R = list(map(lambda x, y: x ^ y, self.R, self.L))

# Optimization: This now replaces the below commented code

#j = 0

#while j < len(self.R):

#      self.R[j] = self.R[j] ^ self.L[j]

#      j += 1

# L[i] becomes R[i - 1]

self.L = tempR

i += 1

iteration += iteration_adjustment

# Final permutation of R[16]L[16]

self.final = self.__permutate(des.__ip, self.R + self.L)

return self.final

# Data to be encrypted/decrypted

def crypt(self, data, crypt_type):

"""Crypt the data in blocks, running it through des_crypt()"""

# Error check the data

if not data:

return ''

if len(data) % self.block_size != 0:

if crypt_type == des.DECRYPT: # Decryption must work on 8 byte blocks

raise ValueError("Invalid data length, data must be a multiple of " + str(self.block_size) + " bytes\n.")

if not self.getPadding():

raise ValueError("Invalid data length, data must be a multiple of " + str(self.block_size) + " bytes\n. Try setting the optional padding character")

else:

data += (self.block_size - (len(data) % self.block_size)) * self.getPadding()

# print "Len of data: %f" % (len(data) / self.block_size)

if self.getMode() == CBC:

if self.getIV():

iv = self.__String_to_BitList(self.getIV())

else:

raise ValueError("For CBC mode, you must supply the Initial Value (IV) for ciphering")

# Split the data into blocks, crypting each one seperately

i = 0

dict = {}

result = []

#cached = 0

#lines = 0

while i < len(data):

# Test code for caching encryption results

#lines += 1

#if dict.has_key(data[i:i+8]):

#print "Cached result for: %s" % data[i:i+8]

#      cached += 1

#      result.append(dict[data[i:i+8]])

#      i += 8

#      continue

block = self.__String_to_BitList(data[i:i+8])

# Xor with IV if using CBC mode

if self.getMode() == CBC:

if crypt_type == des.ENCRYPT:

block = list(map(lambda x, y: x ^ y, block, iv))

#j = 0

#while j < len(block):

#      block[j] = block[j] ^ iv[j]

#      j += 1

processed_block = self.__des_crypt(block, crypt_type)

if crypt_type == des.DECRYPT:

processed_block = list(map(lambda x, y: x ^ y, processed_block, iv))

#j = 0

#while j < len(processed_block):

#      processed_block[j] = processed_block[j] ^ iv[j]

#      j += 1

iv = block

else:

iv = processed_block

else:

processed_block = self.__des_crypt(block, crypt_type)

# Add the resulting crypted block to our list

#d = self.__BitList_to_String(processed_block)

#result.append(d)

result.append(self.__BitList_to_String(processed_block))

#dict[data[i:i+8]] = d

i += 8

# print "Lines: %d, cached: %d" % (lines, cached)

# Return the full crypted string

if _pythonMajorVersion < 3:

return ''.join(result)

else:

return bytes.fromhex('').join(result)

def encrypt(self, data, pad=None, padmode=None):

"""encrypt(data, [pad], [padmode]) -> bytes

data : Bytes to be encrypted

pad  : Optional argument for encryption padding. Must only be one byte

padmode : Optional argument for overriding the padding mode.

The data must be a multiple of 8 bytes and will be encrypted

with the already specified key. Data does not have to be a

multiple of 8 bytes if the padding character is supplied, or

the padmode is set to PAD_PKCS5, as bytes will then added to

ensure the be padded data is a multiple of 8 bytes.

"""

data = self._guardAgainstUnicode(data)

if pad is not None:

pad = self._guardAgainstUnicode(pad)

data = self._padData(data, pad, padmode)

return self.crypt(data, des.ENCRYPT)

def decrypt(self, data, pad=None, padmode=None):

"""decrypt(data, [pad], [padmode]) -> bytes

data : Bytes to be encrypted

pad  : Optional argument for decryption padding. Must only be one byte

padmode : Optional argument for overriding the padding mode.

The data must be a multiple of 8 bytes and will be decrypted

with the already specified key. In PAD_NORMAL mode, if the

optional padding character is supplied, then the un-encrypted

data will have the padding characters removed from the end of

the bytes. This pad removal only occurs on the last 8 bytes of

the data (last data block). In PAD_PKCS5 mode, the special

padding end markers will be removed from the data after decrypting.

"""

data = self._guardAgainstUnicode(data)

if pad is not None:

pad = self._guardAgainstUnicode(pad)

data = self.crypt(data, des.DECRYPT)

return self._unpadData(data, pad, padmode)

#############################################################################

#                              Triple DES                                  #

#############################################################################

class triple_des(_baseDes):

"""Triple DES encryption/decrytpion class

This algorithm uses the DES-EDE3 (when a 24 byte key is supplied) or

the DES-EDE2 (when a 16 byte key is supplied) encryption methods.

Supports ECB (Electronic Code Book) and CBC (Cypher Block Chaining) modes.

pyDes.des(key, [mode], [IV])

key  -> Bytes containing the encryption key, must be either 16 or

24 bytes long

mode -> Optional argument for encryption type, can be either pyDes.ECB

(Electronic Code Book), pyDes.CBC (Cypher Block Chaining)

IV  -> Optional Initial Value bytes, must be supplied if using CBC mode.

Must be 8 bytes in length.

pad  -> Optional argument, set the pad character (PAD_NORMAL) to use

during all encrypt/decrpt operations done with this instance.

padmode -> Optional argument, set the padding mode (PAD_NORMAL or

PAD_PKCS5) to use during all encrypt/decrpt operations done

with this instance.

"""

def __init__(self, key, mode=ECB, IV=None, pad=None, padmode=PAD_NORMAL):

_baseDes.__init__(self, mode, IV, pad, padmode)

self.setKey(key)

def setKey(self, key):

"""Will set the crypting key for this object. Either 16 or 24 bytes long."""

self.key_size = 24  # Use DES-EDE3 mode

if len(key) != self.key_size:

if len(key) == 16: # Use DES-EDE2 mode

self.key_size = 16

else:

raise ValueError("Invalid triple DES key size. Key must be either 16 or 24 bytes long")

if self.getMode() == CBC:

if not self.getIV():

# Use the first 8 bytes of the key

self._iv = key[:self.block_size]

if len(self.getIV()) != self.block_size:

raise ValueError("Invalid IV, must be 8 bytes in length")

self.__key1 = des(key[:8], self._mode, self._iv,

self._padding, self._padmode)

self.__key2 = des(key[8:16], self._mode, self._iv,

self._padding, self._padmode)

if self.key_size == 16:

self.__key3 = self.__key1

else:

self.__key3 = des(key[16:], self._mode, self._iv,

self._padding, self._padmode)

_baseDes.setKey(self, key)

# Override setter methods to work on all 3 keys.

def setMode(self, mode):

"""Sets the type of crypting mode, pyDes.ECB or pyDes.CBC"""

_baseDes.setMode(self, mode)

for key in (self.__key1, self.__key2, self.__key3):

key.setMode(mode)

def setPadding(self, pad):

"""setPadding() -> bytes of length 1. Padding character."""

_baseDes.setPadding(self, pad)

for key in (self.__key1, self.__key2, self.__key3):

key.setPadding(pad)

def setPadMode(self, mode):

"""Sets the type of padding mode, pyDes.PAD_NORMAL or pyDes.PAD_PKCS5"""

_baseDes.setPadMode(self, mode)

for key in (self.__key1, self.__key2, self.__key3):

key.setPadMode(mode)

def setIV(self, IV):

"""Will set the Initial Value, used in conjunction with CBC mode"""

_baseDes.setIV(self, IV)

for key in (self.__key1, self.__key2, self.__key3):

key.setIV(IV)

def encrypt(self, data, pad=None, padmode=None):

"""encrypt(data, [pad], [padmode]) -> bytes

data : bytes to be encrypted

pad  : Optional argument for encryption padding. Must only be one byte

padmode : Optional argument for overriding the padding mode.

The data must be a multiple of 8 bytes and will be encrypted

with the already specified key. Data does not have to be a

multiple of 8 bytes if the padding character is supplied, or

the padmode is set to PAD_PKCS5, as bytes will then added to

ensure the be padded data is a multiple of 8 bytes.

"""

ENCRYPT = des.ENCRYPT

DECRYPT = des.DECRYPT

data = self._guardAgainstUnicode(data)

if pad is not None:

pad = self._guardAgainstUnicode(pad)

# Pad the data accordingly.

data = self._padData(data, pad, padmode)

if self.getMode() == CBC:

self.__key1.setIV(self.getIV())

self.__key2.setIV(self.getIV())

self.__key3.setIV(self.getIV())

i = 0

result = []

while i < len(data):

block = self.__key1.crypt(data[i:i+8], ENCRYPT)

block = self.__key2.crypt(block, DECRYPT)

block = self.__key3.crypt(block, ENCRYPT)

self.__key1.setIV(block)

self.__key2.setIV(block)

self.__key3.setIV(block)

result.append(block)

i += 8

if _pythonMajorVersion < 3:

return ''.join(result)

else:

return bytes.fromhex('').join(result)

else:

data = self.__key1.crypt(data, ENCRYPT)

data = self.__key2.crypt(data, DECRYPT)

return self.__key3.crypt(data, ENCRYPT)

def decrypt(self, data, pad=None, padmode=None):

"""decrypt(data, [pad], [padmode]) -> bytes

data : bytes to be encrypted

pad  : Optional argument for decryption padding. Must only be one byte

padmode : Optional argument for overriding the padding mode.

The data must be a multiple of 8 bytes and will be decrypted

with the already specified key. In PAD_NORMAL mode, if the

optional padding character is supplied, then the un-encrypted

data will have the padding characters removed from the end of

the bytes. This pad removal only occurs on the last 8 bytes of

the data (last data block). In PAD_PKCS5 mode, the special

padding end markers will be removed from the data after

decrypting, no pad character is required for PAD_PKCS5.

"""

ENCRYPT = des.ENCRYPT

DECRYPT = des.DECRYPT

data = self._guardAgainstUnicode(data)

if pad is not None:

pad = self._guardAgainstUnicode(pad)

if self.getMode() == CBC:

self.__key1.setIV(self.getIV())

self.__key2.setIV(self.getIV())

self.__key3.setIV(self.getIV())

i = 0

result = []

while i < len(data):

iv = data[i:i+8]

block = self.__key3.crypt(iv,    DECRYPT)

block = self.__key2.crypt(block, ENCRYPT)

block = self.__key1.crypt(block, DECRYPT)

self.__key1.setIV(iv)

self.__key2.setIV(iv)

self.__key3.setIV(iv)

result.append(block)

i += 8

if _pythonMajorVersion < 3:

data = ''.join(result)

else:

data = bytes.fromhex('').join(result)

else:

data = self.__key3.crypt(data, DECRYPT)

data = self.__key2.crypt(data, ENCRYPT)

data = self.__key1.crypt(data, DECRYPT)

return self._unpadData(data, pad, padmode)

CBC那裡庫函式好像有點問題……於是直接呼叫的ECB模式,然後自己寫的CBC,如下,同時可寫出逆運算。des_cbc_decrypt之後的hex encode也是先編碼低四位,後編碼高四位,所以需要更改一下變成16進位制的演算法

import pyDes

from pwn import xor

from gmpy2 import isqrt_rem

def hexc(inp):

inp = inp.encode("hex")

return "".join(inp[i + 1] + inp[i] for i in range(0, len(inp), 2))

def ihexc(inp):

inp = format(inp, 'x')

inp = '0' * (len(inp) % 2) + inp

return "".join(inp[i + 1] + inp[i] for i in range(0, len(inp), 2)).decode("hex")

def compute(inp):

des = pyDes.des("*2017*10")

rest = len(inp) % 8

if rest:

inp += "\x00" * (8 - rest)

inp = [inp[i:i+8] for i in range(0, len(inp), 8)]

iv = "\x00" * 8

out = [iv]

for i in range(len(inp)):

out.append(des.decrypt(xor(inp[i], out[i][::-1])))

return hexc("".join(out[1:]))

def inv_compute(inp):

des = pyDes.des("*2017*10")

inp = ihexc(inp)

ap = len(inp) % 8

if ap:

inp = "\x00" * (8 - ap) + inp

inp = [inp[i:i+8] for i in range(0, len(inp), 8)]

iv = "\x00" * 8

out = []

for i in range(len(inp)):

out.append(xor(des.encrypt(inp[i]), iv[::-1]))

iv = inp[i]

return "".join(out).rstrip("\x00")

DES 這部分花的時間大概最長……因為C++的STL也不是很好除錯……所以只能一輪一輪的對……看是不是改對了……

二. LuaJIT

我剛開始沒有直接看MIRACL,因為感覺太煩了……(看見了類似傅立葉變換的東西),所以從最後的lua開始繼續逆向,lua位元組碼和0x5A異或了,這裡直接idapython dump出來異或一下就好了,然後得到的是LuaJIT的bytecode(注意不是lua),位元組碼的enum定義我沒有找到……所以主要用了這兩個工具。由於之前從來沒研究過lua……所以還是花了不少時間的

https://github.com/viruscamp/luadec

看起來好像大家都在用的一個反編譯器,可得到原始碼,缺點:由於是JIT的緣故,所以得不到在函式中使用全域性變數引用,導致某些地方看起來很詭異……

而且好像必須要在同一個目錄下將檔案命名為test.lua才可以執行……(貌似可以改AutoIt指令碼,不過懶……)

看雪.騰訊TSRC 2017 CTF 秋季賽 第六題點評及解析思路

得到的反編譯程式碼大概長這個德行……搜尋xyByInt,可以找到這個http://blog.csdn.net/xianyun2009/article/details/44796823,貌似是lua的某個大整數實現

經過精簡後,得到的程式碼大概是這樣的

function show(a)

print(get(a))

end

function get(a)

s = {a[#a]}

for i=#a-1, 1, -1 do

table.insert(s, string.format("%04d", a[i]))

end

return table.concat(s, "")

end

function create(s)

if s["xyBitInt"] == true then return s end

n, t, a = math.floor(#s/4), 1, {}

a["xyBitInt"] = true

if #s%4 ~= 0 then a[n + 1], t = tonumber(string.sub(s, 1, #s%4), 10), #s%4 + 1 end

for i = n, 1, -1 do a[i], t= tonumber(string.sub(s, t, t + 3), 10), t + 4 end

return a

end

function add(a,b)

local var_3_2 = create(a)

var_3_3 = create(b)

c = create("0")

t =  0

local var_3_1 = var_3_3

local var_3_0 = var_3_2

for var_3_5 =  1 , math.max(c, var_3_5), 1  do --location 0022, loop ends at 0048-1

if not unknown2 then

--jump to 0028 (if previous if statement is false)

--location 0028

if not unknown3 then

--jump to 0033 (if previous if statement is false)

--location 0033

t =  (  t +  0  +  0  )

local var_3_7 = t % uget_3_0

local var_3_8 = math.floor( t / uget_3_0 )

t = var_3_8

c[var_3_5] = var_3_7

end --location 0047, loops back to 0023-1

if t ~= 0 then

--jump to 0068 (if previous if statement is false)

repeat

var_3_4 = t % uget_3_0

local var_3_5 = math.floor( t / uget_3_0 )

t = var_3_5

c[ c +  1 ] = var_3_4

--jump to 0048 (if previous if statement is false)

--location 0068

until false or (previous if statement is true) --location 0068

return c

end

function bsub(a, b)

a, b, c, t = create(a), create(b), create("0"), 0

for i = 1, #a do

c[i] = a[i] - t - (b[i] or 0)

if c[i] < 0 then t, c[i] = 1, c[i] + mod  else t = 0 end

end

return c

end

function by(a, b)

a, b, c, t = create(a), create(b), create("0"), 0

for i = 1, #a do

for j = 1, #b do

t = t + (c[i + j - 1] or 0) + a[i] * b[j]

c[i + j - 1], t = t%mod, math.floor(t / mod)

end

if t ~= 0 then c[i + #b], t = t + (c[i + #b] or 0), 0 end

end

return c

end

function myst(xut)

c = add(xut, "1001")

d =  0

for i =  1 , 100 , 1  do --location 0011, loop ends at 0028-1

for j =  1 , 100 , 1  do --location 0015, loop ends at 0022-1

c = add(c, "1001")

end --location 0021, loops back to 0016-1

c = add(c, "10101")

end --location 0027, loops back to 0012-1

c = by(c, "983751509373")

for i =  1 , 13 , 1  do --location 0036, loop ends at 0073-1

c = bsub(c, "1023")

for j =  1 , 14 , 1  do --location 0045, loop ends at 0072-1

c = bsub(c, "1203")

for k =  1 , 15 , 1  do --location 0054, loop ends at 0071-1

c = bsub(c, "1230")

for l =  1 , 16 , 1  do --location 0063, loop ends at 0070-1

c = bsub(c, "1231")

--until false or (previous if statement is true) --location 0068

end --location 0069, loops back to 0064-1

end --location 0070, loops back to 0055-1

end --location 0071, loops back to 0046-1

end --location 0072, loops back to 0037-1

c = add(c, "1")

c = by(c, "2")

e = get(c)

lene = string.len(e)

leny = string.len(uget_6_0)

if lene == leny then

--jump to 0129 (if previous if statement is false)

i =  1

d =  1

if i <= lene then

--jump to 0129 (if previous if statement is false)

repeat

var_6_0 = string.byte(e, i)

local var_6_1 = string.byte(uget_6_0, i)

if var_6_0 ~= var_6_1 then

--jump to 0125 (if previous if statement is false)

d =  0

--jump to 0129 (if previous if statement is false)

i =  i +  1

--jump to 0105 (if previous if statement is false)

until false or (previous if statement is true) --location 0129

return d

end

function add(INPUT_VAR_0_)

return  INPUT_VAR_0_ + INPUT_VAR_1_

end

function sub(INPUT_VAR_0_)

return  INPUT_VAR_0_ - INPUT_VAR_1_

end

function someFunc9()

local var_9_0 = 10000 --var_9_0 NUMBER-NUMBER

local var_9_1 = "1574592838300862641516215149137548264158058079230003764126382984039489925466995870724568174393389905601620735902909057604303543552180706761904" --strings longer than 40 characters get cut

end

還有一些不重要的部分沒有化簡,不過可以大概看出lua的邏輯了,執行多次運算後,將其化為2進位制的字串和var_9_1做比較。這裡剛開始判斷錯了add,以為是sub,所以寫出來的程式碼不對……然後手動的去更改luaJIT的位元組碼,在get那裡直接return,然後用Lua的C API寫了個小的wrapper來測試

#include

#include "lua.h"

#include "lualib.h"

#include "lauxlib.h"

/* Convenience stuff */

static void close_state(lua_State **L) { lua_close(*L); }

#define cleanup(x) __attribute__((cleanup(x)))

#define auto_lclose cleanup(close_state)

int main(int argc, char *argv[])

{

/* Create VM state */

auto_lclose lua_State *L = luaL_newstate();

if (!L)

return 1;

luaL_openlibs(L); /* Open standard libraries */

/* Load config file */

if (argc > 1) {

luaL_loadfile(L, argv[1]); /* (1) */

//luaJIT_setmode(L, 0, 0);

int ret = lua_pcall(L, 0, 0, 0);

if (ret != 0) {

fprintf(stderr, "%s\n", lua_tostring(L, -1));

return 1;

}

}

lua_pushstring(L, argv[2]);

lua_setfield(L, -10002, "xut");

lua_getfield(L, -10002, "myst");

lua_pcall(L, 0, 1, 0);

printf("%s\n", lua_tolstring(L, -1, 0));

lua_settop(L, 0); /* (4) */

return 0;

}

這裡簡單說一下patch luaJIT位元組碼的過程,不知道位元組碼的enum很頭疼……於是上網搜尋的途中找到了這個有用的工具https://github.com/franko/luajit-lang-toolkit, 其中run.lua的-bx功能可以檢視lua位元組碼對應的16進位制位元組是什麼……讓更改位元組碼變得簡單了許多,效果大概是這個樣子

看雪.騰訊TSRC 2017 CTF 秋季賽 第六題點評及解析思路

如果要增加一個kgc的話,需要更改的地方有kgc的長度和這段proto的總長度,其中proto總長度是U128LE編碼,然後大概學習了一下怎麼寫luaJIT位元組碼……後來發現自己把add看成了sub以後解決問題就沒啥用了……總的來說lua位元組碼還是挺難除錯的……不知道有沒有好的方法……之後可以考慮寫個hook的工具之類的……

第一次逆向luaJIT,感覺還是挺有趣的,學到了不少東西,luaJIT程式碼逆向出來後的逆運算如下

def lua(target):

target = int(target) / 2 - 1

for i in range(13):

for j in range(14):

for k in range(15):

for l in range(16):

target += 1231

target += 1230

target += 1203

target += 1023

target /= 983751509373

for i in range(100):

target -= 10101

for j in range(100):

target -= 1001

target -= 1001

return target

可以看出並不是很困難……大概是當時比較困……所以出了各種差錯……

三. MIRACL

看雪.騰訊TSRC 2017 CTF 秋季賽 第六題點評及解析思路

攻破了luaJIT和des以後,就只剩下我們最後的MIRACL了……首先我們來看看他都做了什麼

首先把我們des_cbc_decrypt之後的結果16進位制化以後變成了一個mirvar的大整數,然後乘了個173,之後做了fft_mult(使用傅立葉變換進行快速大數乘法)1817,以後做了某個計算(其實是除法,這個之後我們說怎麼識別出來的)

看起來很簡單對不對……結果怎麼算怎麼不對……fft_mult那裡的輸出和輸入永遠對不上……(輸入的變換也不是線性的……所以只能逆向了)於是考慮可能fft_mult被更改過了……檢視fft_mult

之前都一樣……最後居然多出了兩行!

看雪.騰訊TSRC 2017 CTF 秋季賽 第六題點評及解析思路

看雪.騰訊TSRC 2017 CTF 秋季賽 第六題點評及解析思路

然後通過olly根據輸入輸出猜測,得到第一個是平方的演算法,第二個根據bindiff得到是decr(減法,不過內建了第二個減數為1001)

於是fft_mult的實際演算法是 (inp * 1817)^2 - 1001……嗯……怪不得怎麼都算不對

最後還差divide那個函式,那個函式長這樣……

看雪.騰訊TSRC 2017 CTF 秋季賽 第六題點評及解析思路

嗯……看了一眼就不想看了……遂祭出olly的連蒙帶猜的的方法……輸入了幾組資料,觀察輸出

輸入:1

輸出:713

輸入:2

輸出:436

輸入:3

輸出:159

嗯……這個好像第二個反過來再除以輸入就是317啊……驗證了一下以後還真是

至此,我們已將所有程式逆向完畢……完整指令碼如下

import pyDes

from pwn import xor

from gmpy2 import isqrt_rem

def hexc(inp):

inp = inp.encode("hex")

return "".join(inp[i + 1] + inp[i] for i in range(0, len(inp), 2))

def ihexc(inp):

inp = format(inp, 'x')

inp = '0' * (len(inp) % 2) + inp

return "".join(inp[i + 1] + inp[i] for i in range(0, len(inp), 2)).decode("hex")

def compute(inp):

des = pyDes.des("*2017*10")

rest = len(inp) % 8

if rest:

inp += "\x00" * (8 - rest)

inp = [inp[i:i+8] for i in range(0, len(inp), 8)]

iv = "\x00" * 8

out = [iv]

for i in range(len(inp)):

out.append(des.decrypt(xor(inp[i], out[i][::-1])))

return hexc("".join(out[1:]))

def inv_compute(inp):

des = pyDes.des("*2017*10")

inp = ihexc(inp)

ap = len(inp) % 8

if ap:

inp = "\x00" * (8 - ap) + inp

inp = [inp[i:i+8] for i in range(0, len(inp), 8)]

iv = "\x00" * 8

out = []

for i in range(len(inp)):

out.append(xor(des.encrypt(inp[i]), iv[::-1]))

iv = inp[i]

return "".join(out).rstrip("\x00")

def lua(target):

target = int(target) / 2 - 1

for i in range(13):

for j in range(14):

for k in range(15):

for l in range(16):

target += 1231

target += 1230

target += 1203

target += 1023

target /= 983751509373

for i in range(100):

target -= 10101

for j in range(100):

target -= 1001

target -= 1001

return target

assert(compute("1122334455667788").upper() == "BEC20560FEB6FCEFF93B5F94B3CF259D")

assert(compute("1234567890abcdef").upper() == "C7BF2111CD9A94A30E31F260D9BF9432")

n1 = 0xad

n2 = 0x719

target = "1574592838300862641516215149137548264158058079230003764126382984039489925466995870724568174393389905601620735902909057604303543552180706761904"

target = lua(target)

target = int(str(target)[::-1])

assert(target % 317 == 0)

target /= 317

target += 1001

res, rem = isqrt_rem(target)

assert(int(rem) == 0)

prod = (n2 * n1)

assert(res % prod == 0)

res /= prod

print inv_compute(res)

"""

inp = "0011223344556677"

num1 = int(compute(inp), 16)

num1 *= n1

print hex(num1 * n2)

"""

總結

這題說難也不難……就是很繁瑣……而且考察的知識點比較雜……附上我的IDA6.95 idb和解題用的全部程式碼……感興趣的人可以看一眼……

看雪.騰訊TSRC 2017 CTF 秋季賽 第一題點評及解析思路

看雪.騰訊TSRC 2017 CTF 秋季賽 第二題點評及解析思路

看雪.騰訊TSRC 2017 CTF 秋季賽 第三題點評及解析思路

看雪.騰訊TSRC 2017 CTF 秋季賽 第四題點評及解析思路

看雪.騰訊TSRC 2017 CTF 秋季賽10月24日開賽,神祕大獎揭曉!

看雪.騰訊TSRC 2017 CTF 秋季賽 明日開賽!你不得不知的參賽指南

【火熱報名中】看雪Android 安全訓練營,11月17號開課啦!


相關文章