[XNUCA2018] StrangeInterpreter
本题做题过程比较奇怪,没有什么好参考的
唉, OLLVM
尝试用插件去一下
十分钟过去了, 它还没有完事
我打算先开一局CSGO
大概有几百行这种东西吧..., 又是一道vm题
看上去是读入一个长32的OPcode,然后开始搞事, 最后这要正好变成0-5a-z
好长啊..看看猜猜先.
根据它是一道题, 它要有解, 所以投机取巧的判断要么这玩意有规律可找,要么就得自动化操作, 肯定不可能是人眼硬看, 我们先输入个0-5a-z看看这玩意会变成啥
?我直接疑惑了,这是对称的直接把flag给我了吗?
X-NUCA{5e775e5e7}
这给了一部分吧算是, 和同系列的Interpreter的flag蛮像的, 我再把这个输进去试试. 感觉可能是flag分段加密了, 然后第一段恰好是个异或之类的
额,然后后半段flag就出来了, {5e775e5e775e5e775e5e775e}
咱就是说多少有点疑惑了...
交一发试试.. 果然错了.. 看来是我太天真了
额, 回头看了一发发现是多写了一个5e7, 这.. 竟然过了
可是这题我还.. 完全不知道它干了啥
[zer0pts2020] vmlog
熟悉brainfuck的话这题不难,基本上就是一个brainfuck解释器
先贴exp再解释
base = 247905749270528
p = 4611686018427387903
seq = [
0,
28629151,
4588277794174371330,
4557362566608270193,
4597225827500493308,
4399455111035409631,
3664679811648746944,
1822527803964528750,
2107290073593614393,
103104307719214561,
3773217954610171964,
1852072839260827083,
3465871536121230779,
223194874355517702,
1454204952931951837,
3030456872916287478,
426011771323652532,
1276028785627724173,
1962653697352394735,
1600956848133034570,
2045579747554458289,
4248193240456187641,
4478689482975263576,
1235692576284114044,
2579703272274331094,
1394874119223018380,
4275420194958799226,
2401030954359721279,
1313700932660640339,
2401701271938149070,
4217153612451355368,
2389747163516760623,
3483955087661197897,
4522489230881850831
]
flag = ""
for i in range(34) :
for j in range(0x30, 0x7f) :
if (seq[i-1] + j) * base % p == seq[i] :
flag += chr(j)
print(flag)
我是从头直接翻译brainfuck到一个类c的东西, 翻译一半就看懂它在干啥了
reg ++, mem[p] = reg;
reg ++, p ++ , mem[p] = reg;
p ++, reg += 60;
mem[0] = 1, mem[1] = 2;
reg = 60, p = 2;
for(int i = 1; i <= 60; i ++ ) {
mem[0] *= mem[1];
}
p -= 2;
reg = mem[p];
reg --;
mem[p] =reg;
p ++;
for(int i = 1; i <= 6; i ++ ) {
reg *= mem[p];
reg --;
mem[p] = reg;
}
p ++;
reg = mem[p];
reg *= mem[p];
reg += 5;
mem[p] = reg;
reg *= mem[p];
reg -= 5;
mem[p] = reg;
reg *= mem[p] ** 4;
p += 2;
reg = mem[p];
reg ++;
mem[p] = reg;
do {
print(mem);
reg = mem[4];
reg --;
mem[4] = reg;
p -= 2;
reg = mem[2];
p ++;
// p == 3
reg += input();
do {
reg *= mem[1];
mem[3] =reg;
reg = mem[0];
reg = mem[3] % reg;
// reg = (reg + input) * mem[1] % mem[0]
mem[2] = reg;
reg = mem[4];
mem[3] = reg;
reg = mem[4];
mem[4] = reg;
reg = mem[3]
}while(reg)
p ++;
reg = mem[4];
}while(reg)
一般来讲, brainfuck每个位置的指针都是比较固定的, 不会出现循环中相同语句指针不同的情况.
所以所有的mem[p]的p都可以直接填上.
这样就发现前面一直是输入无关的 , 且围绕着mem0,1,2,4, 检查题给的log, 0 1 4 都是固定的, 2是变的
等翻译到do while的时候, 学过hash的人一眼就能看懂了, 就是在做一个 h = (h+c)*base%p;
所以前面那些翻译都白翻译,就是预处理了一个base和一个p
因为给了每步hash的结果,直接爆破每一个字符即可
一般来讲这种类型的解释器, 包括asm类型的vm题,我都是先写出来汇编代码, 然后自己手动反汇编写一个C, 再把冗余的东西优化下去, 比如 r = mem[0], r ++, mem[0] = r这种,就可以写成 r = ++mem[0], 而且经验来讲,这种r一般下一步都不会用的,都是为了算mem临时放一下.
pwnhub2022某内部赛 easyre
binary :
EasyRe.exe.bak.zip
确实比较easy
main里面有一些奇怪的东西, 和线程有关的,但是还没什么卵用.
关键函数在1400014A0, 一开始没看出来, 后来发现就是一个循环展开的 XTEA
检查结果的交叉引用,在13F0处比对,和明文比对.
接下来是找XTEA的key, 发现是运行时四个rand生成的, 猜测是假随机, 因为没找到srand().
把Thrd包括scanf啥的都扬了, 直接动调获得key, 这里不知道为啥scanf也会throw一个error, 但是也没打算仔细探究, 扬了就行
解题脚本
#include <cstdio>
#include <cstdint>
unsigned char ida_chars[] =
{
0x34, 0x9B, 0xB2, 0xC0,
0x6A, 0xAF, 0x30, 0xEF,
0x38, 0xB2, 0xCC, 0x98,
0x95, 0xF1, 0xB6, 0x85,
0x85, 0x06, 0x48, 0xA2,
0x59, 0x9B, 0x3D, 0xA6,
0x1E, 0xC7, 0x91, 0xF1,
0x7B, 0x76, 0x90, 0x67
};
unsigned int key[4] = {
0x29, 0x4823, 0x18BE, 0x6784
};
void decipher(uint32_t v[2]) {
unsigned int i;
uint32_t v0=v[0], v1=v[1], delta=0x9E3779B9, sum=delta*32;
for (i=0; i < 32; i++) {
v1 -= (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum>>11) & 3]);
sum -= delta;
v0 -= (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]);
}
v[0]=v0; v[1]=v1;
char* p = (char*)v;
for(int i = 0; i < 8; i ++ ) printf("%c", p[i]);
}
int main() {
unsigned int * p = (uint32_t *)&ida_chars[0];
decipher(p);
decipher(p+2);
decipher(p+4);
decipher(p+6);
}
[QCTF2018] ollvm
我承认我没看出来这是 RSA,不过能爆破谁还 RSA
巨恶心无比的C++逆向,不过虽说是C++逆向,不用考虑动态构建和虚函数
主要是C++的GNU大整数库,这东西想要看明白要费死劲了, 我选择直接从判断逻辑和输入流向入手,看看分别能走到哪儿.
输入从参数输入,要求是38位, 主函数直接反Ollvm,
我们发现了这样的一个地方, 其中mpow原名encrypt, 看上去就很像关键函数, 对吧!
这个函数做的事就是把pow(2, 3, 4)放在1里面, 具体猜测应该是2, 3, 4地址上指向的大整数的值. 然后要和第i个BigInt相等. 每个BigInt都是明文写在函数里的
到这里我认为核心应该就是两件事:一件事是动调找到方次和模数, 一件事是找到底数和输入的关系. 根据大整数的数目, 我猜底数应该就只是0-9而已,因为大概有30多个大整数, 每次Load的次序是递增的, 之所以是BigInt2 + i, 我认为是第一个大整数和后面的的载入方式不同, 肯定是有所区别, 这里也变相印证了我的猜测. 猜测第一个BigInt应该是次方或者模数之类的, 并且还有一个比较小的Int. 合理猜测大的是模数小的是次方, 我们先来试试
def qpow(x, y, p) :
ans = 1
while y :
if y & 1 :
ans *= x
ans %= p
y >>= 1
x *= x
x %= p
return ans
y = 41609
p = 102651473104605400881443209436335207143364593902607831104659513254775518923447923418776053184964127417292310751110954018379230925038183526950825957076822861193097601598650310458833168757490447855063621089262488908245458996608300475771810218545021100532975095664160301297355685288040601637561442455179633110079846710134322730324284152857852000538712290118985202146945804609486282114958343308956178741452118058076488074220293303425550534104695916297651437260840156614208398265649572936591070707149727591261946573549966777759067945870542262596595729532884877741113485459644140649532472144766397594354234108576755815917168003359597993072007662182342629809152936969421895552780144527391604982975657819198048361600023456702313892160810273481932944137974282216381637292972896247123735265111606267014480229602366343234386044792316318028789845062796141719269577108623503432313385433912012680162134432871999515572195678215741149038561107783321860080221501590006190270380869307116405522430479782169008415898242325411271043447827685653357582905940740125675545255630155684164945409286389585255976330122263610987012875989116119004168237430091958750377684385918076049811463810026787681970329875350154256541485562299929744955216059064911687028831046268556619196694147338675484825249402694136964225868844636091765013673567792490217775308571769318669397501731133181116010571526812759760618304461298242712032781458409426120142005834592865613957784145176085762288081896026523415885624361265385011667667343515890459493297688753
for i in range(10) :
print(qpow(i, y, p))
print("ENDLINE")
没有找到结果, 不过确实不应该找到结果, 答案并非0-9, 不然这38位必有重复的, 我们把数据拿出来在0xff内试一下
def qpow(x, y, p) :
ans = 1
while y :
if y & 1 :
ans *= x
ans %= p
y >>= 1
x *= x
x %= p
return ans
y = 41609
p = 略
d = 57919849217920783735142893327761275876448411493502728132956707072863216231372451540130539410530734693690133611505015750745473767117095699247694234614292891944184479830561690619859951393308826035561100556994427219937010060311702288149648005097758983511057426746484396534545030289932356028544687342592923922803783495649273096408267525320445022288126075221943749274456753050741344845451290180528383828407320579317296850546238777349897433170487395669442216318823364023542414555456429730981078587184721198533108314233131081265498525707860216159899618142092421526084615060168328956981349473206991065608827035640385395182812441310805733145868912422735307512348165640379213917204367297929602757223239062306798137457618628488760505602009175153415828242259280122053012475123425514485973565026810325008744469632920912645134167328810176327191572519778068538837550945791269407402823695996076054889265210839501169276344626279938887699237729275344538821558290431868218566474303662853946230500962709599747461479547444903507839231799841931377560127953941850483973009101401257071956245964246044190198357228713446159217751836990331758278814381255485259574037552968645187437467109446571102137819749089303219446589473342531325470927244288064411756855877536822423517040253923560090409380732344749564186154861678426280672246199968365975314800393618660752678522581994910359718167871693739260232035357100533769726199069653855768756934004412511398443096508836948629027697545138474472751597338527768840014851488519919974390498415309
for i in range(0xff) :
tmp = qpow(i, y, p)
if d == tmp :
print("CONG", i)
果然, i = 85 符合条件, 不过感觉85是U,这个字符还是有点怪, 应该是QCTF(81, 67, 84, 70)才对,不过这至少证明我们猜测的方向正确了, 把所有的数据都拿下来跑一下, 没有想到什么好方法能自动化,只好一个个手搓了
聪明的我手搓到第二个发现这玩意竟然是个不可见字符,这可如何是好?... 还好没把所有的都拿出来
这些变量范围是V63-V99, 37个值, 怎么看怎么像一一对应, 但是我把QCTF这四个扔进去发现...没有..完全没有, 看来猜测方向还是错了
不过我把爆破范围扩大到0xffff,发现在0xffff之内, 都能找到对应的解, 前六个分别是, 我觉得这个方向肯定还是能用上, 不过还是要看看输入流向,我们回头分析一下输入
0x55
0x14f
0x152
0xeb
0x19a
0x200
0x211
可以发现这里输入是动过一次的,先把输入丢到118里面,然后输入取5次方, 模323结果扔116里大概是
看看116做了什么, 如果看不懂就爆破一波0xff内5次方%323等于刚刚结果的
等一下,我们不妨先试一下'Q', 81 ** 5 %323 = 47, 还是不对, 还要继续看, 发现这里:
这个地方和encpara2也就是底数有关, 可以看到这是把116[i]和116[i-1]加和了, 那我们再用QCTF这四个猜一波, 第二位 335 和刚刚第二位结果恰好相同, 看下一位 !! 338 也恰好符合, 至此整个程序流程基本分析清楚了,我们再理一遍写脚本
读入38位可见字符, 对于每一位的ord取5次方再模323放进v116, 定义v116[-1] = 38, 然后v117[i] = v116[i] + v116[i-1]
之后用固定的指数和模数对v117每一位取次方, 和明文比较.
exp太大了,和文件一起打包上传了
ollvm.zip
QCTF{5Ym4aOEww2NcZcvUPOWKYMnPaqPywR2m}
[SWPU] EasiestRE
关键字符串+交叉引用,可以看得出这是一个自己调试自己的多线程程序, 父进程预置了一些Code, 然后在子进程终端的时候通信,在ExceptionAddress写入
子进程这里正好是长度为6的一个nop+一个int3, 我们直接把v16先Patch上
于是有了这里:
我们知道还有一段长度0x1E的要写,跟进40247D, 40460B, 4087E0, 又是一个debugbreak, 一长段的nop恰好可以填进去
写个脚本Patch进去
v16 = [0x90, 0x83, 0x7D, 0xF8, 0x18, 0x7D, 0x11]
base_addr = 0x408AF8
for i in range(7) :
patch_byte(base_addr+i, v16[i])
arr = [0x90,0xF,0xB6,0x55,0xF7,0x8B,0x45,8,0x8B,4,0x90,0xF,0xAF,0x45,0xFC,0x33,0xD2,0xF7,0x75,0xF8,0xF,0xB6,0x4D,0xF7,0x8B,0x45,0xC,0x89,0x14,0x88]
base_addr = 0x408824
for i in range(30) :
patch_byte(base_addr+i, arr[i])
src是原来的函数处理好传进来的, dst是目标,这几个函数传的都是指针,但是IDA自动分析的都不太对,建议改了好看一点
这部分变换的逻辑是,共变换len轮,第一轮str[i]和0x1234异或,之后v9[i] = (v4&Str[i]) ? 1 : 0 , target[i] += pre[7-k]*v9[k], 后面每轮str[i]和target[i-1]异或.最后在40384B验证target
check函数很诡异,最后是看了别的师傅的wp才知道要去汇编里找, 被IDA骗了,以为函数结束了
整个加密过程不难,直接逆一下即可:
先找异或过的flag[i]
对于flag[i], 和它直接产生关系的是target[i-1] 和 target[i] , 根据target[i]的情况就能倒推flag[i]的每一位, 这是根据pre来的, 对于每个target[i], 他可以看做一个pre[]的子集和, pre可以通过预处理给出,子集可以通过2^8枚举给出, 这个时间复杂度可以接受, 在处理过flag[i]的Xor后值后,对于flag[0], xor 0x1234, 对于flag[i], xor target[i-1]即可
target = [
0x1234, 0x3d1,0x2f0,0x52,0x475,0x1d2,0x2f0,0x224,0x51c,0x4e6,0x29f,0x2ee,0x39b,0x3f9,0x32b,0x2f2,0x5b5,0x24c,0x45a,0x34c,0x56d,0xa,0x4e6,0x476,0x2d9
]
gPre = [
2, 3, 7, 14, 30, 57, 120, 251
]
flag = []
pre = [0] * 8
for i in range(8) :
pre[i] = 41 * gPre[i] % 0x1EB
print(pre)
for i in range(1, 25, 1) :
for j in range(0x100) :
temp, ans = 0, j
for k in range(8) :
if j & 1 :
temp += pre[7-k]
j >>= 1
if temp == target[i] :
print(ans)
flag.append(ans)
break
for i in range(24) :
print(chr(flag[i] ^ target[i] & 0xff), end = "")
[SWPU2019] Easyre
C++ 逆向, 关键函数是个虚函数,要动调看看
main里有个很明显的反调,扬了就行
this+12是这样的
int __thiscall this_12(const char **this)
{
int v2; // [esp+Ch] [ebp-B0h]
unsigned int v3; // [esp+14h] [ebp-A8h]
int i; // [esp+24h] [ebp-98h]
char v6[56]; // [esp+30h] [ebp-8Ch] BYREF
char v7[20]; // [esp+68h] [ebp-54h] BYREF
char v8[48]; // [esp+7Ch] [ebp-40h] BYREF
int v9; // [esp+B8h] [ebp-4h]
v3 = (unsigned int)&this[2][strlen(this[2])];
strcpy(v8, "Ncg`esdvLkLgk$mL=Lgk$mL=Lgk$mL=Lgk$mL=Lgk$mLm");
sub_F926C0(v6, 0x38u);
sub_F92B00();
v9 = 0;
for ( i = 0; i < 45; ++i )
v8[i] ^= 0x10u;
sub_F926C0(v7, 0x14u);
sub_F92A70(v8, 1);
LOBYTE(v9) = 1;
v2 = (unsigned __int8)sub_F94260(this[2], v3, v6, v7, 0);
LOBYTE(v9) = 0;
sub_F92A50(v7);
v9 = -1;
sub_F926A0();
return v2;
}
其中this[2]是输入, F926C0是memset为0, 猜测F92B00也是一个清0函数.
傻了,管他干啥,直接动调, F94260前都与输入无关, 动调到这儿的内存是这样的
swpuctf\{\w{4}\-\w{4}\-\w{4}\-\w{4}\-\w{4}\}
进行了一波flag格式的提示,本着不费二遍事,不返二遍工的原则,我们把刚刚随便输入的东西改成flag格式再来一次
一番动调下来猜测这个this+12就是判断flag是否符合格式,然后把有意义内容单独拿出来
然后在this+24中处理,最后this+40是判断函数,要this[12~51] == this[52-91]
动调拿Target
unsigned char ida_chars[] =
{
0x08, 0xEA, 0x58, 0xDE, 0x94, 0xD0, 0x3B, 0xBE, 0x88, 0xD4,
0x32, 0xB6, 0x14, 0x82, 0xB7, 0xAF, 0x14, 0x54, 0x7F, 0xCF,
0x20, 0x20, 0x30, 0x33, 0x22, 0x33, 0x20, 0x20, 0x20, 0x30,
0x20, 0x32, 0x30, 0x33, 0x22, 0x20, 0x20, 0x20, 0x24, 0x20
};
this+24里面调用了this+28, 可以很明显的看出来this+28做了这件"把有意义的内容单独拿出来"事
this+28是F91E40, IDA未能正确的识别并反汇编这部分代码,这里利用了DL的RCL来更改符号位,并用JB指令转移, JB是无符号小于转移,相当于要求CF = 1, 也就是 DL 无符号左移产生进位,也就是DL的最高位为1
我们查看这部分汇编代码
.text:00F91EB9 loc_F91EB9: ; CODE XREF: sub_F91E40+7E↓j
.text:00F91EB9 rcl dl, 1
.text:00F91EBB inc ebx
.text:00F91EBC jb short loc_F91EC0
.text:00F91EBE jmp short loc_F91EB9
不断地对dl右移,每次右移让ebx++, 直到有进位时跳到下一代码块, 并把ebx保存到栈上, 清空ebx
也就是看看dl的最高位1在第几位
loc_F91EC9: ; CODE XREF: sub_F91E40+8E↓j
.text:00F91EC9 rcr al, 1
.text:00F91ECB inc ebx
.text:00F91ECC jb short loc_F91ED0
.text:00F91ECE jmp short loc_F91EC9
同理,这部分是看看al的最低位1在第几位
dl, al都是从一个栈上地址mov来的,而每次这个地址指向一位flag
保存的地址分别为[ebp+esi+var_1C+8] [ebp+...+0xC], 同时再 esi ++
Stack[000037E0]:0073FBE4 db 3
Stack[000037E0]:0073FBE5 db 3
Stack[000037E0]:0073FBE6 db 3
Stack[000037E0]:0073FBE7 db 3
Stack[000037E0]:0073FBE8 db 4
Stack[000037E0]:0073FBE9 db 0
Stack[000037E0]:0073FBEA db 1
Stack[000037E0]:0073FBEB db 0
我输入的前四位是 0123
, 分别是 0011 0000 / 0011 0001 / 0011 0010 / 0011 0011
对应的最高位1和最低位1分别在3 3 3 3 和 4 0 1 0, 这部分代码逻辑是显然的
.text:00F91EE5 loc_F91EE5: ; CODE XREF: sub_F91E40+171↓j
.text:00F91EE5 mov eax, [ebp+var_20]
.text:00F91EE8 add eax, 1
.text:00F91EEB mov [ebp+var_20], eax
.text:00F91EEE
.text:00F91EEE loc_F91EEE: ; CODE XREF: sub_F91E40+A3↑j
.text:00F91EEE cmp [ebp+var_20], 4
.text:00F91EF2 jge loc_F91FB6
.text:00F91EF8 mov ecx, [ebp+var_20]
.text:00F91EFB movzx edx, [ebp+ecx+var_1C+8]
.text:00F91F00 mov eax, [ebp+var_20]
.text:00F91F03 movzx ecx, [ebp+eax+var_1C+0Ch]
.text:00F91F08 add edx, ecx
.text:00F91F0A mov eax, [ebp+var_20]
.text:00F91F0D mov [ebp+eax+var_1C+4], dl
.text:00F91F11 mov ecx, [ebp+var_20]
.text:00F91F14 movsx edx, [ebp+ecx+var_1C]
.text:00F91F19 mov eax, [ebp+var_20]
.text:00F91F1C movzx ecx, [ebp+eax+var_1C+8]
.text:00F91F21 shl edx, cl
.text:00F91F23 mov ecx, [ebp+var_20]
.text:00F91F26 mov [ebp+ecx+var_1C+10h], dl
.text:00F91F2A mov edx, [ebp+var_20]
.text:00F91F2D mov eax, [ebp+var_20]
.text:00F91F30 mov dl, [ebp+edx+var_1C+10h]
.text:00F91F34 mov cl, [ebp+eax+var_1C+4]
.text:00F91F38 shr dl, cl
.text:00F91F3A mov eax, [ebp+var_20]
.text:00F91F3D mov [ebp+eax+var_1C+10h], dl
.text:00F91F41 mov ecx, [ebp+var_20]
.text:00F91F44 movzx edx, [ebp+ecx+var_1C+8]
.text:00F91F49 mov eax, 8
.text:00F91F4E sub eax, edx
.text:00F91F50 mov [ebp+var_22], al
.text:00F91F53 mov ecx, [ebp+var_20]
.text:00F91F56 movsx edx, [ebp+ecx+var_1C]
.text:00F91F5B movzx ecx, [ebp+var_22]
.text:00F91F5F sar edx, cl
.text:00F91F61 mov [ebp+var_24], dl
.text:00F91F64 mov eax, [ebp+var_20]
.text:00F91F67 movzx ecx, [ebp+eax+var_1C+0Ch]
.text:00F91F6C mov edx, 8
.text:00F91F71 sub edx, ecx
.text:00F91F73 mov [ebp+var_23], dl
.text:00F91F76 mov eax, [ebp+var_20]
.text:00F91F79 movsx edx, [ebp+eax+var_1C]
.text:00F91F7E movzx ecx, [ebp+var_23]
.text:00F91F82 shl edx, cl
.text:00F91F84 mov [ebp+var_21], dl
.text:00F91F87 mov eax, [ebp+var_20]
.text:00F91F8A mov dl, [ebp+var_21]
.text:00F91F8D mov cl, [ebp+eax+var_1C+4]
.text:00F91F91 shr dl, cl
.text:00F91F93 mov [ebp+var_21], dl
.text:00F91F96 movzx eax, [ebp+var_24]
.text:00F91F9A mov ecx, [ebp+var_20]
.text:00F91F9D movzx ecx, [ebp+ecx+var_1C+8]
.text:00F91FA2 shl eax, cl
.text:00F91FA4 movzx edx, [ebp+var_21]
.text:00F91FA8 or eax, edx
.text:00F91FAA mov ecx, [ebp+var_20]
.text:00F91FAD mov [ebp+ecx+var_1C+14h], al
.text:00F91FB1 jmp loc_F91EE5
然后我们看这部分汇编, ebp+var_20 是一个循环变量,我们要循环四次,对应每段flag的四位
我们把刚刚两个数组分别称为 high[] 和 low[]
手动反汇编
定义 :
ebp+var_20 : i
ebp+var_1C : input
ebp+var_1C + 8 : high
ebp+var_1C + 0xC : low
ebp+var_1C + 4 : tmp
ebp+var_1C + 0x10 : dst
ebp+var_1C + 0x14 : target
for(int i = 0; i < 4; i ++ ) {
tmp[i] = h[i] + l[i]
dst[i] = (input[i] << h[i]) & 0xFF
dst[i] >>= tmp[i] // SHR
int tmp1 = 8 - h[i] // ebp + var_22
int tmp2 = input[i] >> tmp3 // ebp + var_24 // SAR
int tmp3 = 8 - l[i] // ebp + var_23
int tmp4 = (input[i] << tmp3) & 0xFF// ebp + var_21
tmp4 >>= tmp[i]
target[i] = (tmp2 << h[i]) | tmp4
}
于是设计一个结构体存每一段的信息, 因为发现一会还有一个函数要用...
this_24调用的另一个函数是this_36, 地址在F928A0
现在是北京时间1:56, 大半夜的多少有点整不动了, 起床再来
接着跟this_36
把this[26]放成由一个函数拿到的地址上的值,盲猜都能猜到是刚刚生成的东西, 跳转验证一下
正是
把指针类型改正确后, 加密逻辑清晰了一些, 其中V15 应该是最后存放数据的地址, v14 初始值为 32, F92DC0 应该是程序获取地址的一个函数, 暂不用去理, 这段程序是这样的
int base = 32;
int dst0 = 0;
uint8_t dst1[4];
for(int i = 0; i < 8; i ++ ) {
if(i >= 4) {
base -= flag_sec[i]; // <=> low_plus_high[i-4]
dst0 |= data_enc[i] << base; // <=> data_enc2[i-4]
}
else {
base -= 8 - hpl[i];
dst0 |= data_enc[i] << base;
dst1[i] = low[i] | (high[i] << 4)
}
}
有了加密逻辑, 有了目标值, 直接进行一个爆破, 不过爆破的范围好像比较大, 我们用 C 爆一波
#include <cstdio>
#include <cstring>
#include <cstdint>
uint8_t tar1[20] = {
0x08, 0xEA, 0x58, 0xDE,
0x94, 0xD0, 0x3B, 0xBE,
0x88, 0xD4, 0x32, 0xB6,
0x14, 0x82, 0xB7, 0xAF,
0x14, 0x54, 0x7F, 0xCF
};
uint8_t tar2[20] = {
0x20, 0x20, 0x30, 0x33,
0x22, 0x33, 0x20, 0x20,
0x20, 0x30, 0x20, 0x32,
0x30, 0x33, 0x22, 0x20,
0x20, 0x20, 0x24, 0x20
};
struct data {
uint8_t sec[4];
uint8_t sum[4];
uint8_t hig[4];
uint8_t low[4];
uint8_t enc[4];
uint8_t enc1[4];
};
uint8_t hig(uint8_t x) {
uint8_t res = 0;
while((x & 0x80) == 0) {
x <<= 1;
res ++;
}
return ++ res;
}
uint8_t low(uint8_t x) {
uint8_t res = 0;
while((x & 1) == 0) {
x >>= 1;
res ++;
}
return res;
}
void findAns(int tar0, uint8_t* tar1) {
int x[4];
for(x[0] = 30; x[0] < 128; x[0] ++ )
for(x[1] = 30; x[1] < 128; x[1] ++ )
for(x[2] = 30; x[2] < 128; x[2] ++ )
for(x[3] = 30; x[3] < 128; x[3] ++ ) {
// x[0] = '0', x[1] = '1', x[2] = '2', x[3] = '3';
data tmp;
for(int i = 0; i < 4; i ++ ) {
tmp.sec[i] = x[i];
tmp.hig[i] = hig(x[i]);
tmp.low[i] = low(x[i]);
tmp.sum[i] = tmp.hig[i] + tmp.low[i];
tmp.enc[i] = ((x[i] << tmp.hig[i]) & 0xFF) >> tmp.sum[i];
tmp.enc1[i] = ((x[i] >> (8 - tmp.hig[i])) << tmp.hig[i]) | (((x[i] << (8 - tmp.low[i])) & 0xFF) >> tmp.sum[i]);
}
// for(int i = 0; i < 4; i ++ ) printf("%d ", tmp.sum[i]);
// puts("");
// for(int i = 0; i < 4; i ++ ) printf("%d ", tmp.hig[i]);
// puts("");
// for(int i = 0; i < 4; i ++ ) printf("%d ", tmp.low[i]);
// puts("");
// for(int i = 0; i < 4; i ++ ) printf("%d ", tmp.enc[i]);
// puts("");
// for(int i = 0; i < 4; i ++ ) printf("%d ", tmp.enc1[i]);
// puts("");
int base = 32;
int dst0 = 0;
uint8_t dst1[4] = {0, 0, 0, 0};
for(int i = 0; i < 8; i ++ ) {
if(i < 4) {
base -= 8 - tmp.sum[i];
dst0 |= tmp.enc[i] << base;
dst1[i] = tmp.low[i] | (tmp.hig[i] << 4);
}
else {
base -= tmp.sec[i];
dst0 |= tmp.enc[i] << base;
}
}
// printf("%x ", dst0);
// for(int i = 0; i < 4; i ++ ) printf("%d ", dst1[i]);
if(dst0 == tar0 && !memcmp(dst1, tar1, 4)) {
printf("%c%c%c%c-", x[0], x[1], x[2], x[3]);
return;
}
}
}
int main() {
int *p = (int*) tar1;
uint8_t *pp = tar2;
for(int i = 0; i < 5; i ++ ) {
findAns(*p, pp);
p ++;
pp += 4;
}
}
swpuctf{we18-l8co-m1e4-58to-swpu}
[FBCTF2019] go_get_the_flag
golang 逆向, 检查字符串之后推测是通过参数直接给出flag, 然后在main.checkpassword函数里check一下
第一个参数是输入的地址,
结合动调,第二个参数猜测是字符串长度.
把输入和s0_M4NY_Func710n2!做memequal, 猜测是判等, 直接运行之, 给出了一串输出
fb{.60pcln74b_15_4w350m3}
后面 is awesome 清晰可见,但是前面应该还有些不对, 不管如何,先交一发试试
对了...
[RCTF2019] DontEatMe
输入, 然后有一个固定种子的rand, 动调获取数据
前面奇奇怪怪的东西会让动调挂掉, 直接全扬了防止反调
然后程序又主动给这部分rand过的东西赋值了, 看来rand的没啥用, 障眼法
函数4E1090用了这部分内容给一个长度17的32位栈变量赋值, 输入无关, 直接拿出来
从4E501A到4E503A,又做了一些输入无关的事情
然后是4E5018到5038, 注意到覆盖了前一段, 翻到下面发现一个用wasd的迷宫,迷宫内容在4E53A8, 可以动调拿, 输出成地图
int main() {
freopen("maze.txt", "w", stdout);
int *p = (int*) ida_chars;
for(int i = 0; i < 256; i ++ ) {
if(i % 16 == 0) puts("");
printf("%2d", *p ++);
}
}
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1
1 0 1 1 1 1 1 1 1 0 1 1 1 1 1 1
1 0 1 1 1 1 1 1 1 0 1 1 1 1 1 1
1 0 1 1 1 1 0 0 t 0 0 0 0 1 1 1
1 0 1 1 1 1 0 1 1 1 1 1 0 1 1 1
1 0 1 1 1 1 0 1 1 1 1 1 0 1 1 1
1 0 1 1 1 1 0 0 0 0 1 1 0 1 1 1
1 0 1 1 1 1 1 1 1 0 1 1 0 1 1 1
1 0 1 1 1 1 1 1 1 0 1 1 0 1 1 1
1 0 0 0 s 0 0 0 0 0 1 1 0 1 1 1
1 1 1 1 1 0 1 1 1 1 1 1 0 1 1 1
1 1 1 1 1 0 0 0 0 0 0 0 0 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
初始位置(10, 5), 目标位置 (4, 9), 要求是17 步内走到, dddddwwwaaawwwdd,
步骤来自于v23, v23来自于v51 (实际IDA版本和环境不同可能不同). v51是一个calloc的, 长度是输出长度扩展为8的整数倍的地址, 继续追溯可以追溯到这里:
v7是个指针 4E11EB是个sscanf, 大概就是把输入转成十六进制,再加密最后的结果要是迷宫步, 这个加密多少有点大, 感觉是现成的加密. 回到刚刚忽略的函数里面想找到点蛛丝马迹和魔数之类的, 点进去一看,好家伙,不用找了, 没去符号, 知道是BLOWFISH加密了. 秘钥明文给出的
,