qchecker
We’re given a fun Ruby quine program that self-modifies based on its argument:1
[franksh@moso qchecker]$ ruby qchecker.rb
eval$uate=%w(a=%(eval$uate=%w(#{$uate})*"");Bftjarzs=b=->a{a.split(?+).map{|b|b.to_i(36)}};c=b["awyiv4fjfkuu2pkv+awyiv4f
v ut 71 6g 3j +a x
c e5e4pxrogszr3+5i0o mfd5dm9xf9q7+axce5 e4khrz21ypr+5htqqi 9iasvmjri7+axcc76i 03zrn7gu7+cbt4 m8 xybr3cb27+1ge6 s
n jex10w3si9+1k8vdb4 fzcys2yo0"];d,e,f, g,h,i=b["0+0+zeexa xq012eg+k2htkr1ola j6+3cbp5mnkzll t3 +2qpvamo605t7j "
] ;(j=eval(?A<<82<<7 1<<86)[0])&&d==0&& (e+=1;k=2**64;l=-> (a,b){(a-j.ord)*25 6.pow(b-2,b)%b }; f=l[f,k+13];g= l
[ g, k+ 37];h=l[h,k+51];i= l[i,k+81];j==?}&&( d=e==32&&f+g+h +i ==0?2:1);a.sub !
(/"0.*?"/,'"0'+[d ,e ,f,g,h,i].map{|x|x .to_s(36)}*?+<<34) );srand(f);k=b["7a cw+jsjm+46d84" ]; l=d==2?7:6;m=[ ?
#*(l*20)<<10]*11* "" ;l.times{|a|b=d==0 &&e!=0?rand(4):0;9 .times{|e|9.times{ |f|(c[k[d]/10* *a %10]>>(e*9+f)& 1
)!=0&&(g=f;h=e;b. ti mes{g,h=h,8-g};t=( h*l+l+a)*20+h+g*2+ 2;m[t]=m[t+1]=""<< 32)}}};a.sub!( /B .*?=/,"B=");n= m
. co un t( ?# )- a.length;a.sub !
("B=","B#{(1..n).map{(rand(26)+97).chr}*""}=");o=0;m.length.times{|b|m[b]==?#&&o<a.length&&(m[b]=a[o];o+=1)};puts(m))*""
[franksh@moso qchecker]$
[franksh@moso qchecker]$ ruby qchecker.rb > out.rb; for c in `echo "SECCON{AAAAA}" | fold -w1`; do ruby - $c < out.rb > tmp.rb; mv tmp.rb out.rb; done; cat out.rb
eval$uate=%w(a=%(eval$uate=%w(#{$uate})*"");Bygzwgnlnjkmwzhugrpnmdvlcwpmqlebkawvjklvmkmkc=b=->a{a.split(?+).map{|b|b.to_
i (36)}} ;c=b[" aw yi v4 fj fkuu2pkv+awyiv4fvut71
6 g3j+ax ce5e4p xr ogszr3+5i0omfd 5d m9xf9q7+axce5e 4k hrz21ypr+5htqq i9 iasvmjri7+axcc76i03zrn7gu7+cbt4m8xybr
3 cb27+1 ge6snj ex 10w3si9+1k8vdb 4f zcys2yo0"];d,e ,f ,g,h,i=b["01+d +m 6177zx5cmtf+1mdtba3ieal9d+2v6gou7jwyt
c+2 uf h2 68 232n wq"];(j=eval(? A< <82<<71<<86)[0 ]) &&d==0&&(e+=1; k= 2**64;l=->(a,b){(a-j.ord)*256.pow(b-2
,b) %b }; f= l[f, k+ 13];g=l[g,k+37 ]; h=l[h,k+51];i= l[ i,k+81]; j==?}&&(d=e==32&&f+g+
h+i == 0? 2: 1);a .sub!(/" 0.*?"/,' "0'+[d,e,f,g,h ,i ].map{|x|x.to_ s( 36)}*?+<<34)); srand(f);k=b["7acw+js
jm+46 d84"]; l=d==2 ?7:6;m=[?# *(l*20 )<<10]*11*"";l .t imes{|a|b=d==0 && e!=0?rand(4):0 ;9.times{|e|9.times{|
f|(c[ k[d]/1 0**a%1 0]>>(e*9+f)& 1)!= 0&&(g=f;h=e;b. ti mes{g,h=h,8-g} ;t =(h*l+l+a)*20+ h+ g*2+ 2;m[ t]=m[
t+1]= ""<<32 )}}};a .sub!(/B.*?=/, "B =" );n=m.count(?# )- a. leng th;a .sub!
("B=","B#{(1..n).map{(rand(26)+97).chr}*""}=");o=0;m.length.times{|b|m[b]==?#&&o<a.length&&(m[b]=a[o];o+=1)};puts(m))*""
[franksh@moso qchecker]$
Cool!
Caveat: I’ve never written even a hello world in Ruby2, and I solved this task simply by deconstructing the program “naively,” so this is going to be a bit sketch.
My emacs buffer ended up such:
# a=%(eval$uate=%w(#{$uate})*"");
# Bftjarzs=b=->a{a.split(?+).map{|b|b.to_i(36)}};
Apparently Ruby syntax for lambdas are ->args{code}
and apparently you don’t
call lambdas with v(x)
but rather v.call(x)
, but, again apparently, v[x]
is a short-hand trick, even though []
is usually used for lookups like in
Python. So there’s this b
lambda that splits a string on +
and then
interprets the fragments as base-36. Useful.
%(...)
is some weird Ruby syntax for literal string arrays(?), ?<char>
is an
escaped literal char, a*""
joins an array of strings (perhaps * :: [T] -> T -> T
for some monoid T
works like intersperse?). I mean, I could definitely
get into Ruby, this is good stuff.
# c=b["awyiv4fjfkuu2pkv+awyiv4fvut716g3j+axce5e4pxrogszr3+5i0omfd5dm9xf9q7+axce5e4khrz21ypr+5htqqi9iasvmjri7+axcc76i03zrn7gu7+cbt4m8xybr3cb27+1ge6snjex10w3si9+1k8vdb4fzcys2yo0"];
# 2413138514168077294502911 0x1ff0080402010080403ff b'\x01\xff\x00\x80@ \x10\x08\x04\x03\xff'
# 2413138514203124227638271 0x1ff0080403ff0080403ff b'\x01\xff\x00\x80@?\xf0\x08\x04\x03\xff'
# 2415504318135715148071935 0x1ff80c0603e10080403ff b'\x01\xff\x80\xc0`>\x10\x08\x04\x03\xff'
# 1216023231471466527982591 0x10180c06030180c0603ff b'\x01\x01\x80\xc0`0\x18\x0c\x06\x03\xff'
# 2415504318120356412261375 0x1ff80c06030180c0603ff b'\x01\xff\x80\xc0`0\x18\x0c\x06\x03\xff'
# 1214839173222390695330815 0x1014090443ff80c0603ff b'\x01\x01@\x90D?\xf8\x0c\x06\x03\xff'
# 2415495076716156996027391 0x1ff8040201ff0080403ff b'\x01\xff\x80@ \x1f\xf0\x08\x04\x03\xff'
# 75705726472931768279551 0x100804020100804021ff b'\x10\x08\x04\x02\x01\x00\x80@!\xff'
# 321749341105789098926865 0x442211154aa554462311 b'D"\x11\x15J\xa5TF#\x11'
# 345406059408174499233792 0x49248000000000000000 b'I$\x80\x00\x00\x00\x00\x00\x00\x00'
Here I found a big such “array” of raw data, which I pretty printed, and it looks to me maybe it’s used for constructing the ASCII-art output…
# d,e,f,g,h,i=b["0+0+zeexaxq012eg+k2htkr1olaj6+3cbp5mnkzllt3+2qpvamo605t7j"];
# d = 0 0x0 b''
# e = 0 0x0 b''
# f = 4659461645708163688 0x40a9bbae0cfdfa68 b'h\xfa\xfd\x0c\xae\xbb\xa9@'
# g = 2641556351334323346 0x24a8b18187759492 b'\x92\x94u\x87\x81\xb1\xa8$'
# h = 15837377083725718695 0xdbc9aa8c316224a7 b'\xa7$b1\x8c\xaa\xc9\xdb'
# i = 12993509283917003551 0xb45237d9e59cfb1f b'\x1f\xfb\x9c\xe5\xd97R\xb4'
(j=eval(?A<<82<<71<<86)[0])
&& d==0 && (
e+=1;
k=2**64;
l=->(a,b){(a-j.ord)*256.pow(b-2,b)%b};
f=l[f,k+13];
g=l[g,k+37];
h=l[h,k+51];
i=l[i,k+81];
j==?} && (d=e==32&&f+g+h+i==0?2:1);
a.sub!(/"0.*?"/,'"0'+[d,e,f,g,h,i].map{|x|x.to_s(36)}*?+<<34)
);
Now wait just a second, this is way more suspicious. pow
, %
-mod, 2**64+13
is a prime i recognize, etc. So yeah, this is likely to be the logic.
But following the calculations through, we have a bunch of steps , with and being
the ordinal value of our input characters. And the goal seems to be and since they’re non-negative (Ruby’s %
operator is sane) it means
each sequence should end at 0.
From this it’s reasonable to assume that the starting values is the flag itself, and indeed the modulus is big enough to reconstruct it with CRT:
>>> crt(vals, [2**64 + v for v in [13,37,51,81]]).bytes()
b'}!!!3n1uQ_ru0y_3t1rw_5t3L{NOCCE'
And there we go.
The rest isn’t that interesting, but I guess it constructs the ASCII-art output:
srand(f);k=b["7acw+jsjm+46d84"];
l=d==2?7:6;
m=[?#*(l*20)<<10]*11*"";
l.times{
|a|
b=d==0
&& e!=0?rand(4):0;
9.times{|e|9.times{|f|(c[k[d]/10**a%10]>>(e*9+f)&1)!=0&&(g=f;h=e;b.times{g,h=h,8-g};t=(h*l+l+a)*20+h+g*2+2;m[t]=m[t+1]=""<<32)}}
};
a.sub!(/B.*?=/,"B=");
n=m.count(?#)-a.length;
a.sub!("B=","B#{(1..n).map{(rand(26)+97).chr}*""}=");
o=0;
m.length.times{
|b|m[b]==?#
&&
o<a.length&& (m[b]=a[o]; o+=1)
};
puts(m)