吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 3336|回复: 8
收起左侧

[CTF] BUUCTF 部分中等难度题目总结(二)

[复制链接]
Y1rannn 发表于 2022-1-25 18:28

[XNUCA2018] StrangeInterpreter

本题做题过程比较奇怪,没有什么好参考的

唉, OLLVM

尝试用插件去一下

十分钟过去了, 它还没有完事

我打算先开一局CSGO

屏幕截图 2022-01-16 220058.png

大概有几百行这种东西吧..., 又是一道vm题

看上去是读入一个长32的OPcode,然后开始搞事, 最后这要正好变成0-5a-z

好长啊..看看猜猜先.

根据它是一道题, 它要有解, 所以投机取巧的判断要么这玩意有规律可找,要么就得自动化操作, 肯定不可能是人眼硬看, 我们先输入个0-5a-z看看这玩意会变成啥

?我直接疑惑了,这是对称的直接把flag给我了吗?
屏幕截图 2022-01-16 221157.png

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,

屏幕截图 2022-01-17 031051.png

我们发现了这样的一个地方, 其中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

屏幕截图 2022-01-17 034410.png

可以发现这里输入是动过一次的,先把输入丢到118里面,然后输入取5次方, 模323结果扔116里大概是

看看116做了什么, 如果看不懂就爆破一波0xff内5次方%323等于刚刚结果的

等一下,我们不妨先试一下'Q', 81 ** 5 %323 = 47, 还是不对, 还要继续看, 发现这里:

屏幕截图 2022-01-17 034755.png

这个地方和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写入

屏幕截图 2022-01-17 043606.png

子进程这里正好是长度为6的一个nop+一个int3, 我们直接把v16先Patch上

屏幕截图 2022-01-17 043645.png

于是有了这里:

屏幕截图 2022-01-17 044023.png

我们知道还有一段长度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])

屏幕截图 2022-01-17 045904.png

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骗了,以为函数结束了

屏幕截图 2022-01-17 050829.png

整个加密过程不难,直接逆一下即可:

先找异或过的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
}

于是设计一个结构体存每一段的信息, 因为发现一会还有一个函数要用...
屏幕截图 2022-01-20 015511.png

this_24调用的另一个函数是this_36, 地址在F928A0

现在是北京时间1:56, 大半夜的多少有点整不动了, 起床再来


接着跟this_36

把this[26]放成由一个函数拿到的地址上的值,盲猜都能猜到是刚刚生成的东西, 跳转验证一下

屏幕截图 2022-01-20 100816.png

屏幕截图 2022-01-20 100907.png

正是
屏幕截图 2022-01-20 101105.png

把指针类型改正确后, 加密逻辑清晰了一些, 其中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一下

第一个参数是输入的地址,

屏幕截图 2022-01-20 152346.png

结合动调,第二个参数猜测是字符串长度.

把输入和s0_M4NY_Func710n2!做memequal, 猜测是判等, 直接运行之, 给出了一串输出

fb{.60pcln74b_15_4w350m3}

后面 is awesome 清晰可见,但是前面应该还有些不对, 不管如何,先交一发试试

对了...

[RCTF2019] DontEatMe

输入, 然后有一个固定种子的rand, 动调获取数据

前面奇奇怪怪的东西会让动调挂掉, 直接全扬了防止反调

然后程序又主动给这部分rand过的东西赋值了, 看来rand的没啥用, 障眼法

函数4E1090用了这部分内容给一个长度17的32位栈变量赋值, 输入无关, 直接拿出来

屏幕截图 2022-01-21 193832.png

从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的整数倍的地址, 继续追溯可以追溯到这里:

屏幕截图 2022-01-21 200340.png

v7是个指针 4E11EB是个sscanf, 大概就是把输入转成十六进制,再加密最后的结果要是迷宫步, 这个加密多少有点大, 感觉是现成的加密. 回到刚刚忽略的函数里面想找到点蛛丝马迹和魔数之类的, 点进去一看,好家伙,不用找了, 没去符号, 知道是BLOWFISH加密了. 秘钥明文给出的
屏幕截图 2022-01-21 201259.png
,
屏幕截图 2022-01-21 200907.png

免费评分

参与人数 7威望 +1 吾爱币 +27 热心值 +6 收起 理由
lyfff + 1 + 1 我很赞同!
sunlei658 + 1 + 1 谢谢@Thanks!
Hmily + 1 + 20 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
Anonymous、 + 2 + 1 我很赞同!
wildbloom + 1 + 1 热心回复!
itmacbook + 1 + 1 谢谢@Thanks!
A3uRa + 1 我很赞同!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

kangok 发表于 2022-1-25 22:09
跟随学习,一起进步
Avanana7mi 发表于 2022-1-26 00:11
头像被屏蔽
wxf2589 发表于 2022-1-26 05:45
Allen666 发表于 2022-1-26 08:54
好用,感谢大佬
tkcheems 发表于 2022-1-26 15:06
小白海淘教学中。。。
cyj888 发表于 2022-1-26 16:17


跟随学习,一起进步
yudream 发表于 2022-1-27 09:34
思路很清晰,学习到了
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-12-25 13:44

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表