看雪.纽盾 KCTF 2019 Q2 | 第四题点评及解题思路

栏目: 编程语言 · 发布时间: 5年前

内容简介:连日的阴雨之后,上海的天气终于放晴,今天太阳露出明媚的笑脸,似乎我们的心情也变得更轻松舒畅些。前三道题的题目解析我们已于看雪学院公众号平台持续放出,有疑惑的小伙伴请点击文章末尾原文链接。今天我们对第四道题进行题目解析,一起来破解神秘的达芬奇密码。

连日的阴雨之后,上海的天气终于放晴,今天太阳露出明媚的笑脸,似乎我们的心情也变得更轻松舒畅些。

前三道题的题目解析我们已于看雪学院公众号平台持续放出,有疑惑的小伙伴请点击文章末尾原文链接。

今天我们对第四道题进行题目解析,一起来破解神秘的达芬奇密码。

题目简介

恭喜你成功进入金字塔,但这并不是结束。一阵冷风慢慢的由脚底爬到背后,让人不由得一哆嗦,映入眼帘的是两排高耸的圆形柱子,柱子上刻着古埃及的种种神话与传说。空气中弥漫着寂静的味道,只能听到你的脚步声和火把上火苗孜孜爆裂的声音。

沿着这排柱子走去,看到了道路那头坐着一扇长方形的门。门上刻着一串古埃及文字:“open the door”,下方是一串密密麻麻的数字和符号......能量宝石就藏在门后,只有破解这些密码,才能打开这扇门,成功获取能量宝石。

看雪.纽盾 KCTF 2019 Q2 | 第四题点评及解题思路

本题围观人数为2168人,观者众多,攻破人数和前三道题相比有所减少,数量为28人,纵观全局,在十道题之中攻破人数和难度都属于中等水平。

攻破此题的战队排名一览:

看雪.纽盾 KCTF 2019 Q2 | 第四题点评及解题思路

看雪评委crownless点评

本题有两个门槛,一个是变形,一个是序列号算法。变形非常的简单,追求的是趣味。算法的难点又有二,一个难点是还原算法的方程,另一个难点是求一个二元二次方程符合条件的整数解。

设计思路

本题出题战队 兔斯基保护协会:

看雪.纽盾 KCTF 2019 Q2 | 第四题点评及解题思路

题目设计

本题有两个门槛,一个是变形,一个是序列号算法。

先说 变形部分

变形非常的简单,追求的是趣味。还有就是拦截一些直接IDA静态分析,过于草率的认为找到了序列号判断函数的玩家。

判断序列号是否有效的函数为TestKey,这个函数一开始的内容就是一个一般复杂的序列号判断程序(事实上解它的难度高于真实的判断逻辑)。程序运行之后,在初始化对话框的时候,会读取一块内存,解密后覆盖TestKey的代码内存,从而彻底的改变TestKey的逻辑。这就是本题简单的变形过程。

再说 序列号算法部分

只要程序跑起来,玩家就可以得到序列号算法的全部代码了。此处无混淆,除算法本身之外没有别的坑。

算法的难点又有二,一个难点是还原算法的方程,这个认真去逆或者有些“猜”的悟性可以很快得出方程。另一个难点是解方程,这个方程就是求一个二元二次方程符合条件的整数解。

方程为:x*x - 7y*y = 8

(参数是我深思熟虑反复调整的。这个方程的解序列对初始参数即为敏感,不会因为看起来简单而简单)

解的要求是:x长度为正好8字节,满足条件的有两组解,通过首位的判断把较大解排除,正确答案为较小的那个。从而避免多解。本题采用的大数算法排除了负解的可能性。(如果有人发现多解,我穿女装发自拍到群里)

至于方程的解怎么限定为0-9、A-Z、a-z的。我把用户的初始输入和预设字节流亦或了一下,以保证玩家要输入的正确序列号符合题目要求。

通过“输入在亦或后符合比赛要求”来排除部分输入后,蛮力搜索依然在复杂度上不可行。

常规堆题。简单直接的功能题,malloc,free,edit,show功能。同样直接的堆单字节溢出。知识点unlink+堆溢出。

功能分析

show功能默认不可用。

edit功能默认可用一次。

malloc功能申请0x30大小堆块。单字节溢出。在程序init时提前申请了一个堆块。之后malloc功能申请出的地址必须在此堆块地址附近。直接给出heap地址。

破解思路

变形部分的破解太容易,只要程序跑起来,界面初始化完毕,变形就已经结束了。在GetDlgItemTextW下个断点,就能跑到确定长度的代码和判断序列号有效性的代码。喜欢dump出来慢慢分析,还是手动调试,看玩家口味。

然后,玩家发现长度限定为16的字符,通过判断序列号的函数逆出判断方程x*x - 7y*y = 8.这个方程,凭借蛮力枚举是解不了的。

有两种解法,

第一种解法

先枚举较小的解,排成一列,分析其规律~

最小的解(x,y)为(6,2).

其它解从小到大为:(90,34), (1434,542)...

有耐心的玩家会觉得,这有个递推公式吧?

试一试,构造一下。

发现递推公式是:

x[k + 1] = 8x[k] + 21y[k]

y[k + 1] = 3x[k] + 8y[k]

是不是很线性~

x[0],y[0]就是6.2.

15次递推之内就能得出本题要求的结果~

第二种解法

用线性代数方法求X[k+1]=M*X[k],X为解序列的数对,M为2*2待定系数的矩阵,把(6,2)(90,34)(1434,542)代入确定系数,绝大多数递推公式都能这么不浪漫的机械求出。

只要用户想到从序列找规律,就不难了。所以本题叫序列探秘(Sequence Adventure),这本身也起到提示作用。

解题思路

本题解题思路由看雪论坛   htg   提供:

看雪.纽盾 KCTF 2019 Q2 | 第四题点评及解题思路

工具:IDA、 Python 、Notepad++、Resource_Hacker

一、题目主要流程

1、获取用户输入字符串sn:16位

2、解密函数代码sub_4010E0

3、进入 sub_4010E0内

4、将输入字符串sn分割为两个8位字符串,变成两个超大整数sn[0]、sn[1],各8个字节长

5、两个超大整数与内置的两个超大整数异或运算,得到新的两个超大整数 X、Y

6、检测:X、Y内各个字节不能有00存在:目的是为了保证高位字节不能为00,也就是避免了多解的可能

7、检测:第7个字节(从0计算),需为01至0F之间,经过异或运算0x64还原,该输入字节只能为0x60-0x6F:即  ` a b c ^ o

8、检测:X^2 - 7 * Y^2 =8;这个是重点,丢番图方程,通过搜索网页,才知道这个方程解法

9、通过,即为正确字符串,L3mZ2k9aZ0a36DMM

二、解题思路

1、还原 sub_4010E0 ,Patch掉主程序,便于静态分析

1.1、找到关键CALL:004010E0

1.2、004010E0是由从00567B8拷贝0x330字节

1.3、00567B8的内容是通过XOR 0xAB算出来的

1.4、Patch文件,便于静态分析

1.4.1、修改 XOR 地址处的汇编为90

1.4.2、修改 00567B8的内容为解密的内容,即算出0xAB后,复原

2、学习丢番图方程的通解 X^2 - 7 * Y^2 =8 :找到满足条件的解

具体可以学习丢番图方程的解法,该通解是:( 6 + 2 * sqrt(7) ) ( 8 + 3 *  sqrt(7) )^n;我们可以通过程序来列出在一定范围的解

3、根据上述流程,反推输入字符串

三、主要源代码分析过程

1、IDA内找到软件入口:

因为是MFC程序,所以我们要找到按钮的触发方法才行。

方法:找到Button等控件的ID,通过 Resource_Hacker查找,打开Dialog表,

102 DIALOGEX 0, 0, 319, 117
STYLE DS_FIXEDSYS | DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION
EXSTYLE WS_EX_APPWINDOW
CAPTION "SequenceAdventure (CTF2019)"
LANGUAGE LANG_CHINESE, 0x2
FONT 9, "MS Shell Dlg"
{
 CONTROL "Check", 1, BUTTON, BS_DEFPUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 7, 90, 305, 17  CONTROL "", 1000, EDIT, ES_LEFT | ES_AUTOHSCROLL | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP, 7, 18, 305, 67 
 CONTROL "Please Input The Serial:", -1, STATIC, SS_LEFT | WS_CHILD | WS_VISIBLE | WS_GROUP, 7, 5, 123, 12  CONTROL "Close", 1002, BUTTON, BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 241, 1, 50, 14 
}

我们能发现 Button Check的ID为1(估计是作者故意的,搞那么小);附近有一个Close,其ID是1002(0x3EA),我们通过它来找到对应的dialog。

IDA:Search 搜索立即数:0x3EA,很快就在.rdata里定位到具体内容

大佬一般看到后,就能很快找到对应的处理方法,我们可以先添加具体的消息处理结果,便于理解,添加两个结构 AFX_MSGMAP_ENTRY和AFX_MSGMAP,主要先后添加:IDA→View→ Sub Views→Local Types:按Insert键,先后插入两个消息定义。

struct AFX_MSGMAP_ENTRY
{
 UINT nMessage;
 UINT nCode;
 UINT nID;
 UINT nLastID;
 UINT_PTR nSig;
 void (*pfn)(void);
};
 
struct AFX_MSGMAP
{
 const AFX_MSGMAP *(__stdcall *pfnGetBaseMap)();
 const AFX_MSGMAP_ENTRY *lpEntries;
};

选中刚才定义的消息(拖入到最下面,能看到最新添加的),然后右键,同步到 idb。

现在定位到刚才的 .rdata 的0x3EA处。

.rdata:005456D8 db 11h
.rdata:005456D9 db 1
.rdata:005456DA db 0
.rdata:005456DB db 0
.rdata:005456DC db 0
.rdata:005456DD db 0
.rdata:005456DE db 0
.rdata:005456DF db 0
.rdata:005456E0 db 0EAh
.rdata:005456E1 db 3
.rdata:005456E2 db 0
.rdata:005456E3 db 0
.rdata:005456E4 db 0EAh
.rdata:005456E5 db 3
.rdata:005456E6 db 0
.rdata:005456E7 db 0
.rdata:005456E8 db 39h ; 9
.rdata:005456E9 db 0
.rdata:005456EA db 0
.rdata:005456EB db 0
.rdata:005456EC dd offset sub_402000

在 .rdata:005456D8   按 Art +Q键盘,选择AFX_MSGMAP_ENTRY, 最后,形成的结构如下所述:

.rdata:00545690 stru_545690 AFX_MSGMAP_ENTRY <0Fh, 0, 0, 0, 13h, offset sub_401DE0>
.rdata:00545690 ; DATA XREF: .rdata:stru_545708↓o
.rdata:005456A8 AFX_MSGMAP_ENTRY <37h, 0, 0, 0, 28h, offset sub_401E90>
.rdata:005456C0 AFX_MSGMAP_ENTRY <111h, 0, 1, 1, 39h, offset sub_401EA0>
.rdata:005456D8 AFX_MSGMAP_ENTRY <111h, 0, 3EAh, 3EAh, 39h, offset sub_402000>
.rdata:005456F0 AFX_MSGMAP_ENTRY <0>
.rdata:00545708 stru_545708 AFX_MSGMAP <offset sub_4055EC, offset stru_545690>

然后,我们比对一下刚才在 Resource_Hacker查找的Dialog内容,很容易定位到button的ID为1 ,即check对应的是.rdata:005456C0   AFX_MSGMAP_ENTRY <111h, 0, 1, 1, 39h, offset sub_401EA0>,然后我们双击 offset sub_401EA0 进入到该方法,按F5,很容易判断即为button check的处理方法。

int __thiscall btnCheck(CWnd *this) {
 CWnd *v1; // esi
 int i; // eax
 WCHAR inputStr; // [esp+Ch] [ebp-310h]
 char v5; // [esp+Eh] [ebp-30Eh]
 char newInputStr; // [esp+20Ch] [ebp-110h]
 char v7; // [esp+20Dh] [ebp-10Fh]
 DWORD v8; // [esp+30Ch] [ebp-10h]
 CWnd *v9; // [esp+310h] [ebp-Ch]
 int v10; // [esp+314h] [ebp-8h]
 DWORD flOldProtect; // [esp+318h] [ebp-4h]
 
 v1 = this;
 v9 = this;
 inputStr = 0;
 memset(&v5, 0, 0x1FEu);
 newInputStr = 0;
 memset(&v7, 0, 0xFFu);
 CWnd::GetDlgItemTextW(v1, 1000, &inputStr, 20);
 if ( wcslen(&inputStr) == 16 ) // 输入字符串长度:16
 {
 i = 0;
 while ( !(*(&inputStr + i) & 0xFF00) ) // 字符应该是 0001 至 00FF;不能为 FF00 和 0100 的宽字符
 {
 *(&newInputStr + i) = *((_BYTE *)&inputStr + 2 * i);
 if ( ++i >= 16 )
 {
 v8 = 0x40;
 flOldProtect = 0;
 VirtualProtect(sub_4010E0, 0xD17u, 0x40u, &flOldProtect);// 0x40:PAGE_EXECUTE_READWRITE:内存地址、长度、访问、保存旧的
 if ( GetLastError() )
 return CWnd::MessageBoxW(v1, L"Wrong!", 0, 0);
 qmemcpy(sub_4010E0, byte_5647B8, 0x330u);
 VirtualProtect(sub_4010E0, 0xD17u, flOldProtect, &v8);
 if ( !GetLastError() )
 {
 v10 = 0;
 v10 = sub_4010E0(&newInputStr);
 if ( v10 == 1 )
 return CWnd::MessageBoxW(v9, L"Congratulations! You are right!", 0, 0);
 }
 v1 = v9;
 return CWnd::MessageBoxW(v1, L"Wrong!", 0, 0);
 }
 }
 }
 return CWnd::MessageBoxW(v1, L"Wrong!", 0, 0);
}

2、分析主函数 sub_401EA0

里面有一个VirtualProtect(sub_4010E0, 0xD17u, 0x40u, &flOldProtect);这里有修改 qmemcpy(sub_4010E0, byte_5647B8, 0x330u);

很明显,它将 byte_5647B8内容覆盖了 sub_4010E0,继续跟进 byte_5647B8

.data:005647B8 ; char byte_5647B8[816]
.data:005647B8 2A byte_5647B8 db 2Ah ; DATA XREF: btnCheck+E0↑o
.data:005647B9 47 db 47h ; G
.data:005647BA 3B db 3Bh ; ;
.data:005647BB AB db 0ABh
.data:005647BC AB db 0ABh
.data:005647BD AB db 0ABh
.data:005647BE FD db 0FDh
.data:005647BF 1B db 1Bh
.data:005647C0 2A db 2Ah ; *
.data:005647C1 FC db 0FCh
.data:005647C2 20 db 20h
.data:005647C3 17 db 17h
.data:005647B8 2A byte_5647B8 db 2Ah ; DATA XREF: sub_401D80:loc_401DC0↑w
.data:005647B9 47 db 47h ; G
.data:005647BA 3B db 3Bh ; ;
.data:005647BB AB db 0ABh
.data:005647BC AB db 0ABh
.data:005647BD AB db 0ABh
.data:005647BE FD db 0FDh
.data:005647BF 1B db 1Bh
.data:005647C0 2A db 2Ah ; *
.data:005647C1 FC db 0FCh
.data:005647C2 20 db 20h
.data:005647C3 17 db 17h

很明显,这个不是代码,我们看看有哪些代码访问了它,按x,找到条目(我在原代码做了Patch,其实该代码处有两处引用),一处是check处理;一处是修改 byte_5647B8,我们跟进sub_401D80,查看:

signed int __thiscall sub_401D80(CDialog *this) {
 CDialog *v1; // esi
 unsigned int v2; // eax
 
 v1 = this;
 CDialog::OnInitDialog(this);
 SendMessageW(*((HWND *)v1 + 8), 0x80u, 1u, *((_DWORD *)v1 + 29));
 SendMessageW(*((HWND *)v1 + 8), 0x80u, 0, *((_DWORD *)v1 + 29));
 v2 = 0;
 do
 {
 byte_5647B8[v2] ^= 0xABu;
 ++v2;
 }
 while ( v2 < 0x330 );
 return 1;
}

它相当于对 byte_5647B8做了异或处理后又保存了,相当于原来进行了加密,现在进行了解密。

3、Patch代码:以使其更容易静态分析

我们做两件事:一是通过异或解密覆盖sub_4010E0;二是nop掉赋值语句。

3.1、 异或解密覆盖sub_4010E0,使用IDC代码处理。

static main(void)
{
 auto fp, begin, end, dexbyte, sourceAdd;
 sourceAdd = 0x5647B8;
 begin = 0x4010E0;
 end = begin + 0x330;
 for ( dexbyte = begin; dexbyte < end; dexbyte ++ , sourceAdd ++ )
 {
 PatchByte(dexbyte,Byte(sourceAdd) ^ 0xAB);
 }
}

3.2、 nop掉赋值语句

.text:00401F8A F3 A5 ===> 90 90//也可以将 text:00401F80- text:00401F8B处均nop  

.text:00401F48 8B 1D 10 D2 51 00 mov ebx, ds:VirtualProtect
.text:00401F4E 8D 45 FC lea eax, [ebp+flOldProtect]
.text:00401F51 50 push eax ; lpflOldProtect
.text:00401F52 6A 40 push 40h ; flNewProtect
.text:00401F54 68 17 0D 00 00 push 0D17h ; dwSize
.text:00401F59 68 E0 10 40 00 push offset sub_4010E0 ; lpAddress
.text:00401F5E C7 45 F0 40 00 00 00 mov [ebp+var_10], 40h
.text:00401F65 C7 45 FC 00 00 00 00 mov [ebp+flOldProtect], 0
.text:00401F6C FF D3 call ebx ; VirtualProtect
.text:00401F6E FF 15 08 D4 51 00 call ds:GetLastError
.text:00401F74 85 C0 test eax, eax
.text:00401F76 75 62 jnz short loc_401FDA
.text:00401F78 8B 55 FC mov edx, [ebp+flOldProtect]
.text:00401F7B B9 CC 00 00 00 mov ecx, 0CCh
.text:00401F80 90 nop
.text:00401F81 90 nop
.text:00401F82 90 nop
.text:00401F83 90 nop
.text:00401F84 90 nop
.text:00401F85 90 nop
.text:00401F86 90 nop
.text:00401F87 90 nop
.text:00401F88 90 nop
.text:00401F89 90 nop
.text:00401F8A 90 nop
.text:00401F8B 90 nop
.text:00401F8C 8D 4D F0 lea ecx, [ebp+var_10]
.text:00401F8F 51 push ecx ; lpflOldProtect
.text:00401F90 52 push edx ; flNewProtect
.text:00401F91 68 17 0D 00 00 push 0D17h ; dwSize
.text:00401F96 68 E0 10 40 00 push offset sub_4010E0 ; lpAddress

3.3、保存

IDA:Edit→Patch Program→Assembly

4、静态分析 sub_4010E0

F5分析伪代码

signed int __cdecl sub_4010E0(int a1)//a1为输入字符串,16字节 {
 signed int v1; // eax
 char v2; // cl
 signed int v3; // ecx
 signed int v4; // eax
 signed int v5; // eax
 signed int v6; // esi
 signed int v7; // ecx
 __int16 v8; // dx
 char *v9; // edi
 __int16 v10; // ax
 signed int v11; // eax
 signed int v12; // ecx
 unsigned __int16 v13; // bx
 signed int v14; // esi
 signed int v15; // ecx
 __int16 v16; // dx
 char *v17; // edi
 __int16 v18; // ax
 signed int v19; // eax
 signed int v20; // ecx
 unsigned __int16 v21; // bx
 unsigned int v22; // eax
 signed int v23; // ecx
 unsigned __int16 v24; // dx
 char v25; // dl
 signed int v26; // eax
 __int16 v27; // si
 int v28; // eax
 struc_1 v30; // [esp+8h] [ebp-90h]
 int v31; // [esp+1Ch] [ebp-7Ch]
 int v32; // [esp+20h] [ebp-78h]
 int v33; // [esp+24h] [ebp-74h]
 int v34[2]; // [esp+28h] [ebp-70h]
 int v35[4]; // [esp+30h] [ebp-68h]
 int v36[2]; // [esp+40h] [ebp-58h]
 struc_1 v37; // [esp+48h] [ebp-50h]
 struc_1 v38; // [esp+5Ch] [ebp-3Ch]
 struc_1 v39; // [esp+70h] [ebp-28h]
 struc_1 v40; // [esp+84h] [ebp-14h]
 
 v30.intArray[1] = 0x646E9881;
 v1 = 0;
 v30.intArray[0] = 0xE38C9616;
 v30.intArray[2] = 0x81DC0884;
 v30.intArray[3] = 0x4F484DBE;
 *(_DWORD *)&v30.charPtr = 0;
 v31 = 0;
 v32 = 0;
 v33 = 0;
 v34[0] = 0;
 v34[1] = 0;
 v36[0] = 0;
 v36[1] = 0;
 do
 {
 v2 = *((_BYTE *)&v30.intArray[2] + v1) ^ *(_BYTE *)(a1 + v1 + 8);
 *((_BYTE *)v34 + v1) = *((_BYTE *)v30.intArray + v1) ^ *((_BYTE *)v30.intArray + v1 + a1 - (_DWORD)&v30);
 *((_BYTE *)v36 + v1++) = v2;
 }//v34存放前8个字节异或结果;//v36存放后8个字节异或结果
 while ( v1 < 8 );
 v30.intArray[0] = 0;
 v39.intArray[0] = 0;
 v39.intArray[1] = 0;
 v39.intArray[2] = 0;
 v39.intArray[3] = 0;
 v39.charPtr = 0;
 v40.intArray[0] = 0;
 v40.intArray[1] = 0;
 v40.intArray[2] = 0;
 v40.intArray[3] = 0;
 v40.charPtr = 0;
 v37.intArray[0] = 0;
 v37.intArray[1] = 0;
 v37.intArray[2] = 0;
 v37.intArray[3] = 0;
 v37.charPtr = 0;
 v38.intArray[0] = 0;
 v38.intArray[1] = 0;
 v38.intArray[2] = 0;
 v38.intArray[3] = 0;
 v38.charPtr = 0;
 v30.intArray[1] = 0;
 v30.intArray[2] = 0;
 v30.intArray[3] = 0;
 v30.charPtr = 0;
 v3 = 8;
 LOBYTE(v30.intArray[0]) = 8;//v30=8
 v4 = 7;
 do
 {
 if ( *((_BYTE *)v34 + v4) )//异或后字节不能为00,避免多解
 break;
 --v3;
 --v4;
 }
 while ( v4 >= 0 );
 if ( v3 == 8 )
 {
 v5 = 7;
 do
 {
 if ( *((_BYTE *)v36 + v5) )//异或后字节不能为00,避免多解
 break;
 --v3;
 --v5;
 }
 while ( v5 >= 0 );
 if ( v3 == 8 && !(v34[1] & 0xF0000000) )//第7个字节(0开始计数)智能为a-o;貌似没有意义。可能是作者辅助添加去重措施,其实后面有一个解,其有不可输入字符,估计就排除了。
 {
 v6 = 0;
 do
 {
 v35[0] = 0;
 v35[1] = 0;
 v35[2] = 0;
 v35[3] = 0;
 v7 = 0;
 v8 = *((unsigned __int8 *)v34 + v6);
 v9 = (char *)v35 + v6;
 do
 {
 v10 = *((unsigned __int8 *)&v35[2] + v6) + v8 * *((unsigned __int8 *)v34 + v7);
 v9[v7] = *((_BYTE *)&v35[2] + v6) + v8 * *((_BYTE *)v34 + v7);
 ++v7;
 *((_BYTE *)&v35[2] + v6) = HIBYTE(v10);
 }
 while ( v7 < 8 );
 LOBYTE(v11) = 0;
 v12 = 0;
 do
 {
 v13 = (char)v11 + *((unsigned __int8 *)v39.intArray + v12 + v6) + (unsigned __int8)v9[v12];
 *((_BYTE *)v39.intArray + v12++ + v6) = v13;
 v11 = (signed int)v13 >> 8;
 }
 while ( v12 < 9 );
 ++v6;
 }
 while ( v6 < 8 );//执行完了后,v39=X^2(X、Y为分别为前、后8个字节异或后的整数)
 v14 = 0;
 do
 {
 v35[0] = 0;
 v35[1] = 0;
 v35[2] = 0;
 v35[3] = 0;
 v15 = 0;
 v16 = *((unsigned __int8 *)v36 + v14);
 v17 = (char *)v35 + v14;
 do
 {
 v18 = *((unsigned __int8 *)&v35[2] + v14) + v16 * *((unsigned __int8 *)v36 + v15);
 v17[v15] = *((_BYTE *)&v35[2] + v14) + v16 * *((_BYTE *)v36 + v15);
 ++v15;
 *((_BYTE *)&v35[2] + v14) = HIBYTE(v18);
 }
 while ( v15 < 8 );
 LOBYTE(v19) = 0;
 v20 = 0;
 do
 {
 v21 = (char)v19 + *((unsigned __int8 *)v40.intArray + v20 + v14) + (unsigned __int8)v17[v20];
 *((_BYTE *)v40.intArray + v20++ + v14) = v21;
 v19 = (signed int)v21 >> 8;
 }
 while ( v20 < 9 );
 ++v14;
 }
 while ( v14 < 8 );//执行完了后,v40=Y^2(X、Y为分别为前、后8个字节异或后的整数)
 LOBYTE(v22) = v37.charPtr;
 v23 = 0;
 do
 {
 v24 = (unsigned __int8)v22 + 7 * *((unsigned __int8 *)v40.intArray + v23);
 *((_BYTE *)v37.intArray + v23++) = v24;
 v22 = (unsigned int)v24 >> 8;
 }
 while ( v23 < 17 );////执行完了后,v37=7*Y^2(X、Y为分别为前、后8个字节异或后的整数)
 v37.charPtr = HIBYTE(v24);
 v25 = 0;
 v26 = 0;
 do
 {
 v27 = *((unsigned __int8 *)v39.intArray + v26) - *((unsigned __int8 *)v37.intArray + v26) - v25;
 *((_BYTE *)v38.intArray + v26) = v27;
 if ( v27 < 0 )
 v25 = 1;
 ++v26;
 }
 while ( v26 < 17 );///执行完了后,v38=X^2 - 7*Y^2(X、Y为分别为前、后8个字节异或后的整数)
 if ( !v25 )
 {
 v28 = 0;
 while ( *((_BYTE *)v38.intArray + v28) == *((_BYTE *)v30.intArray + v28) )
 {
 if ( ++v28 >= 17 )
 return 1;
 }//判断方程X^2 - 7*Y^2 = 8,那么就要求解 X 和 Y
 }
 }
 }
 return 0;
}

5、求解X^2 - 7*Y^2 = 8,并反推字符串序列号。

# -*- coding: UTF-8 -*-
import re
import array
 
#丢番图方程 X^2 - D * Y^2 = M
#丢番图方程 X^2 - 7 * Y^2 = 8
#通解( 6 + 2 * sqrt(7) )( 8 + 3 * sqrt(7) )^n
 
#丢番图初始化参数:
A = 6;
B = 2;
C = 8;
D = 3;
#-----------------------------------------------------------------------------------------
# 计算丢番图方程参数:关键代码
def getArg(arg):
 return [arg[0] * C + arg[1] * D * 7,arg[0] * D +arg[1] * C];
#-----------------------------------------------------------------------------------------
# 字符串转十六进制
def str_to_hex(s):
 return ' '.join([hex(ord(c)).replace('0x', '') for c in s])
#-----------------------------------------------------------------------------------------
def str_to_hexArr(s):
 return re.findall(r'.{2}', s).reverse();
#-----------------------------------------------------------------------------------------
#十六进制数(字符串) 转换成 十六进制数组(十六进制数)(按小端存放)
def hex_to_hexArr(s):
 tmp = ("%s"%(s))[2:len(s)-1]
 #print tmp;
 if len(tmp)%2==1 :
 tmp="0"+tmp;
 tmpArr = re.findall(r'.{2}', tmp);
 tmpArr.reverse();
 #print tmpArr;
 argAHexArr = [];
 for item in tmpArr:
 argAHexArr.append(int(item,16));
 return argAHexArr;
#-----------------------------------------------------------------------------------------
#输出十六进制数组
def print_hexArr_1D(arr):
 hex_array=[];
 for item in arr:
 hex_array.append('0x%02X'%item);
 return hex_array ;
def print_hexArr_2D(arr):
 hex_array=[];
 for item in arr:
 hex_array.append( print_hexArr_1D(item));
 return hex_array;
#-----------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------
#关键子程序,对找到的解,进行校核判断
def Solution(arg):
 print "\n进一步验证是否满足要求";
 argHex = [hex(arg[0]),hex(arg[1])];
 argHexArr = [hex_to_hexArr(hex(arg[0])),hex_to_hexArr(hex(arg[1]))];
 print "Result for argHexArr_Solution:\n %s"%(argHexArr);
 print "Result for argHexArr_Solution(Hex):\n %s\n"%(print_hexArr_2D(argHexArr));
 #print "-"*80;
 if len(argHexArr)!=2 or len(argHexArr[0])<8 or len(argHexArr[1])<8 :
 print '不满足要求:长度不够(不然高位将为00)';
 print "-"*80;
 return;
 for item in argHexArr:
 for itm in item:
 if itm == 0 :
 print '不满足要求:不能为00';
 print "-"*80;
 return;
 #异或运算
 strHexArr = [];
 i=0;
 while i<2:
 j=0;
 tmpHexArr =[];
 while j<8:
 tmpHexArr.append(argHexArr[i][j] ^ keyArr[i][j]);
 j=j+1;
 strHexArr.append(tmpHexArr);
 i=i+1;
 print "Result for strHexArr:\n %s"%(strHexArr);
 print "Result for strHexArr(Hex):\n %s"%(print_hexArr_2D(strHexArr));
 #验证所有字符:应为可输入字符:0x20-0x7E。
 for item in strHexArr:
 for itm in item:
 if itm < 0x20 or itm > 0x7E :
 print '不满足要求:应为可输入字符:0x20-0x7E';
 print "-"*80;
 return;
 print "找到序列号:";
 #sn = str.decode(strHexArr[0]);
 #sn='---'.join(strHexArr[0])
 sn="";
 for item in strHexArr:
 for itm in item:
 sn=sn+chr(itm);
 print sn;
 sn_result.append(sn);
 print "-"*80;
 return;
#-----------------------------------------------------------------------------------------
#-----------------------------------------------------------------------------------------
#-----------------------------------------------------------------------------------------
# 求解 大整数:Main主函数,找到潜在的解,判断解,输出解
 
#key按小端存放
key0 = 0xE38C9616;
key1 = 0x646E9881;
key2 = 0x81DC0884;
key3 = 0x4F484DBE;
sn_result=[];
key = [hex(key0 ^ (key1<<32)),hex(key2 ^ (key3<<32))];
keyArr = [hex_to_hexArr(key[0]),hex_to_hexArr(key[1])];
print "Result for keyArr:\n %s\n"%(keyArr);
print "Result for keyArr(Hex):\n %s"%(print_hexArr_2D(keyArr));
print "-"*80;
arg = [A,B];
argHex =[0x00,0x00];
while arg[0] < 0x1000000000000000 :#第8个字节应为01至0F,才能满足要求
 arg =getArg(arg);
 print "Result for Solution(Hex):\n %s"%(print_hexArr_1D(arg));
 if arg[0] <0x0100000000000000 :
 print "长度不满足要求";
 print "-"*80;
 continue;
 print "长度初步满足要求";
 Solution(arg);
print "-"*80;
print "最终找的结果是:%d个"%(len(sn_result));
print sn_result;
#-----------------------------------------------------------------------------------------
#-----------------------------------------------------------------------------------------

6、运算结果

Result for keyArr:
 [[22, 150, 140, 227, 129, 152, 110, 100], [132, 8, 220, 129, 190, 77, 72, 79]]
 
Result for keyArr(Hex):
 [['0x16', '0x96', '0x8C', '0xE3', '0x81', '0x98', '0x6E', '0x64'], ['0x84', '0x 08', '0xDC', '0x81', '0xBE', '0x4D', '0x48', '0x4F']]
--------------------------------------------------------------------------------
 
Result for Solution(Hex):
 ['0x5A', '0x22']
长度不满足要求
--------------------------------------------------------------------------------
 
Result for Solution(Hex):
 ['0x59A', '0x21E']
长度不满足要求
--------------------------------------------------------------------------------
 
Result for Solution(Hex):
 ['0x5946', '0x21BE']
长度不满足要求
--------------------------------------------------------------------------------
 
Result for Solution(Hex):
 ['0x58EC6', '0x219C2']
长度不满足要求
--------------------------------------------------------------------------------
 
Result for Solution(Hex):
 ['0x58931A', '0x217A62']
长度不满足要求
--------------------------------------------------------------------------------
 
Result for Solution(Hex):
 ['0x583A2DA', '0x2158C5E']
长度不满足要求
--------------------------------------------------------------------------------
 
Result for Solution(Hex):
 ['0x57E19A86', '0x21374B7E']
长度不满足要求
--------------------------------------------------------------------------------
 
Result for Solution(Hex):
 ['0x578960586', '0x2115F2B82']
长度不满足要求
--------------------------------------------------------------------------------
 
Result for Solution(Hex):
 ['0x57317EBDDA', '0x20F4BB6CA2']
长度不满足要求
--------------------------------------------------------------------------------
 
Result for Solution(Hex):
 ['0x56D9F55D81A', '0x20D3A579E9E']
长度不满足要求
--------------------------------------------------------------------------------
 
Result for Solution(Hex):
 ['0x5682C3DEC3C6', '0x20B2B0BE7D3E']
长度不满足要求
--------------------------------------------------------------------------------
 
Result for Solution(Hex):
 ['0x562BE9E966446', '0x2091DD1903542']
长度不满足要求
--------------------------------------------------------------------------------
 
Result for Solution(Hex):
 ['0x55D5672587809A', '0x20712A6844D6E2']
长度不满足要求
--------------------------------------------------------------------------------
 
Result for Solution(Hex):
 ['0x557F3B3B9E1A55A', '0x2050988B2BD38DE']
长度初步满足要求
 
进一步验证是否满足要求
Result for argHexArr_Solution:
 [[90, 165, 225, 185, 179, 243, 87, 5], [222, 56, 189, 178, 136, 9, 5, 2]]
Result for argHexArr_Solution(Hex):
 [['0x5A', '0xA5', '0xE1', '0xB9', '0xB3', '0xF3', '0x57', '0x05'], ['0xDE', '0x 38', '0xBD', '0xB2', '0x88', '0x09', '0x05', '0x02']]
 
Result for strHexArr:
 [[76, 51, 109, 90, 50, 107, 57, 97], [90, 48, 97, 51, 54, 68, 77, 77]]
Result for strHexArr(Hex):
 [['0x4C', '0x33', '0x6D', '0x5A', '0x32', '0x6B', '0x39', '0x61'], ['0x5A', '0x 30', '0x61', '0x33', '0x36', '0x44', '0x4D', '0x4D']]
找到序列号:
L3mZ2k9aZ0a36DMM
--------------------------------------------------------------------------------
 
Result for Solution(Hex):
 ['0x552965D47892D506', '0x20302760C38EB6FE']
长度初步满足要求
 
进一步验证是否满足要求
Result for argHexArr_Solution:
 [[6, 213, 146, 120, 212, 101, 41, 85], [254, 182, 142, 195, 96, 39, 48, 32]]
Result for argHexArr_Solution(Hex):
 [['0x06', '0xD5', '0x92', '0x78', '0xD4', '0x65', '0x29', '0x55'], ['0xFE', '0x B6', '0x8E', '0xC3', '0x60', '0x27', '0x30', '0x20']]
 
Result for strHexArr:
 [[16, 67, 30, 155, 85, 253, 71, 49], [122, 190, 82, 66, 222, 106, 120, 111]]
Result for strHexArr(Hex):
 [['0x10', '0x43', '0x1E', '0x9B', '0x55', '0xFD', '0x47', '0x31'], ['0x7A', '0x BE', '0x52', '0x42', '0xDE', '0x6A', '0x78', '0x6F']]
不满足要求:应为可输入字符:0x20-0x7E
--------------------------------------------------------------------------------
 
--------------------------------------------------------------------------------
 
最终找的结果是:1个
['L3mZ2k9aZ0a36DMM']
 
请按任意键继续. . .

后记:

关于如何找到 丢番图  X^2 - 7*Y^2 = 8的最小正整数解,我的方法是 用Excel来搞定。因为这个方程不复杂,很快就能找到,如果作者整个复杂的角色,估计就麻烦了。

比如X^2 - 1141 * Y^2 =1,它的基本解Xn+Yn*sqrt(1141)中的Yn=30693385322765657197397208,这就麻烦了。如果好好设计系数D和M,将增强解密的难度。

▼▼▼

▼▼

1、 看雪.纽盾 KCTF 2019 Q2 | 第一题点评及解题思路

2、 看雪.纽盾 KCTF 2019 Q2 | 第三题点评及解题思路

3、看雪.纽盾 KCTF 2019 Q2 | 第二题点评及解题思路

4、 终曲 · 看雪.纽盾 KCTF 2019 Q2 圆满落幕,精彩回顾!

主办方

看雪.纽盾 KCTF 2019 Q2 | 第四题点评及解题思路

看雪.纽盾 KCTF 2019 Q2 | 第四题点评及解题思路

看雪学院(www.kanxue.com)是一个专注于PC、移动、智能设备安全研究及逆向工程的开发者社区!创建于2000年,历经19年的发展,受到业内的广泛认同,在行业中树立了令人尊敬的专业形象。平台为会员提供安全知识的在线课程教学,同时为企业提供智能设备安全相关产品和服务。 

合作伙伴

看雪.纽盾 KCTF 2019 Q2 | 第四题点评及解题思路

看雪.纽盾 KCTF 2019 Q2 | 第四题点评及解题思路

上海纽盾科技股份有限公司( www.newdon.net )成立于2009年,是一家以“网络安全”为主轴,以“科技源自生活,纽盾服务社会”为核心经营理念,以网络安全产品的研发、生产、销售、售后服务与相关安全服务为一体的专业安全公司,致力于为数字化时代背景下的用户提供安全产品、安全服务以及等级保护等安全解决方案。

看雪.纽盾 KCTF 2019 Q2 | 第四题点评及解题思路

看雪.纽盾 KCTF 2019 Q2 | 第四题点评及解题思路 小手一戳,了解更多

看雪.纽盾 KCTF 2019 Q2 | 第四题点评及解题思路

公众号ID:ikanxue

官方微博:看雪安全

商务合作:wsc@kanxue.com

看雪.纽盾 KCTF 2019 Q2 | 第四题点评及解题思路

戳原文,查看更多精彩writeup!


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

白帽子讲Web安全(纪念版)

白帽子讲Web安全(纪念版)

吴翰清 / 电子工业出版社 / 2014-6 / 69.00元

互联网时代的数据安全与个人隐私受到前所未有的挑战,各种新奇的攻击技术层出不穷。如何才能更好地保护我们的数据?《白帽子讲Web 安全(纪念版)》将带你走进Web 安全的世界,让你了解Web 安全的方方面面。黑客不再神秘,攻击技术原来如此,小网站也能找到适合自己的安全道路。大公司如何做安全,为什么要选择这样的方案呢?在《白帽子讲Web 安全(纪念版)》中都能找到答案。详细的剖析,让你不仅能“知其然”,......一起来看看 《白帽子讲Web安全(纪念版)》 这本书的介绍吧!

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换