s16384 —— 全国高校网安联赛逆向专题

本文主要探讨全国高校网安联赛中涉及的逆向技术和算法问题,包括置换群的概念和一次同余方程组的求解策略。通过分析代码,揭示了如何利用置换操作解决竞赛中的核心问题,并提出了针对大整数的解题方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

逆向部分

0x400796处把输入的32字符解析成16字节,题目要求输入为数字和小写,这里要求字母只能是0~f。
0x400960这里是核心。整理一下有三个数组,下面记为a,b,c。它们长度都是0x4000(即题目的16384),a和c已经有初值,b被赋值0~0x3fff。先进入下一层函数,回头再分析。
0x400875此函数接收4个参数,除去最后一个是长度(0x4000)外,就是3个。此函数命名为Add,后面会解释。

void __fastcall Add(int *a1, int *a2, void *a3, int len)
{
  int v4; // [sp+4h] [bp-2Ch]@1
  int i; // [sp+24h] [bp-Ch]@1
  _DWORD *src; // [sp+28h] [bp-8h]@1

  v4 = len;
  src = malloc(4LL * len);
  for ( i = 0; i < v4; ++i )
    src[i] = a1[a2[i]];
  memcpy(a3, src, 4LL * v4);
  free(src);
}

顺次以a2作下标,按下标拿a1的值,放到新数组里面,然后复制给a3。注意到我们给Add函数传参的时候,a和b的值和下标全都是0~0x3fff的不重复的值,所以这将是一次置换操作。记置换操作为~Add(a, b, c, len)意味着c = a~b

回到上一层,函数命名为Change,因为是基于输入对a,b两个数组进行变换。

void __fastcall Change(const void *a4, char *input, void *a3, unsigned int len)
{
  void *v5; // [sp+8h] [bp-38h]@1
  signed int i; // [sp+24h] [bp-1Ch]@1
  signed int v7; // [sp+28h] [bp-18h]@2
  signed int j; // [sp+2Ch] [bp-14h]@2
  int *b; // [sp+30h] [bp-10h]@1
  void *a; // [sp+38h] [bp-8h]@1

  v5 = a3;
  b = malloc(4LL * len);
  initial(b, len);  //把b赋值0~0x3fff
  a = malloc(4LL * len);
  memcpy(a, a4, 4LL * len);
  for ( i = 0; i <= 15; ++i )
  {
    v7 = input[i];
    for ( j = 0; j <= 7; ++j )
    {
      if ( v7 & 1 )
        Add(a, b, b, len);
      Add(a, a, a, len);
      v7 >>= 1;
    }
  }
  memcpy(v5, b, 4LL * len);
  free(b);
  free(a);
}

Add函数只有两种使用,把从输入得到的值逐位取出,若为0,则a = a~a,否则b = a~b然后再a = a~a

回到main,按上面的方法变换结束后拿b的值和c对比,完全相同则答案正确。

算法部分

置换群

前面已经说过Add操作为置换,其元素构成置换群,即标题的s16384。群的乘法运算在这里我要用Add来表述,不是因为满足交换律,是后面要用,还请注意。把此群的加法还是记为~。
这个群的单位元就是b的初值,记为O;记a的初值为A,c的值为C。
之前提到我们有两种操作,一个是b = a~b,一个是a = a~a,后面的式子可以记为a = 2a

先假设输入的比特都是0,会怎样?
1. a=A , b=O
2. a=2A, b=O
3. a=4A, b=O
…………
可见b不会变,a则每次翻倍。

再假设依次输入比特0,1,0,1,会怎样?
1. a=A, b=O
2. a=2A, b=O
3. a=4A, b=2A
4. a=8A, b=2A
5. a=16A,b=10A

显然无论如何a和b都是A的倍数。其中利用到pA+qA=qA+pA,记作(p+q)A,这是群的性质之一,和交换律无关。
现在我们来看看b的系数10,2进制表示刚好就是0x1010。是不是想到了什么?其实b的值就等于输入比特构成的二进制数字,很容易证明的。

最后输入若正确,b=c,由于b是a的倍数,假设b=Na,N就是我们的输入。
最后目标:解方程c=Na。

一次同余方程组

要解这个方程,当然可以遍历N。但请注意,N是个128bit的数字,根本跑不来!
注意到置换群是可能被分解为更小的置换群的,其中任一小群各元素的阶都相同,大群的阶就是小群阶的最小公倍数。不过我不知道应该怎么分解,只是从前20个元素着手给出一种方法(一开始写的0x200,后来发现根本没必要):

round=[0]*20        #周期的集合
remain=[0]*20       #到C的余数

def change(a,b):    #一次变换 c=a~b
    c=[]
    for i in range(0x4000):
        c.append(a[b[i]])
    return c


for i in range(20):
    A=[...]     #太长了
    C=[...]
    temp=A
    count=0
    value=A[i]  #取个值
    place=i     #此值在temp中的位置
    while True:
        if value==C[place]:   #如果此值的位置和
            remain[i]=count
        temp=change(temp,A)
        count+=1
        place=temp.index(value)
        if A[place]==value:
            round[i]=count
            break
    print(i)

open("1.txt","w").write(str(round)+'\n'+str(remain)).close()

即提取前20个元素,设分别是 m1,m2,,m20 作为周期,即 mi A的位置和A的位置相同,而 r1,r2,,r20 作为余数,即 ri A的位置刚好和C一样,我们的N应该满足 N=ri(modmi)
这些 mi 可能不互素,比较麻烦,网上没找到现成工具,听说mathematical可以,手头没有,http://wolframalpha.com/试了下,数字太大好像不行。

比赛结束后自己写了个类:

from fractions import gcd

class ChineseRemainder:

    def __init__(self, M, R):
        assert(len(M)==len(R))
        self.M=M
        self.R=R

    def egcd(self,a, b):
        x,y, u,v = 0,1, 1,0
        while a != 0:
            q,r = b//a,b%a; m,n = x-u*q,y-v*q # use x//y for floor "floor division"
            b,a, x,y, u,v = a,r, u,v, m,n
        return b, x, y

    def modinv(self,a, m):
        g, x, y = self.egcd(a, m) 
        if g != 1:
            return None
        else:
            return x % m

    def CR(self, r1, r2, m1, m2):
        return (r1*self.modinv(m2,m1)*m2+r2*self.modinv(m1,m2)*m1)%(m1*m2)

    def Add(self, m, r):
        self.M.append(m)
        self.R.append(r)

    def Union(self):
        M=self.M
        R=self.R
        assert(len(M)>1)
        m1=M[-1]
        m2=M[-2]
        r1=R[-1]
        r2=R[-2]
        m=gcd(m1,m2)
        assert((r1-r2)%m==0)
        z=self.CR(0,(r2-r1)//m,m1//m,m2//m)
        M=m1*m2//m
        x=(z*m+r1)%M
        self.R[-2]=x
        self.M[-2]=M
        self.R.pop(-1)
        self.M.pop(-1)

    def UnionAll(self):
        while len(self.M)>1:
            self.Union()
            print(self.M)
        return (self.M[0],self.R[0])

后来就解出来了,刚好32字符。然后又扒了20个数据,解出来还是一样,就对了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值