Y1rannn 发表于 2022-1-25 18:28

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

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

唉, OLLVM

尝试用插件去一下

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

我打算先开一局CSGO

![屏幕截图 2022-01-16 220058.png](https://tianyu.xin/usr/uploads/2022/01/3818721468.png)


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

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

好长啊..看看猜猜先.

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

?我直接疑惑了,这是对称的直接把flag给我了吗?
![屏幕截图 2022-01-16 221157.png](https://tianyu.xin/usr/uploads/2022/01/3504363589.png)


> X-NUCA{5e775e5e7}

这给了一部分吧算是, 和同系列的Interpreter的flag蛮像的, 我再把这个输进去试试. 感觉可能是flag分段加密了, 然后第一段恰好是个异或之类的

额,然后后半段flag就出来了, {5e775e5e775e5e775e5e775e}

咱就是说多少有点疑惑了...

交一发试试.. 果然错了.. 看来是我太天真了

额, 回头看了一发发现是多写了一个5e7, 这.. 竟然过了

可是这题我还.. 完全不知道它干了啥

## vmlog
熟悉brainfuck的话这题不难,基本上就是一个brainfuck解释器

先贴exp再解释

```python
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 + j) * base % p == seq :
            flag += chr(j)

print(flag)
```

我是从头直接翻译brainfuck到一个类c的东西, 翻译一半就看懂它在干啥了

```cpp
reg ++, mem = reg;
reg ++, p ++ , mem = reg;
p ++, reg += 60;


mem = 1, mem = 2;
reg = 60, p = 2;

for(int i = 1; i <= 60; i ++ ) {
    mem *= mem;
}
p -= 2;
reg = mem;
reg --;
mem =reg;

p ++;
for(int i = 1; i <= 6; i ++ ) {
    reg *= mem;
    reg --;
    mem = reg;
}
p ++;
reg = mem;
reg *= mem;
reg += 5;
mem = reg;

reg *= mem;
reg -= 5;
mem = reg;
reg *= mem ** 4;
p += 2;
reg = mem;
reg ++;
mem = reg;
do {
    print(mem);
    reg = mem;
    reg --;
    mem = reg;
    p -= 2;
    reg = mem;
    p ++;
    // p == 3
    reg += input();
    do {
      reg *= mem;
      mem =reg;
      reg = mem;
      reg = mem % reg;
      // reg = (reg + input) * mem % mem
      mem = reg;
      reg = mem;
      mem = reg;
      reg = mem;
      mem = reg;
      reg = mem
    }while(reg)
    p ++;
    reg = mem;
}while(reg)
```

一般来讲, brainfuck每个位置的指针都是比较固定的, 不会出现循环中相同语句指针不同的情况.

所以所有的mem的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, r ++, mem = r这种,就可以写成 r = ++mem, 而且经验来讲,这种r一般下一步都不会用的,都是为了算mem临时放一下.

## pwnhub2022某内部赛 easyre

binary :
(https://tianyu.xin/usr/uploads/2022/01/3718297498.zip)

确实比较easy

main里面有一些奇怪的东西, 和线程有关的,但是还没什么卵用.

关键函数在1400014A0, 一开始没看出来, 后来发现就是一个循环展开的 XTEA

检查结果的交叉引用,在13F0处比对,和明文比对.

接下来是找XTEA的key, 发现是运行时四个rand生成的, 猜测是假随机, 因为没找到srand().

把Thrd包括scanf啥的都扬了, 直接动调获得key, 这里不知道为啥scanf也会throw一个error, 但是也没打算仔细探究, 扬了就行

解题脚本

```cpp
#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 = {
    0x29, 0x4823, 0x18BE, 0x6784
};
void decipher(uint32_t v) {
    unsigned int i;
    uint32_t v0=v, v1=v, 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);
    }
    v=v0; v=v1;
    char* p = (char*)v;
    for(int i = 0; i < 8; i ++ ) printf("%c", p);
}
int main() {
    unsigned int * p = (uint32_t *)&ida_chars;
    decipher(p);
    decipher(p+2);
    decipher(p+4);
    decipher(p+6);
}
```

## ollvm

我承认我没看出来这是 RSA,不过能爆破谁还 RSA

巨恶心无比的C++逆向,不过虽说是C++逆向,不用考虑动态构建和虚函数

主要是C++的GNU大整数库,这东西想要看明白要费死劲了, 我选择直接从判断逻辑和输入流向入手,看看分别能走到哪儿.

输入从参数输入,要求是38位, 主函数直接反Ollvm,

![屏幕截图 2022-01-17 031051.png](https://tianyu.xin/usr/uploads/2022/01/2050540261.png)


我们发现了这样的一个地方, 其中mpow原名encrypt, 看上去就很像关键函数, 对吧!

这个函数做的事就是把pow(2, 3, 4)放在1里面, 具体猜测应该是2, 3, 4地址上指向的大整数的值. 然后要和第i个BigInt相等. 每个BigInt都是明文写在函数里的

到这里我认为核心应该就是两件事:一件事是动调找到方次和模数, 一件事是找到底数和输入的关系. 根据大整数的数目, 我猜底数应该就只是0-9而已,因为大概有30多个大整数, 每次Load的次序是递增的, 之所以是BigInt2 + i, 我认为是第一个大整数和后面的的载入方式不同, 肯定是有所区别, 这里也变相印证了我的猜测. 猜测第一个BigInt应该是次方或者模数之类的, 并且还有一个比较小的Int. 合理猜测大的是模数小的是次方, 我们先来试试

```python
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内试一下

```python
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之内, 都能找到对应的解, 前六个分别是, 我觉得这个方向肯定还是能用上, 不过还是要看看输入流向,我们回头分析一下输入

```python
0x55
0x14f
0x152
0xeb
0x19a
0x200
0x211
```

![屏幕截图 2022-01-17 034410.png](https://tianyu.xin/usr/uploads/2022/01/161572599.png)


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

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

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

![屏幕截图 2022-01-17 034755.png](https://tianyu.xin/usr/uploads/2022/01/1296976892.png)


这个地方和encpara2也就是底数有关, 可以看到这是把116和116加和了, 那我们再用QCTF这四个猜一波, 第二位 335 和刚刚第二位结果恰好相同, 看下一位 !! 338 也恰好符合, 至此整个程序流程基本分析清楚了,我们再理一遍写脚本

读入38位可见字符, 对于每一位的ord取5次方再模323放进v116, 定义v116[-1] = 38, 然后v117 = v116 + v116

之后用固定的指数和模数对v117每一位取次方, 和明文比较.

exp太大了,和文件一起打包上传了
(https://tianyu.xin/usr/uploads/2022/01/2124476020.zip)

QCTF{5Ym4aOEww2NcZcvUPOWKYMnPaqPywR2m}
## EasiestRE
关键字符串+交叉引用,可以看得出这是一个自己调试自己的多线程程序, 父进程预置了一些Code, 然后在子进程终端的时候通信,在ExceptionAddress写入

![屏幕截图 2022-01-17 043606.png](https://tianyu.xin/usr/uploads/2022/01/3497203416.png)


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

![屏幕截图 2022-01-17 043645.png](https://tianyu.xin/usr/uploads/2022/01/412791392.png)


于是有了这里:

![屏幕截图 2022-01-17 044023.png](https://tianyu.xin/usr/uploads/2022/01/3841027032.png)


我们知道还有一段长度0x1E的要写,跟进40247D, 40460B, 4087E0, 又是一个debugbreak, 一长段的nop恰好可以填进去

写个脚本Patch进去

```python
v16 =
base_addr = 0x408AF8
for i in range(7) :
    patch_byte(base_addr+i, v16)
arr =
base_addr = 0x408824
for i in range(30) :
    patch_byte(base_addr+i, arr)
```

![屏幕截图 2022-01-17 045904.png](https://tianyu.xin/usr/uploads/2022/01/3010764384.png)


src是原来的函数处理好传进来的, dst是目标,这几个函数传的都是指针,但是IDA自动分析的都不太对,建议改了好看一点

这部分变换的逻辑是,共变换len轮,第一轮str和0x1234异或,之后v9 = (v4&Str) ? 1 : 0 , target += pre*v9, 后面每轮str和target异或.最后在40384B验证target

check函数很诡异,最后是看了别的师傅的wp才知道要去汇编里找, 被IDA骗了,以为函数结束了

![屏幕截图 2022-01-17 050829.png](https://tianyu.xin/usr/uploads/2022/01/3111033656.png)

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

先找异或过的flag

对于flag, 和它直接产生关系的是target 和 target , 根据target的情况就能倒推flag的每一位, 这是根据pre来的, 对于每个target, 他可以看做一个pre[]的子集和, pre可以通过预处理给出,子集可以通过2^8枚举给出, 这个时间复杂度可以接受, 在处理过flag的Xor后值后,对于flag, xor 0x1234, 对于flag, xor target即可

```python
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 = * 8
for i in range(8) :
    pre = 41 * gPre % 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
            j >>= 1
      if temp == target :
            print(ans)
            flag.append(ans)
            break
for i in range(24) :
    print(chr(flag ^ target & 0xff), end = "")
```

## Easyre


C++ 逆向, 关键函数是个虚函数,要动调看看

main里有个很明显的反调,扬了就行

this+12是这样的

```c
int __thiscall this_12(const char **this)
{
int v2; //
unsigned int v3; //
int i; //
char v6; // BYREF
char v7; // BYREF
char v8; // BYREF
int v9; //

v3 = (unsigned int)&this)];
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 ^= 0x10u;
sub_F926C0(v7, 0x14u);
sub_F92A70(v8, 1);
LOBYTE(v9) = 1;
v2 = (unsigned __int8)sub_F94260(this, v3, v6, v7, 0);
LOBYTE(v9) = 0;
sub_F92A50(v7);
v9 = -1;
sub_F926A0();
return v2;
}
```

其中this是输入, 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 == this

动调拿Target

```c
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

我们查看这部分汇编代码

```asm
.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在第几位**

```asm
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

保存的地址分别为 , 同时再 esi ++

```pt
Stack:0073FBE4 db    3
Stack:0073FBE5 db    3
Stack:0073FBE6 db    3
Stack:0073FBE7 db    3
Stack:0073FBE8 db    4
Stack:0073FBE9 db    0
Stack:0073FBEA db    1
Stack:0073FBEB db    0
```

我输入的前四位是 `0123`, 分别是 `0011 0000 / 0011 0001 / 0011 0010 / 0011 0011`

对应的最高位1和最低位1分别在3 3 3 3 和 4 0 1 0, 这部分代码逻辑是显然的

```asm
.text:00F91EE5 loc_F91EE5:                           ; CODE XREF: sub_F91E40+171↓j
.text:00F91EE5 mov   eax,
.text:00F91EE8 add   eax, 1
.text:00F91EEB mov   , eax
.text:00F91EEE
.text:00F91EEE loc_F91EEE:                           ; CODE XREF: sub_F91E40+A3↑j
.text:00F91EEE cmp   , 4
.text:00F91EF2 jge   loc_F91FB6
.text:00F91EF8 mov   ecx,
.text:00F91EFB movzx   edx,
.text:00F91F00 mov   eax,
.text:00F91F03 movzx   ecx,
.text:00F91F08 add   edx, ecx
.text:00F91F0A mov   eax,
.text:00F91F0D mov   , dl
.text:00F91F11 mov   ecx,
.text:00F91F14 movsx   edx,
.text:00F91F19 mov   eax,
.text:00F91F1C movzx   ecx,
.text:00F91F21 shl   edx, cl
.text:00F91F23 mov   ecx,
.text:00F91F26 mov   , dl
.text:00F91F2A mov   edx,
.text:00F91F2D mov   eax,
.text:00F91F30 mov   dl,
.text:00F91F34 mov   cl,
.text:00F91F38 shr   dl, cl
.text:00F91F3A mov   eax,
.text:00F91F3D mov   , dl
.text:00F91F41 mov   ecx,
.text:00F91F44 movzx   edx,
.text:00F91F49 mov   eax, 8
.text:00F91F4E sub   eax, edx
.text:00F91F50 mov   , al
.text:00F91F53 mov   ecx,
.text:00F91F56 movsx   edx,
.text:00F91F5B movzx   ecx,
.text:00F91F5F sar   edx, cl
.text:00F91F61 mov   , dl
.text:00F91F64 mov   eax,
.text:00F91F67 movzx   ecx,
.text:00F91F6C mov   edx, 8
.text:00F91F71 sub   edx, ecx
.text:00F91F73 mov   , dl
.text:00F91F76 mov   eax,
.text:00F91F79 movsx   edx,
.text:00F91F7E movzx   ecx,
.text:00F91F82 shl   edx, cl
.text:00F91F84 mov   , dl
.text:00F91F87 mov   eax,
.text:00F91F8A mov   dl,
.text:00F91F8D mov   cl,
.text:00F91F91 shr   dl, cl
.text:00F91F93 mov   , dl
.text:00F91F96 movzx   eax,
.text:00F91F9A mov   ecx,
.text:00F91F9D movzx   ecx,
.text:00F91FA2 shl   eax, cl
.text:00F91FA4 movzx   edx,
.text:00F91FA8 or      eax, edx
.text:00F91FAA mov   ecx,
.text:00F91FAD mov   , al
.text:00F91FB1 jmp   loc_F91EE5
```

然后我们看这部分汇编, ebp+var_20 是一个循环变量,我们要循环四次,对应每段flag的四位

我们把刚刚两个数组分别称为 high[] 和 low[]

手动反汇编

```c
定义 :
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 = h + l
    dst = (input << h) & 0xFF
    dst >>= tmp // SHR
    int tmp1 = 8 - h // ebp + var_22
    int tmp2 = input >> tmp3 // ebp + var_24 // SAR
    int tmp3 = 8 - l // ebp + var_23
    int tmp4 = (input << tmp3) & 0xFF// ebp + var_21
    tmp4 >>= tmp
    target = (tmp2 << h) | tmp4
}
```

于是设计一个结构体存每一段的信息, 因为发现一会还有一个函数要用...
![屏幕截图 2022-01-20 015511.png](https://tianyu.xin/usr/uploads/2022/01/2889855251.png)


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

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


----

接着跟this_36

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

![屏幕截图 2022-01-20 100816.png](https://tianyu.xin/usr/uploads/2022/01/794173212.png)


![屏幕截图 2022-01-20 100907.png](https://tianyu.xin/usr/uploads/2022/01/487473139.png)


正是
![屏幕截图 2022-01-20 101105.png](https://tianyu.xin/usr/uploads/2022/01/789796.png)


把指针类型改正确后, 加密逻辑清晰了一些, 其中V15 应该是最后存放数据的地址, v14 初始值为 32, F92DC0 应该是程序获取地址的一个函数, 暂不用去理, 这段程序是这样的

```c
int base = 32;
int dst0 = 0;
uint8_t dst1;
for(int i = 0; i < 8; i ++ ) {
    if(i >= 4) {
      base -= flag_sec; // <=> low_plus_high
      dst0 |= data_enc << base; // <=> data_enc2
    }
    else {
      base -= 8 - hpl;
      dst0 |= data_enc << base;
      dst1 = low | (high << 4)
    }
}
```

有了加密逻辑, 有了目标值, 直接进行一个爆破, 不过爆破的范围好像比较大, 我们用 C 爆一波

```cpp
#include <cstdio>
#include <cstring>
#include <cstdint>
uint8_t tar1 = {
    0x08, 0xEA, 0x58, 0xDE,
    0x94, 0xD0, 0x3B, 0xBE,
    0x88, 0xD4, 0x32, 0xB6,
    0x14, 0x82, 0xB7, 0xAF,
    0x14, 0x54, 0x7F, 0xCF
};
uint8_t tar2 = {
    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;
    uint8_t sum;
    uint8_t hig;
    uint8_t low;
    uint8_t enc;
    uint8_t enc1;
};
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;
    for(x = 30; x < 128; x ++ )
    for(x = 30; x < 128; x ++ )
    for(x = 30; x < 128; x ++ )
    for(x = 30; x < 128; x ++ ) {
      // x = '0', x = '1', x = '2', x = '3';
      data tmp;
      for(int i = 0; i < 4; i ++ ) {
            tmp.sec = x;
            tmp.hig = hig(x);
            tmp.low = low(x);
            tmp.sum = tmp.hig + tmp.low;
            tmp.enc = ((x << tmp.hig) & 0xFF) >> tmp.sum;
            tmp.enc1 = ((x >> (8 - tmp.hig)) << tmp.hig) | (((x << (8 - tmp.low)) & 0xFF) >> tmp.sum);
      }
      
      // for(int i = 0; i < 4; i ++ ) printf("%d ", tmp.sum);
      // puts("");
      // for(int i = 0; i < 4; i ++ ) printf("%d ", tmp.hig);
      // puts("");
      // for(int i = 0; i < 4; i ++ ) printf("%d ", tmp.low);
      // puts("");
      // for(int i = 0; i < 4; i ++ ) printf("%d ", tmp.enc);
      // puts("");
      // for(int i = 0; i < 4; i ++ ) printf("%d ", tmp.enc1);
      // puts("");
      
      int base = 32;
      int dst0 = 0;
      uint8_t dst1 = {0, 0, 0, 0};
      for(int i = 0; i < 8; i ++ ) {
            if(i < 4) {
                base -= 8 - tmp.sum;
                dst0 |= tmp.enc << base;
                dst1 = tmp.low | (tmp.hig << 4);
            }
            else {
                base -= tmp.sec;
                dst0 |= tmp.enc << base;
            }
      }
      // printf("%x ", dst0);
      // for(int i = 0; i < 4; i ++ ) printf("%d ", dst1);
      if(dst0 == tar0 && !memcmp(dst1, tar1, 4)) {
            printf("%c%c%c%c-", x, x, x, x);
            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}

## go_get_the_flag

golang 逆向, 检查字符串之后推测是通过参数直接给出flag, 然后在main.checkpassword函数里check一下

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

![屏幕截图 2022-01-20 152346.png](https://tianyu.xin/usr/uploads/2022/01/1492356164.png)


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

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

```pt
fb{.60pcln74b_15_4w350m3}
```

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

对了...

## DontEatMe

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

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

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

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



![屏幕截图 2022-01-21 193832.png](https://tianyu.xin/usr/uploads/2022/01/2886189656.png)


从4E501A到4E503A,又做了一些输入无关的事情

然后是4E5018到5038, 注意到覆盖了前一段, 翻到下面发现一个用wasd的迷宫,迷宫内容在4E53A8, 可以动调拿, 输出成地图

```cpp
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](https://tianyu.xin/usr/uploads/2022/01/2853921235.png)


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

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

思路很清晰,学习到了
页: [1]
查看完整版本: BUUCTF 部分中等难度题目总结(二)