SECCON-CTF-2014-Decrypt-It-easy

20 篇文章 0 订阅
18 篇文章 0 订阅
这篇博客详细介绍了如何使用Linux命令和C程序解密特定格式的加密文件。作者首先分析了给定的文件,确定了加密过程涉及的时间戳、随机数生成以及异或操作。接着,通过反编译和理解程序逻辑,利用 srand 和 rand 函数生成相同的随机数序列,还原了加密过程。最后,通过爆破和中国剩余定理,成功解密得到了原始的字符串。博客中还提到了分解大整数和中国剩余定理在解密中的应用。
摘要由CSDN通过智能技术生成

题目

给了三个文件ecrypt1.bin,readme.txt,rnd

其中readme.txt:

$ ./rnd crypt1.png ecrypt1.bin

Solve

看到前面有一个$,猜测可能和linux系统有关,这一条信息有点像使用./目录下的rnd对crypt1.png进行某种操作,输出为ecrypt1.bin.

file一下

file /home/mangofeng/桌面/rnd
/home/mangofeng/桌面/rnd: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=6a1443272dd530117d3c63884e195120a845c499, stripped

是一个32位ELF文件,通过32ida反编译找主函数可以得到伪代码:

int __cdecl main(int a1, char **a2)
{
  unsigned int v3; // eax
  FILE *v4; // [esp+10h] [ebp-10h]
  FILE *v5; // [esp+14h] [ebp-Ch]
  char ptr; // [esp+1Fh] [ebp-1h] BYREF

  if ( a1 <= 2 )
    return 1;
  v3 = time(0);
  srand(v3);
  v4 = fopen(a2[1], "rb");
  v5 = fopen(a2[2], "wb");
  while ( fread(&ptr, 1u, 1u, v4) == 1 )
  {
    ptr ^= rand() % 256;
    fwrite(&ptr, 1u, 1u, v5);
  }
  fclose(v4);
  fclose(v5);
  return 0;
}
  unsigned int v3; // eax
  FILE *v4; // [esp+10h] [ebp-10h]
  FILE *v5; // [esp+14h] [ebp-Ch]
  char ptr; // [esp+1Fh] [ebp-1h] BYREF
  /*上面这些都是定义了各种变量*/
  if ( a1 <= 2 )
    return 1;
  //判断了一个a1是否小于等于2,若是的话,则返回1,下面语句都不会执行,则我们的a1应大于2
v3 = time(0);
//time()参数一般为0或者Null相当于 取系统时间():
#include <iostream>
#include <ctime>
using namespace std;
 
int main()
{
	int a;
	a=time(0);//time(0)返回的是系统的时间(从1970.1.1午夜算起),单位:秒
	cout<<a<<endl;
}
//1648089941
即时间戳:
时间戳是指格林威治时间19700101000000(北京时间19700101080000)起至现在的总秒数。
在CPP中,时间戳默认就是10位,其精度是秒
  srand(v3);

先提一提rand()

rand()函数常用来生成伪随机数
rand()函数是使用线性同余法做的,它并不是真的随机数,因为其周期特别长,所以在一定范围内可以看成随机的。
rand()函数不需要参数,它将会返回0到RAND_MAX之间的任意的整数。

如:

#include<time.h>
#include<stdlib.h>
#include<iostream>
using namespace std;
 
int main()
{
	for(int i = 0; i < 10; i++)
{
    cout << rand()<<endl;
}
}

/*
41
18467
6334
26500
19169
15724
11478
29358
26962
24464*/

srand()

srand()为初始化随机数发生器,用于设置rand()产生随机数时的种子。传入的参数seed为unsigned int类型,通常我们会使用time(0)或time(NULL)的返回值作为seed。
srand(time(0));
for(int i = 0; i < 10; i++)
{
    cout << rand() << endl;
}
/*22954
16198
7083
19454
25323
32035
19825
3031
24100
11374*/

而且:

此方法默认参数为srand(1),当种子确定以后,输出也是确定的.例:

srand(1);
for(int i = 0; i < 10; i++)
{
    cout << rand() << endl;
}
cout<<time(0);
/*
41
18467
6334
26500
19169
15724
11478
29358
26962
24464
1648090722
*/
/*
41
18467
6334
26500
19169
15724
11478
29358
26962
24464
1648090746
*/

我运行了两次上述代码,可通过时间戳看出,发现rand()结果是一样的.


让我们再回到反编译的代码:

  v3 = time(0);
  srand(v3);

通过上述分析我们得知,此处设置了一个种子,种子为运行时的时间戳.

  v4 = fopen(a2[1], "rb");
  v5 = fopen(a2[2], "wb");
//这句话就有点像python里面的,v4就是我要读取数据的文件,打开方式为read binary;v5就是我要写入数据的文件,打开方式为write binary;
  while ( fread(&ptr, 1u, 1u, v4) == 1 )
  {
    ptr ^= rand() % 256;
    fwrite(&ptr, 1u, 1u, v5);
  }
fread函数用于从文件流中读取数据,其函数原型为:
size_t fread(void* buffer, size_t size, size_t count, FILE*stream);

【参数设置】
1) 
buffer为接收数据的地址,对于fread来书是要读出数据的地址,即数据保存的地址
2)  size是要读出内容的单字节数。
3)  count是要进行读出size字节的数据项的个数。
4)  stream为目标文件指针
fwrite()
size_t fwrite(const void* buffer, size_t size, size_t count, FILE* stream);
    -- buffer:指向数据块的指针
    -- size:每个数据的大小,单位为Byte(例如:sizeof(int)就是4)
    -- count:数据个数
    -- stream:文件指针

然后大概就是原来文件的数据与生成的rand()异或得到新的文件数据.

然后我们之前分析了srand()函数,只要其传入参数一致,生成的rand()即一致

然后我们去分析一下生成文件ecrypt1bin的相关更改时间

可以通过stat命令查看文件的状态

stat /home/mangofeng/桌面/ecrypt1.bin
  文件:/home/mangofeng/桌面/ecrypt1.bin
  大小:45989           块:96         IO 块:4096   普通文件
设备:801h/2049d        Inode:21607       硬链接:1
权限:(0766/-rwxrw-rw-)  Uid:( 1000/mangofeng)   Gid:( 1000/mangofeng)
最近访问:2022-03-24 11:12:41.937672000 +0800
最近更改:2014-11-22 22:46:30.000000000 +0800    #this
最近改动:2022-03-24 11:12:42.026137518 +0800
创建时间:2022-03-24 11:12:41.938137515 +0800

将该更改时间转换为时间戳

a = "2014-11-22 22:46:30"
#将其转换为时间数组
import time
timeArray = time.strptime(a, "%Y-%m-%d %H:%M:%S" )
#转换为时间戳:
timeStamp = int (time.mktime(timeArray))
print(timeStamp)
#1416667590

或者

stat --printf=%Y /home/mangofeng/桌面/ecrypt1.bin                                                                                                       
1416667590

然后用一下解密脚本:

#include <stdio.h>
#include <stdlib.h>
 
int main(int argc, char *argv[]) {
  FILE *cipher = fopen(argv[1], "rb");
  FILE *plain = fopen(argv[2], "wb");
  unsigned int seed = atoi(argv[3]);
  int c;
 
  srand(seed);
  c = (fgetc(cipher) & 0xff) ^ (rand() & 0xff);
  while (!feof(cipher)) {
    fputc(c, plain);
    c = (fgetc(cipher) & 0xff) ^ (rand() & 0xff);
  }
  fclose(plain);
  fclose(cipher);
}

然后用一下gcc编译

gcc /home/mangofeng/桌面/decode.c -o /home/mangofeng/decode
                                                                                                                                                                                                                   
┌──(mangofeng㉿kali)-[~]
└─$ gcc /home/mangofeng/桌面/decode.c -o /home/mangofeng/桌面/decode                                                                                               
                                                                     
┌──(mangofeng㉿kali)-[~]
└─$ /home/mangofeng/桌面/decode /home/mangofeng/桌面/ecrypt1.bin /home/mangofeng/桌面/output.png 1416667590

1

其中N都不是太大的值,可以分解成pq

先一步一步干吧,用yafu分解一下

N1=0xB8AE199365
print(int(N1))
p1 = 913799
q1 = 868019
N2=0xB86E78C811
print(int(N2))
p2 = 904727
q2 = 875543
N3=0x7BD4071E55
print(int(N3))
p3 = 890459
q3 = 597263
assert (q1*p1==N1)
assert (q2*p2==N2)
assert (q3*p3==N3)

因为N都不是太大,就会导致加密的m不能太大,再看有三块,推测flag应该是三块短的字符串拼凑在一起

解法一:

已知flag格式为:SECCON{}

则试一试一块flag大概有多长

N, B, FLAG = 0xB8AE199365, 0xFFEEE, b'SECCON{'
for i in range(1, len(FLAG)+1):
	M = bytes_to_long(FLAG[0:i])
	print(FLAG[0:i],'\t',str(hex(M * (M + B) % N)),end="\n")
b'S'     0x52fc213
b'SE'    0x54f0cb0bf
b'SEC'   0x8c0ad9b877
b'SECC'          0x704d68c1fb
b'SECCO'         0x8d5051562b
b'SECCON'        0x2339eed575
b'SECCON{'       0x1bce931b16

发现到五位长度以后,明文就经过了取模操作,即我们猜测flag每一块被分成不超过五位的明文块

验证一下

B=[0xFFEEE,0xFFFEE,0xFEFEF]
C=[0x8D5051562B,0x5FFA0AC1A2,0x6008DDF867]
p=[913799,904727,890459]
q=[868019,875543,597263]
N=[p1*q1,p2*q2,p3*q3]
test=b'SECCO'
M=bytes_to_long(test)
print(bool(hex(M*(M+B[0])%N[0])=='0x8d5051562b'))
#True

则我们就可以去用ascii爆破剩下的两个明文块,每个长度最多为5;

N, B =N[1],B[1]
for i in range(32, 127):
  for j in range(32, 127):
    for k in range(32, 127):
      M = ('N{' + chr(i) + chr(j) +chr(k))
      M =bytes_to_long(bytes(M, encoding='UTF-8'))
      if ((M * (M + B) % N) == int(0x5FFA0AC1A2)):
        print('N{' + chr(i) + chr(j) + chr(k))
#N{Ra_
for i in tqdm.tqdm(range(32, 127)):
    for j in range(32, 127):
        for k in range(32, 127):
            for l in range(32,127):
                MM = chr(i) + chr(j) +chr(k)+chr(l)+'}'
                MM =bytes_to_long(bytes(MM, encoding='UTF-8'))
                if (((MM * (MM + B[2]))% N[2]) == int(0x6008DDF867)):
                    print(chr(i) + chr(j) + chr(k)+chr(l)+'}')
                    break
#b1_N}

SECCON{Ra_b1_N}

解法二:

N大概在2^40左右,要想从modN下找到一个值满足条件是不现实的

但我们已经把N分解成了p,q,根据中国剩余定理可以把条件拆成两个
{ C p ≡ m p ⋅ ( m p + B ) ( m o d p ) C q ≡ m q ⋅ ( m q + B ) ( m o d q ) \begin{cases}C_p\equiv m_p\cdot(m_p+B)\pmod{p}\\C_q\equiv m_q\cdot(m_q+B)\pmod{q}\end{cases} {Cpmp(mp+B)(modp)Cqmq(mq+B)(modq)
p,q都比较小,可以爆破,寻找到满足上述两式的 m p 和 m q m_p和m_q mpmq
{ m ≡ m p ( m o d p ) m ≡ m q ( m o d q ) \begin{cases}m\equiv m_p\pmod{p}\\m\equiv m_q\pmod{q}\end{cases} {mmp(modp)mmq(modq)
然后就可以根据CRT求解出m啦。

def CRT(moudle,a):
    M = reduce((lambda x,y : x * y),moudle)
    result = 0
    for mi in moudle:
        Mi = M // mi
        inv_Mi = gmpy2.invert(Mi,mi)
        result = (result + a[moudle.index(mi)] * Mi * inv_Mi) % M
    return (result % M)
for i in range(0,3):
    for a1 in [m_p for m_p in range(p[i]) if (C[i] % p[i]) == (m_p * (m_p + B[i])) % p[i]]:
        # 遍历所有小于p[i]的值
        for a2 in [m_q for m_q in range(q[i]) if (C[i] % q[i]) == (m_q * (m_q + B[i])) % q[i]]:
            # 遍历所有小于q[i]的值
            x = CRT([p[i],q[i]],[a1,a2])
            print(long_to_bytes(x))
b'eh\xc6Q('
b'W_V"\xa7'
b'aN\xb3q\xd0'
b'SECCO'
b'\x86V\t\xc5\xee'
b'i\xf3\x16f\xc4'
b'N{Ra_'
b'2\x18_\x025'
b'\x19\xa2\x97\xdf\xe9'
b'%\xf5"\x992'
b'U\xde\xd4\x954'
b'b1_N}'
SECCON{Ra_b1_N}

总结

解出png的过程还挺有意思的,就是后面这个加密方式有点头疼,爆破就完了。

参考

M3ng@L哥哥的链接

https://blog.csdn.net/u013745804/article/details/82379266

https://blog.csdn.net/stf1065716904/article/details/73656036

https://blog.csdn.net/yang2011079080010/article/details/52528261

https://blog.csdn.net/km_moon/article/details/84737151

https://blog.csdn.net/weixin_44604541/article/details/113613885

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
CTFd是一个用于举办和参加CTF(Capture The Flag)安全竞赛的平台。根据引用\[1\]和引用\[2\]的内容,你可以按照以下步骤在Ubuntu上搭建CTFd平台: 1. 首先,确保你已经安装了虚拟机并配置好了Ubuntu系统。具体的安装和配置步骤可以参考相关的教程。 2. 配置阿里云镜像下载源文件。这可以加快软件包的下载速度。你可以按照引用\[1\]中的指导进行配置。 3. 进入CTFd目录。在终端中使用cd命令进入CTFd的目录。 4. 使用gunicorn工具配置CTFd。根据引用\[2\]和引用\[3\]的内容,你可以使用以下命令配置gunicorn工具: ``` gunicorn --bind 0.0.0.0:8000 -w 5 "CTFd:create_app()" ``` 5. 如果你希望在重启电脑后再次运行CTFd平台,确保以root权限运行。在Ubuntu终端中使用sudo命令运行上述命令。 这样,你就可以在Ubuntu上成功搭建CTFd平台了。请注意,这只是一个简单的搭建过程,具体的配置和使用方法可能会有所不同,你可以参考相关的文档和教程进行更详细的了解和操作。 #### 引用[.reference_title] - *1* *2* *3* [基于Ubuntu搭建CTFd平台(全网最全)](https://blog.csdn.net/qq_25953411/article/details/127489944)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值