Codewars - Tap Into Mr. Mxyzinjin 系列

有趣的一个系列。

Tap Into Mr. Mxyzinjin’s Toy Safe

输出login,可以看到:

function check(pw) {
    return new global.RegExp(`^${pw}$`).test(passwd);
}

容易想到二分区间[0x4e00,0x9fff],用^(.{i}[l-r])|\$求出第i+1个字符即可。

Tap Into Mr. Mxyzinjin’s Safe

输出login,可以看到:

function check(pw) {
    return void new RegExp(`^${pw}$`).test(passwd);
}

注意到void标识符使得login只能返回undefined,考虑用时序攻击的方法求出串。
继续二分,容易写出正则表达式^.{i}[l-r].{length-i-1}$来第i+1个字符是否位于区间[l,r]内,此时当区间正确时,login会比另一半区间运行更多的时间,因为引擎还会继续匹配.{length-i-1}。但当i增大时,这个正则表达式的效果迅速减弱。
考虑用|运算符来处理这个问题,可以写出正则表达式^(.{i}[l-r])|(\\D{4})$,此时当区间错误时,login一定会比另一半区间运行更多的时间,因为引擎还会继续匹配(\\D{4}),而且花费的时间足够让我们将两者区分开来。
至于字符串长度,我们设一个足够小的值,当两段区间的login耗时之差不大于这个值时,判定字符串结束即可。(其实我就随便设了几个,跑了几发就过了

const {performance}=require('perf_hooks');
function sgett(l,r,dt,login){
  var i,j,k,t,gc=e=>e.toString(16),reg=`(.{${dt}}[\\u${gc(l)}-\\u${gc(r)}])|(\\D{4})`;
  var run=_=>{var dk=performance.now();login(reg);return performance.now()-dk;}
  var ar=[run(),run(),run(),run()];ar.sort();
  return ar[1]+ar[2];
}
function getc(login,dt){
  var mx=0,ts,i,j,k;
  var b=0x4e00,e=0x9fff,m;while(b<e){
    m=(b+e)>>1;var x=sgett(b,m,dt,login),y=sgett(m+1,e,dt,login);
    if(Math.abs(x-y)<=0.0019)return null;
    x<y?e=m:b=m+1;
  }
  return String.fromCharCode(b);
}
function crack(login) {
  var i,t,rs='';for(i=0;;++i){
    t=getc(login,i);if(!t)break;rs+=t;
  }
  return rs;
}

Tap Into Mr. Mxyzinjin’s Matrix

输出login,可以看到:

function check(pw) {
    return !(Array.isArray(pw) || pw.length>passwd.length) && [...passwd].every((c,i)=>c===pw[i]);
}

观察到对pw的类型检验不严,直接用proxy伪造一个对象传进去记录被比较的字符即可。

Tap Into Mr. Mxyzinjin’s Brain

输出login,可以看到:

function check(pw) {
    return typeof pw==='string' && passwd.length>=pw.length && passwd.every((e,i)=>e===pw[i]);
}

显然此时我们只能用时序攻击去求出串。
这题倒不用拐弯抹角的,直接写就好了,注意运行多次login减少performance.now()的误差。我是运行了1000次,排序之后取中间100次的值作为结果,效果还不错。

const {performance}=require('perf_hooks');
function crack(login) {
  console.log(login.toString());
  while(1){
    var pw='',i,j,k;for(i=0;i<30;++i){
      var mw=0,ps='';for(j=0;j<10;++j){
        var t=pw+j,ar=new Array(1000);
        for(var d=0;d<1000;++d){
          var a=performance.now();
          login(t);
          var b=performance.now();
          ar[d]=b-a;
        }
        ar.sort();var s=0;for(var z=450;z<550;s+=ar[z++]);
        if(s>mw)mw=s,ps=''+j;
      }
      pw+=ps;
    }
    for(i=0;i<10;++i)for(j=0;j<10;++j)if(login(pw+i+j))return pw+i+j;
  }
}

Tap into Mr. Mxyzinjin’s Stream

输出login,可以看到:

function check(pw) {
    if(tries++>=32) throw new Error('The stream becomes too unstable, it exploded and killed you in the process');
    return typeof pw==='string' && pw.includes(passwd);
}

容易想到找到一个包含所有 x < 2 25 x< 2^{25} x<225的串 S S S,通过login判断目标是否在 S S S中来二分,容易在25次调用下解决问题。注意到如果我们从一个24位的01串开始,在其后添加0或1,得到的数可以用 2 s + 0 ∣ 1 m o d    2 25 2s+0|1\mod 2^{25} 2s+01mod225表示,那么我们尝试直接完全随机地生成这个串,在线下测试发现可以覆盖 80 % 80\% 80%的数,优化一下,我们直接不用随机,每次 O ( 1 ) O(1) O(1)在表中查询一下新的 s s s是否已经出现过了,测试发现这么做可以使得 S S S覆盖 99 % 99\% 99%的数,虽然我不会证(ε=ε=ε=┏(゜ロ゜;)┛
注意JavaScript的随机取值速度非常缓慢,如果直接生成一个 2 25 2^{25} 225的表是一定会T的。我是直接生成了一个 2 20 2^{20} 220Uint32Array用做BitSet来记录每个数的状态。

var keys=25,st=new Uint32Array(1<<20);
function gensm(){
  var i,j,k,d,dt,t,rs='',hmd=(1<<5)-1,n=1<<(keys-3),md=(1<<25)-1;
  for(d=i=0;i<24;++i){
    k=0,d=((d<<1)+k)&md;rs+=k;
  }
  for(i=0;i<n;++i){
    var ts='',dn=1<<3;for(j=0;j<dn;++j){
      k=0,dt=((d<<1)+k)&md;if(st[dt>>5]&(1<<(dt&hmd)))k^=1;
      d=((d<<1)+k)&md;if(dt==d||!(st[d>>5]&(1<<(d&hmd))))st[d>>5]^=(1<<(d&hmd));
      ts+=k;
    }
    rs+=ts;
  }
  return rs;
}
function _cl(login,n,len){
  if(len==1)return n;
  var mi=24+(len>>1),l=n.slice(0,mi);
  if(login(l))return _cl(login,l,len>>1);
  return _cl(login,n.slice(mi-24),len>>1);
}
var sm=gensm();
function crack(login) {
  return _cl(login,sm,1<<keys);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值