2021NCTF RE
# Re### Shadowbringer
c++64位程序,ida载入
```c
std::string::string(v4, "U>F2UsQXN`5sXMELT=:7M_2<X]^1ThaWF0=KM?9IUhAsTM5:T==_Ns&<Vhb!", &v6);
std::allocator<char>::~allocator(&v6);
std::operator<<<std::char_traits<char>>(refptr__ZSt4cout, "Welcome.Please input your flag:\n");
std::operator>><char>(refptr__ZSt3cin, (std::string *)input);
std::string::string((std::string *)v8, (const std::string *)input);// strcpy
base64encode1((std::string *)v7, (std::string *)v8);// 换表的base64
std::string::operator=((std::string *)input, (const std::string *)v7);// 赋值 第一层密文
std::string::~string((std::string *)v7);
std::string::~string((std::string *)v8);
std::string::string((std::string *)v10, (const std::string *)input);// 复制一个对象v10
base64encode2((std::string *)v9, (std::string *)v10); // base64换表 两次 不同的表
std::string::operator=((std::string *)input, (const std::string *)v9);
std::string::~string((std::string *)v9);
std::string::~string((std::string *)v10);
if ( (unsigned __int8)std::operator==<char>(input, v4) )
std::operator<<<std::char_traits<char>>(refptr__ZSt4cout, "Right.");
else
std::operator<<<std::char_traits<char>>(refptr__ZSt4cout, "Wrong.");
std::string::~string((std::string *)v4);
```
主要用到了c++ string类来进行处理,结合动调,大致经过了两次base64变表加密,在和v4进行比较。
第一组表,
‘#$%&’,27h,‘()*+,-.s0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ’+‘^_`ab’
第二组表
‘ba`_^]h[ZYXWVUTSRQPONMLKJIHGFEDCBA@?>=<;:9876543210s.-,+*)(’,27h,‘&’‘+’%$#’
两次加密的代码大致相同,主要通过表的长度和每次处理的二进制长度判断为base64.
```c
std::bitset<8ull>::to_string(v13, v14); // 转为2进制
for ( j = 0; j < (unsigned __int64)std::string::size((std::string *)&v9); j += 6 )// 6个二进制一组
v7 = (char *)std::string::operator[](&hisoralce, v6);//表索引
while ( (std::string::size(a1) & 3) != 0 )
{
std::operator+<char>(v19, a1, '!');
std::string::operator=(a1, (const std::string *)v19);
std::string::~string((std::string *)v19);
}
//不为4的倍数就不断+!
```
了解流程后写解密脚本即可
```python
import base64
table1='#$%&'+'\x27'+'()*+,-.s0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ'+'^_`ab'
table2='ba`_^]h[ZYXWVUTSRQPONMLKJIHGFEDCBA@?>=<;:9876543210s.-,+*)('+'\x27'+'&%$#'
base='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
enc='U>F2UsQXN`5sXMELT=:7M_2<X]^1ThaWF0=KM?9IUhAsTM5:T==_Ns&<Vhb!'
#enc='FsJ7M?b<U->2M>U:'#123456789测试
def newbase(enc,table):
m=''
for i in range(len(enc)):
if enc in table:
m+=base)]
else:
m+='='
print(base64.b64decode(m))
return base64.b64decode(m)
c1=newbase(enc,table2).decode()
newbase(c1,table1)
#NCTF{H0m3_r1d1n9_h0m3_dy1n9_h0p3}
```
### 鲨鲨的秘密
32位程序,ida载入
```c
IpAdress = malloc(0x20u);
VirtualProtect(IpAdress, 0x20u, 0x40u, &flOldProtect);
dword_404E48 = (int)IpAdress;
*(_BYTE *)IpAdress = 0xC3; // ret的机器码
((void (*)(void))IpAdress)
```
刚载入就有种莫名其妙的熟悉感,和西湖论剑的一道逆向题思路差不多,又是体力活。
是一种修改代码的操作数并单语句执行的SMC,通过一个数组来确定赋值代码长度的大小,和选定相应的操作数和修改的位置。
挖出汇编代码,结合动调分析语句还原算法。
```
mov ds:dword_403474, 0FFFFFFFFh // mov output ,0xffffffff
mov ecx, ds:dword_403464 // mov ecx,index(0)
mov dl, byte ptr ds:VirtualProtect // mov dl,input
mov byte ptr ds:dword_403470, dl // mov temp,dl
movzx eax, byte ptr ds:dword_403470 //mov eax, temp输入传给eax
xor eax, ds:dword_403474 //xor eax,output 取反类似
mov byte ptr ds:dword_403470, al // mov temp , al保存取反的值
movzx ecx, byte ptr ds:dword_403470 // mov ecx,temp
and ecx, 0FFh // andecx,0xff
mov byte ptr ds:dword_403470, cl // mov temp,cl
mov edx, ds:dword_403474 // mov edx,output
shr edx, 8 // shr edx,8
mov ds:dword_403474, edx // mov output,edx
movzx eax, byte ptr ds:dword_403470 // mov eax,temp
mov ecx, ds:dword_403474 // mov ecx,output
xor ecx, dword ptr ds:byte_403058 //xor ecx, sbox //174841BCxor sbox 结果保存到output
mov ds:dword_403474, ecx
mov edx, ds:dword_403464 ....
mov al,
mov byte ptr ds:dword_403470, al
movzx ecx, byte ptr ds:dword_403470
xor ecx, ds:dword_403474
mov byte ptr ds:dword_403470, cl
mov edx, ds:dword_403474
shr edx, 8
mov ds:dword_403474, edx
movzx eax, byte ptr ds:dword_403470
mov ecx, ds:dword_403474
xor ecx, dword ptr ds:byte_403058 //xor ecx,sbox//eax 0xdd
mov ds:dword_403474, ecx //mov output,ecx
mov edx, ds:dword_403474 //mov edx,output
xor edx, 0FFFFFFFFh //xor edx, 0FFFFFFFFh
mov ds:dword_403474, edx //mov output edx
```
python代码如下
```python
input='a'*40
output=0xffffffff
for index in range(0,40,2):
tmp=(ord(input)^output)&0xff
output=output>>8
output=output^somebox
#print("%x %x"%(tmp,output))
tmp = (ord(input) ^ output) & 0xff
output = output >> 8
output = output ^ somebox
output=output^0xffffffff
print("%x %x" % (tmp, output))
```
可知是两个字节为一组进行的处理,z3解因为涉及下标问题不好下手,所以直接爆破。
```python
somebox=
enc=
s=''
enc=
for iin range(20):
form in range(32,128):
for n in range(32,128):
output = 0xffffffff
tmp=(m^output)&0xff
output=output>>8
output=output^somebox
#print("%x %x"%(tmp,output))
tmp = (n ^ output) & 0xff
output = output >> 8
output = output ^ somebox
output=output^0xffffffff
if output==enc:
s+=chr(m)+chr(n)
print(s)
#NCTF{rLdE57TG0iHA39qUnFZp6LeJyYEBcxMNL7}
```
### 狗狗的秘密
**挺不错的,解完想暴打出题人。**
32位程序,ida载入获得假flag一枚
![](https://i.imgur.com/gAmoAa5.png)
不过main之前有个TlsCallback函数,直接下个断点,动态分析。
```c
if ( !v9 && !IsDebuggerPresent() )
{
off_825014 = (int (__cdecl *)(_DWORD))sub_823000;
v8 = (unsigned int *)((char *)sub_823000 + 256);
for ( i = 0; i < 24; ++i )
v8 += 2;
for ( j = 0; j < 24; ++j )
{
v8 -= 2;
sub_8211F0(v8);
}
}
```
反调试,off_825014在主函数出现过,但是个假逻辑,所以这部分内容是SMC修改技术。
```c
unsigned int __cdecl sub_8211F0(unsigned int *a1)
{
unsigned int result; // eax
int i; //
unsigned int v3; //
unsigned int v4; //
v4 = *a1;
v3 = a1;
for ( i = 0; i < 64; ++i )
{
v3 -= (dword_825004[(*(_DWORD *)delta >> 11) & 3] + *(_DWORD *)delta) ^ (v4 + ((v4 >> 5) ^ (16 * v4)));
*(_DWORD *)delta += dword_825000;
v4 -= (dword_825004 & 3] + *(_DWORD *)delta) ^ (v3 + ((v3 >> 5) ^ (16 * v3)));
}
*a1 = v4;
result = v3;
a1 = v3;
return result;
}
```
直接改eip绕过这个反调试即可,同时修改代码用的xTea,不过delta的值有个小坑,main函数中有个创建线程的函数,将delta赋值为0xDA76C600,patch进行修改,后面下个断F9。
修复函数后拿到真正处理逻辑。
```c
int __cdecl sub_823000(const char *a1)
{
signed int v2; //
unsigned int v3; //
signed int v4; //
int v5; //
int v6; //
char v7; //
signed int Size; //
unsigned int v9; //
int k; //
unsigned __int8 *v11; //
int i; //
signed int j; //
signed int m; //
signed int n; //
signed int ii; //
char v17; //
int v18; //
int v19; //
int v20; //
int v21; //
__int16 v22; //
v3 = strlen(a1);
Size = 146 * v3 / 0x64 + 1;
v4 = 0;
v11 = (unsigned __int8 *)malloc(Size);
v17 = 82;
v17 = -61;
v17 = 26;
v17 = -32;
v17 = 22;
v17 = 93;
v17 = 94;
v17 = -30;
v17 = 103;
v17 = 31;
v17 = 31;
v17 = 6;
v17 = 6;
v17 = 31;
v17 = 23;
v17 = 6;
v17 = 15;
v17 = -7;
v17 = 6;
v17 = 103;
v17 = 88;
v17 = -78;
v17 = -30;
v17 = -116;
v17 = 15;
v17 = 42;
v17 = 6;
v17 = -119;
v17 = -49;
v17 = 42;
v17 = 6;
v17 = 31;
v17 = -104;
v17 = 26;
v17 = 62;
v17 = 23;
v17 = 103;
v17 = 31;
v17 = -9;
v17 = 58;
v17 = 68;
v17 = -61;
v17 = 22;
v17 = 51;
v17 = 105;
v17 = 26;
v17 = 117;
v17 = 22;
v17 = 62;
v17 = 23;
v17 = -43;
v17 = 105;
v17 = 122;
v17 = 27;
v17 = 68;
v17 = 68;
v17 = 62;
v17 = 103;
v17 = -9;
v17 = -119;
v17 = 103;
v17 = -61;
v18 = 0;
v19 = 0;
v20 = 0;
v21 = 0;
v22 = 0;
memset(v11, 0, Size);
v9 = 0;
for ( i = 0; i < 256; ++i )
{
v7 = byte_825018;
byte_825018 = byte_825018[(i + *((unsigned __int8 *)&delta + i % 4)) % 256];// delta变为0了
byte_825018[(i + *((unsigned __int8 *)&delta + i % 4)) % 256] = v7;
}
while ( v9 < strlen(a1) )
{
v5 = a1;
for ( j = 146 * v3 / 0x64; ; --j )
{
v6 = v5 + (v11 << 8);
v11 = v6 % 47;
v5 = v6 / 47;
if ( j < v4 )
v4 = j;
if ( !v5 && j <= v4 )
break;
}
++v9;
}
for ( k = 0; !v11; ++k )
;
for ( m = 0; m < Size; ++m )
v11 = byte_825118]; // 单表替换
while ( m < Size )
v11 = 0;
v2 = strlen((const char *)v11);
for ( n = 0; n < v2; ++n )
v11 ^= byte_825018]; // 异或处理
for ( ii = 0; ii < v2; ++ii )
{
if ( v11 != (unsigned __int8)v17 )
{
printf("Wrong!\n", v2);
exit(0);
}
}
printf("Right!\n", v2);
return 0;
}
```
delta的值是0,需要注意一下,接着就是写脚本逆向,z3不好直接求解,加密流程是先将输入转为47进制下每位的值存在数组v11中,找到第一个非0值的下标k,接着进行单表替换和异或。
因为涉及到表索引和本身异或不好逆向还原,所以想着在爆破v11数组。
```python
enc=
c=[]
for i in range(61):
temp=[]
for j in range(47):
tmp=tb2
tmp=tmp^table
if tmp==enc:
temp.append(j)
if len(temp)==1:
c.append(temp)
else:
c.append(temp)
c.insert(0,0)
#, 44, 30, 40, 8, 23, , , , , , , 24, , , 29, , , 13, 5, 23, 41, , 35, , 9, 14, 35, , , 3, , 10, 24, , , 38, 1, 25, 0, 30, 6, 42, , 36, 30, 10, 24, 21, 42, 26, 28, 25, 25, 10, , 38, 9, ]
```
v11第一位是0,根据加密的最后一位是0xc3,或者多次测试都可知,不过这解有点多,下面就是对c进行排列组合,之后47进制转,long_to_bytes下即可,不过这…tmd,dfs不太会写,直接硬爆破了,大概跑了半小时,直接整emo了。
完整如下
```python
from Crypto.Util.number import *
def dododo(c):
sum = 0
for i in range(62):
sum += c * pow(47, 61 - i)
m=long_to_bytes(sum)
try:
flag=m.decode()
print(flag)
exit(0)
except:
pass
enc=
table=
#delta=
tb2=
c=[]
for i in range(61):
temp=[]
for j in range(47):
tmp=tb2
tmp=tmp^table
if tmp==enc:
temp.append(j)
if len(temp)==1:
c.append(temp)
else:
c.append(temp)
c.insert(0,0)
print(c)
for i in c:
if isinstance(i,int):
pass
else:
print(i)
c=45
tblen=[]
l=1
index=[]
for i in range(len(c)):
try:
tblen.append(len(c))
l*=len(c)
index.append(i)
except:
tblen.append(1)
print(tblen)
print(index)
sum=0
t1=
t2=
t3=
t4=
t5=
fora1 in range(3):
c=t1
for a2 in range(2):
c=t2
for a3 in range(2):
c=t2
for a4 in range(3):
c=t3
for a5 in range(3):
c=t3
for a6 in range(2):
c=t2
for a7 in range(3):
c=t3
for a8 in range(2):
c=t4
for a9 in range(3):
c=t3
for a10 in range(3):
c=t1
for a11 in range(2):
c=t4
for a12 in range(3):
c=t3
for a13 in range(3):
c=t3
for a14 in range(2):
c=t2
for a15 in range(2):
c=t5
for a16 in range(3):
c=t1
for a17 in range(2):
c=t2
for a18 in range(2):
c=t5
for a19 in range(3):
c=t1
for a20 in range(3):
c=t1
dododo(c)
#NCTF{ADF0E239-D911-3781-7E40-A575A19E5835}
```
### easy_mobile
so被ollvm了,1400行。
!(https://i.loli.net/2021/11/29/82dlUtqwkIPuSfJ.png)
但是我用deflat去混淆,最后只剩三分一的代码?,没法只能用原来的看。
我尝试去动调,发现下不了断点?
想到只有1400行而已决定手撕。
然后我比较菜,花了一个多小时才整理了逻辑。
首先,我在719行找到了获取输入
!(https://i.loli.net/2021/11/29/zVwW3jqc2N149uR.png)
然后发现判断长度为24
!(https://i.loli.net/2021/11/29/2LCtozrBMSQN3b4.png)
交叉索引我们的输入
!(https://i.loli.net/2021/11/29/RMze2mvBskUt8VG.png)
发现,程序会将前16个字节分成4个字节一组分成四组的进行操作,最后8个字节分成两组。
我先看了程序对后面八个字节进行了什么操作
!(https://i.loli.net/2021/11/29/Q1wGKPuMH5pfRy2.png)
他作为输入传进了下面这个函数。并且上面还将一个长为16字节的字符串传了进去
跟进去看了一下
!(https://i.loli.net/2021/11/29/aKUGRvBjImeEsw6.png)
我顿时反应过来了,这和tea怎么那么像?
然后我手动恢复了一下
!(https://i.loli.net/2021/11/29/ERkbxQVGSie6Bp7.png)
这不就是改了delta但还是原汁原味的tea吗?而且他a2就是我们传入的第二个字符串,那就是KEY
我直接用我祖传脚本梭哈得到了我们后8个字符。
```c
#include <stdio.h>
#include <stdint.h>
//加密函数
void encrypt(uint32_t* v, uint32_t* k) {
uint32_t v0 = v, v1 = v, sum = 0, i; /* set up */
uint32_t delta = 0x9e3779b9; /* a key schedule constant */
uint32_t k0 = k, k1 = k, k2 = k, k3 = k; /* cache key */
for (i = 0; i < 32; i++) { /* basic cycle start */
sum += delta;
v0 += ((v1 << 4) + k0) ^ (v1 + sum) ^ ((v1 >> 5) + k1);
v1 += ((v0 << 4) + k2) ^ (v0 + sum) ^ ((v0 >> 5) + k3);
} /* end cycle */
v = v0; v = v1;
}
//解密函数
void decrypt(unsigned int* v, unsigned int* k) {
unsigned int v0 = v, v1 = v, i;
int sum = 0x12345678 << 5;/* set up */
uint32_t delta = 0x12345678;// 0x9e3779b8 /* a key schedule constant */
uint32_t k0 = k, k1 = k, k2 = k, k3 = k; /* cache key */
for (i = 0; i < 32; i++) { /* basic cycle start */
v1 -= ((v0 << 4) + k2) ^ (v0 + sum) ^ ((v0 >> 5) + k3);
v0 -= ((v1 << 4) + k0) ^ (v1 + sum) ^ ((v1 >> 5) + k1);
sum -= delta;
} /* end cycle */
v = v0; v = v1;
}
int main()
{
unsigned int v = { 0xC65AEDA,0xADBF8DB1 }, k = { 1634232689, 1852399976, 1851879017, 1835101793 };
// v为要加密的数据是两个32位无符号整数
// k为加密解密密钥,为4个32位无符号整数,即密钥长度为128位
//printf("%x %xx\n", v, v);
decrypt(v, k);
printf("%x %x\n", v, v);
puts((char*)v);
//58af2715c
return 0;
}
```
然后去继续交叉索引到第一次被调用的地方
然后理解一下
!(https://i.loli.net/2021/11/29/CtrE4qpeDHB1KGl.png)
就是将我们的前4个字符,保存在一个int32x4的数据类型中,可以理解为他是一个大小为四的int32类型数组。
下面一样的操作,当取完前16个字节后,他会取一些常量,如下
!(https://i.loli.net/2021/11/29/zluhQLTgYRy74sv.png)
然后整理一下就是这也
```c
v48 = *v299; //
v447 = *v295; //
v446 = v48;
v445 = vmulq_s32(v447, v48);
*v295 = v445;
v49 = *v300; //
v450 = *v295;
v449 = v49;
v448 = vsubq_s32(v450, vnegq_s32(v49));
*v295 = v448; // enc0
v50 = *v299; //
v468 = *v296; //
v467 = v50;
v466 = vmulq_s32(v468, v50);
*v296 = v466;
v51 = *v300; //
v465 = *v296;
v464 = v51;
v463 = vsubq_s32(v465, vnegq_s32(v51));
*v296 = v463; // enc1
v52 = *v301; //
v462 = *v297; //
v461 = v52;
v460 = vmulq_s32(v462, v52);
*v297 = v460;
v53 = *v302; //
v459 = *v297;
v458 = v53;
v457 = vsubq_s32(v459, vnegq_s32(v53));
*v297 = v457; // enc2
v54 = *v299; //
v456 = *v298; //
v455 = v54;
v454 = vmulq_s32(v456, v54);
*v298 = v454;
v55 = *v300; //
v453 = *v298;
v452 = v55;
v451 = vnegq_s32(vsubq_s32(vnegq_s32(v55), v453));// v451=-((-v300)-v453)v453=v451-v300
*v298 = v451; // enc3
```
一开始尝试爆破,然后我发现我傻了,直接求逆不就行了?然后一把梭
```python
enc=[ 0x00000CD7, 0x00000698, 0x00000D7C, 0x000006FA, 0x00000CB7, 0x000007CA, 0x0000079B, 0x000007D2,
0x00000950, 0x00000AD9, 0x00000ADF, 0x000014A6, 0x00000CF7, 0x00000720, 0x00000732,0x000007F6]
v299=
v300=
v301=
v302=
def mul(a,b):
c=[]
for i in range(4):
c.append(a * b)
return c
#set0
c=[]
e=enc
for i in range(4):
c.append(e-v300)
for i in range(4):
print(chr(c//v299),end='')
#set1
c=[]
e=enc
for i in range(4):
c.append(e-v300)
for i in range(4):
print(chr(c//v299),end='')
#set2
c=[]
e=enc
for i in range(4):
c.append(e-v302)
for i in range(4):
print(chr(c//v301),end='')
#set3
c=[]
e=enc
for i in range(4):
c.append(e-v300)
for i in range(4):
print(chr(c//v299),end='')
#e0a0d966076ff437
```
e0a0d966076ff43758af2715 对“狗狗的秘密”这个题提出几个细节问题:
1)为了 patch 反调试,具体位置在哪里。我在TlsCallBack_0函数以及7811C0函数patch过程中,还是无法进入正确流程。
2)是在IDA里、还是在OD里运行完SMC,才能看关键指针函数783300的malloc(146 * strlen(a1) / 0x64 + 1); 等之后的代码。
现在挡住我的不是后面的算法。而是可以看到完整算法的流程和步骤我复现不出来。所以特来求助。希望您给回复。非常感谢。 看晕了,需要仔细研读才行。还有那一堆for循环,是不是可以换一种精简写法,如果 100000个for 层怎么办。。。 向大佬学习 大佬nb{:1_921:} 不明觉厉 向大佬学习 学习打卡 哎,这些对于我来说,搞不懂啊,只能呵呵了 大佬可以分享一下题目的原题文件吗? 感谢分享!
页:
[1]
2