首页
社区
课程
招聘
[原创] CTF2019 readyu crackme (青梅竹马)设计思路
2019-3-3 18:53 6481

[原创] CTF2019 readyu crackme (青梅竹马)设计思路

2019-3-3 18:53
6481
readyu's crackme CTF2019 Q1
readyu@pediy

根据规则,crackme SN唯一且为大小写字母+数字。
本题CODE为: PEDIyV9102dVreadyu
正确提示:       yes, correct sn!

crackme 取名 青梅竹马 ,意指代 小素数的组合, 字面隐含谜底 "两小无猜" (方程右边为2) 。

设计思路:
(1) 原始解:M
考虑100以内的素数,顺序生成一个数列:
2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97
基于这些小素数的乘积,根据RSA的原理拓展出一个题目。

第一项2, 比较特殊,把它排除开来,然后考虑N适当的长度,经过计算,取
N = 3*5*7*11*13*17*19*23*29*31*37*41*43*47*53*59*61*67*71*73 =
20364840299624512075310661735

N的欧拉函数计算如下:
φ(N)= 2*4*6*10*12*16*18*22*28*30*36*40*42*46*52*58*60*66*70*72 =
  5133855159158901099724800000

然后在后续较大的其余项里,任取一个素数,比如取 e = 83,建立一个幂模方程。
求解大整数M ( 2 < M < N), 使得
M^e = 2 mod N

等价于求解:
e*d = 1 mod φ(N)   , 且 1  < d  < φ(N)
2^d = M mod N ,      且 2 < M < N

由前面的条件可知,
(a)N的素因子不包含2;
(b)并且 φ(N) 的每个因子项都小于e, 且e是一个素数, 所以e,φ(N)互素, 那么逆元d存在。
所以上面方程有唯一解(d, M)。

计算可得
d = 1/e mod φ(N) = 1855610298491169072189686747
所以
M = 2^d mod N = 6602940601029543050476765433
转为16进制的大数:
M = 1555D30F38B0DBCAEC83C0F9
M就是最原始的解。

(2) M转换为有意义的CODE
为了使得M “看起来有意义” ,用base64缩短,嵌入信息,伪代码如下:
M =  HEX(1555D30F38B0DBCAEC83C0F9) ,
M长度为12个字节, 相当于3个int32。

base64table=
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/

M_b64= FVXTDziw28rsg8D5
然而这个注册码并不具有明确意义。

做字母替换,可以手动计算取一个特殊的
custom_base64table=
ABCyVPGHTJKLMNOFQRSIUEWDYZgbc8sfah1jklmnopqret5v0xX9wi234u67dz+/

得到
SN = M_b64(custom_base64table)
   = PEDIy9102dreadyu

起初打算把注册码定为
code = PEDIy-9102d-readyu
考虑到规则只允许用数字和字母,用V做分隔符,最终code定为:
CODE = PEDIyV9102dVreadyu

注册码判断的提示信息,也用前面的custom_base64table 编码了。
"bmdeTH8Xb2unTHN5TSVhTQ"   // no, wrong sn!!!!
"bWzXZSB3b3JrTHRvTGRvTQ"   // more work to do!
"sWE9LCBjb3JXZWNwTHN5TQ"   // yes, correct sn!
"c24aZGzlc24n8CB3b3JrTQ"   // sn doesn't work!

程序里的custom_base64table简单地异或加密,避免暴露明文,  每一项 c[i] ^ 0x50

(3) 验证注册码
1. 对code有效字符集和长度作检测, 不通过的,提示错误信息。trim后得到干净的sn。
2. sn 通过custom_base64 decode转换为hex M,也就是一个大整数
3. 用筛法生成100以内的小素数列表, primes[i]
4. 计算 N = 3*5*7...*73  (p=79时截止,79=0x4F,恰好是大写字母O的ascii值)
5. 判断输入 2 =< M <= N , 不通过的,提示错误信息。
6. 计算大整数  X = M^e mod N , 其中e=83, 程序里固定。
   X转换回int值,最多只能转换32bit。
   bits(X) >= 32 bit时,转换为INT_MAX, 提示  sn doesn't work!
   当且仅当 bits(X) <32bit, 并且X=2 时,提示  yes, correct sn!

说明:
   大整数运算采用polarssl-0.10 里的bignum.c/bignum.h,
   这个库比openssl, gmp, miracl 等常见大整数运算库小巧很多。
   https://tls.mbed.org/download-archive

   为了使代码尽可能简单,M^e mod N 没有采用bignum里面的mpi_exp_mod(),
   考虑到e是一个小指数,采用的是power left to right的简单算法。


[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界

最后于 2019-3-25 10:34 被readyu编辑 ,原因: FIX: 修正了在前面添零的多解问题, 比如: AAAAPVEDIy9V102dreadyu
上传的附件:
收藏
点赞3
打赏
分享
最新回复 (5)
雪    币: 6920
活跃值: (2584)
能力值: (RANK:520 )
在线值:
发帖
回帖
粉丝
netwind 13 2019-3-3 19:35
2
0
初步检查没有问题
雪    币: 11704
活跃值: (966)
能力值: ( LV12,RANK:779 )
在线值:
发帖
回帖
粉丝
readyu 12 2019-3-3 21:44
3
0
自己测试发现了可构造多解的bug, 修正一下,重新上传了附件。
FIX:  修正了在前面添零的多解问题, 比如:   AAAAPVEDIy9V102dreadyu
最后于 2019-3-3 21:47 被readyu编辑 ,原因:
雪    币: 6920
活跃值: (2584)
能力值: (RANK:520 )
在线值:
发帖
回帖
粉丝
netwind 13 2019-3-5 18:44
4
0
readyu 自己测试发现了可构造多解的bug,&nbsp;修正一下,重新上传了附件。FIX:&nbsp;&nbsp;修正了在前面添零的多解问题,&nbsp;比如:&nbsp ...
对修复后的程序检测没有发现问题
雪    币: 4836
活跃值: (61)
能力值: ( LV4,RANK:45 )
在线值:
发帖
回帖
粉丝
HeyLXF 2019-3-26 23:46
5
0
请问前辈能发一下源码嘛,对程序一些部分的实现比较好奇
雪    币: 11704
活跃值: (966)
能力值: ( LV12,RANK:779 )
在线值:
发帖
回帖
粉丝
readyu 12 2019-3-27 09:21
6
0
验证流程的主要源代码:

// 小整数求平方根,牛顿法迭代10次
int newton_root(int n)
{
    int k;
    int i;
    int n100;
    int root;

    n100 = n*100;
    k = 2;
    for(i = 0; i < 10; i++)
    {
        k = (k + n100/k) >> 1;
    }
    root = k/10;
    return root;
 }

// 筛法256以内的小素数
#define   NMAX  256
int prime_table(unsigned int n, unsigned int out[])
{      
    char mark[NMAX+1] = {0};  //有没有遍历过
    unsigned int prime[NMAX+1] = {0};  
    unsigned int primeSize;
    unsigned int rootn;
    unsigned int i;
    unsigned int j;
        
    primeSize = 0;
    if(n > NMAX)
        n = NMAX;
    rootn = newton_root(n);

    for (i = 2; i <= n; i++) 
    {
        if (mark[i] == 1)  // 已标记为合数
            continue;

        prime[primeSize] = i;
        primeSize++;
        
        if (i >= rootn)
            continue; //终止条件
        
        for (j = i * i; j <= n; j += i)
             {  
                    mark[j] = 1;  
             }  
    }

    // copy the primes
    for(i = 0; i < primeSize; i++)
    {
        out[i] = prime[i];
    }

    return primeSize;
}  


// n=100
// key=79
// N =  3*5*7*11*13*17*19*23*29*31*37*41*43*47*53*59*61*67*71*73 =
// 20364840299624512075310661735
// 41CD66ACC237B22681A18067
// exp = e=83
int check_sn(int n, unsigned int p79, unsigned char *sn, unsigned int len)
{
    int i;
    unsigned int primes[100] = {0};
    unsigned int primesize = 0;
    unsigned int e;
    unsigned int retn;
    mpi X, M, N;
    mpi P2;
    
    primesize = prime_table(n, primes);

    mpi_init(&X, &M, &N, NULL);
    mpi_init(&P2, NULL);
    
    mpi_lset(&X, 0);
    mpi_lset(&M, 0);
    mpi_lset(&N, 1);
    mpi_lset(&P2, (int)primes[0]);
    mpi_read_binary(&M, sn, len);

    // retn 置0
    retn = mpi_lget(&X);

    for(i = 1; i < (int)primesize; i++)
    {
        if(primes[i] == p79)
        {
            e = primes[i+1];
            break;
        }

        if(primes[i] != 0)
            mpi_mul_int(&N, &N, primes[i]);
    }

    //    2  <= M  <= N
    if( ( mpi_cmp_mpi(&M, &P2)>=0 ) && ( mpi_cmp_mpi(&M, &N)<=0 ) )
    {        
        // now, we get M, e, N,  calc X=M^e mod N
        
        mpi_exp_int_mod(&X, &M, e, &N);
        
        if( ( mpi_cmp_mpi(&X, &P2)>=0 ) && ( mpi_cmp_mpi(&X, &N)<=0 ) )
        {
            retn = mpi_lget(&X);
        }
    }
    
    mpi_free(&X, &M, &N, NULL);
    mpi_free(&P2, NULL);

    return retn;
}
最后于 2019-3-27 09:25 被readyu编辑 ,原因:
游客
登录 | 注册 方可回帖
返回