REVERSE-PRACTICE-BUUCTF-20

[SCTF2019]creakme

exe程序,运行后提示输入ticket,无壳,用ida分析
交叉引用字符串“please input your ticket:”来到sub_402540函数
creakme-logic
分析sub_402320函数,在DebugBreak和return之间有一段代码引用
creakme-sub_402320
修改EIP从地址0x402412处开始执行,调试可知sub_402450函数是对.SCTF段数据的SMC
creakme-402412
回到sub_402540函数,往下走
分析sub_4024A0函数,过掉两个反调试,执行SMC好的.SCTF段的代码,发现是将静态可见的字符串">pvfqYc,4tTc2UxRmlJ,sB{Fh4Ck2:CFOb4ErhtIcoLo"变成了动态可见的字符串"nKnbHsgqD3aNEB91jB3gEzAr+IklQwT1bSs3+bXpeuo="
creakme-sub_4024A0
回到sub_402540函数,往下走
获取输入input,拷贝input到Dst,sub_4020D0函数对Dst进行AES的CBC模式加密,密文存储在v2,令v4=v2,那段动态可见的字符串赋给v6,最后就是比较v4和v6
sub_4020D0->sub_401690
在sub_401690函数中发现两个字符串"sctfsctfsctfsctf"和"sycloversyclover",由于AES的CBC模式加密需要一个偏移量iv与第0块明文异或,于是可以确定iv=“sctfsctfsctfsctf”,key=“sycloversyclover”,参考:AES五种加密模式(CBC、ECB、CTR、OCF、CFB)
creakme-sub_401690
creakme-sub_401690
已知key,iv,cipher,写解AES.CBC脚本即可得到flag

from Crypto.Cipher import AES
import base64
key="sycloversyclover"
iv="sctfsctfsctfsctf"
cipher=base64.b64decode("nKnbHsgqD3aNEB91jB3gEzAr+IklQwT1bSs3+bXpeuo=")
aes=AES.new(key,AES.MODE_CBC,iv)
print(aes.decrypt(cipher))
#sctf{Ae3_C8c_I28_pKcs79ad4}

[网鼎杯 2020 青龙组]bang

apk文件,jadx-gui打开什么都没有,SecShell提示是加了壳
果然,这个apk加了梆梆的壳
bang-shell
使用frida脚本脱壳
bang-frida
脱壳后的dex文件再用jadx-gui分析,在com.example.how_debug.MainActivity类中找到flag
bang-flag

[WUSTCTF2020]funnyre

elf文件,运行后没有输入,无壳,ida分析
main函数没有被ida识别成函数,是因为有花指令
jz和jnz指令都会跳转到同一条(下一条)指令,把jz和jnz都nop掉
call后面的地址不存在,是因为在原本正确的指令字节基础上加了多余的字节,把call那条指令按d转成数据后,顺序地一个一个nop掉多余的字节,直到ida能够正确识别出指令
jz指令跳过了从地址0x400621开始的两个字节,也是直接nop掉jz
funnyre-fakeorder
总共有4处类似这样的花指令,去除完全后,选中main函数的全部红色代码,按p创建函数,F5反汇编
main函数中,验证输入的长度是否为38,且有flag{}包住,对花括号内的32个字符做300多次运算,最后与已知的unk_4025C0比较,验证输入
funnyre-main
angr参考:angr学习【一】
脚本参考:buuctf刷题记录25 [WUSTCTF2020]funnyre
调用angr框架写脚本即可得到flag

import angr
import claripy

p=angr.Project('./attachment',load_options={"auto_load_libs": False})
f=p.factory
state = f.entry_state(addr=0x400605)#设置state开始运行时的地址
flag = claripy.BVS('flag',8*32)#要求的内容有32个,用BVS转成二进制给flag变量
state.memory.store(0x603055+0x300+5,flag)#因为程序没有输入,所以直接把字符串设置到内存
state.regs.rdx=0x603055+0x300
state.regs.rdi=0x603055+0x300+5#然后设置两个寄存器

sm = p.factory.simulation_manager(state)#准备从state开始遍历路径


print("ready")

sm.explore(find=0x401DAE)#遍历到成功的地址

if sm.found:
    print("sucess")
    x=sm.found[0].solver.eval(flag,cast_to=bytes)
    print(x)
else:
    print('error')
#ready
#sucess
#b'1dc20f6e3d497d15cef47d9a66d6f1af'

Dig the way

exe程序,运行后直接闪退,无壳,ida分析
栈溢出的题目
main函数的主要逻辑为
读取data文件到v7,执行func0,func1和func2函数,返回值分别赋给v9,v10和v11,如果v11为0,则调用get_key函数获得flag。但是对于func2函数,无论传入的参数为何值,其返回值永远为正,v11不可能为0,而对于func1函数,如果传入的参数合适,其返回值可以为0,于是便需要通过func0函数交换v15和v16所存储的函数指针,使得执行func1函数时,其返回值可以赋给v11

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int result; // eax
  int v4; // ebx
  size_t v5; // eax
  int v6; // ebx
  char v7[20]; // [esp+1Ch] [ebp-48h]
  int v8; // [esp+30h] [ebp-34h]
  int v9; // [esp+34h] [ebp-30h]
  int v10; // [esp+38h] [ebp-2Ch]
  int v11; // [esp+3Ch] [ebp-28h]
  int v12; // [esp+40h] [ebp-24h]
  int v13; // [esp+44h] [ebp-20h]
  signed int (__cdecl *v14)(int, int, int); // [esp+48h] [ebp-1Ch]
  int (__cdecl *v15)(int, int, int); // [esp+4Ch] [ebp-18h]
  int (__cdecl *v16)(int, int, int); // [esp+50h] [ebp-14h]
  int v17; // [esp+54h] [ebp-10h]
  int v18; // [esp+58h] [ebp-Ch]
  FILE *v19; // [esp+5Ch] [ebp-8h]

  __main();
  v14 = func0;                                  // 交换值
  v15 = func1;                                  // 含abs的运算,返回值可正可负可零
  v16 = func2;                                  // 含abs的运算,返回值永正
  v8 = 0;
  v9 = 1;
  v10 = 2;
  v11 = 3;
  v12 = 3;
  v13 = 4;
  v19 = fopen("data", "rb");                    // 打开data文件流
  if ( !v19 )
    return -1;
  fseek(v19, 0, 2);                             // 指针指到文件流末尾
  v18 = ftell(v19);                             // 获取文件大小
  fseek(v19, 0, 0);                             // 指针指回文件开始
  v17 = ftell(v19);
  if ( v17 )
  {
    puts("something wrong");
    result = 0;
  }
  else
  {
    for ( i = 0; i < v18; ++i )
    {
      v4 = i;
      v7[v4] = fgetc(v19);                      // data文件的字节读到v7
                                                // 由于v7只是个20字节大小的数组,如果data文件中的字节数大于20,则会把v8~v13的值覆盖掉,即栈溢出
    }
    v5 = strlen(v7);
    if ( v5 <= v18 )
    {
      v18 = v11;                                // v18=v11,且v18在获取flag的函数get_key中用到
      i = 0;
      v17 = v13;
      while ( i <= 2 )
      {
        v6 = i + 1;
        *(&v8 + v6) = (*(&v14 + i))((int)&v8, v12, v13);// 执行func0,func1和func2,返回值分别赋给v9,v10,v11
        v12 = ++i;                              // v12和v13用i赋值
        v13 = i + 1;
      }
      if ( v11 )
      {
        result = -1;
      }
      else                                      // v11为0时,执行get_key获得flag
      {
        get_key(v18, v17);
        system("PAUSE");
        result = 0;
      }
    }
    else
    {
      result = -1;
    }
  }
  return result;
}

由于程序读取data文件是从头到尾全部读取,而v7只有20个字节大小,如果data文件的字节数大于20,多出来的字节就会将v8,v9,v10等等这些变量覆盖
v12和v13被程序赋值为3和4,进入func0函数后并不能达到交换v15和v16的目的,因为此时func0函数中参与运算的变量为v11和v12,而不是v15和v16。于是便利用程序读data文件有可能覆盖v12和v13的漏洞,将v12和v13覆盖为7和8
v7有20个字节,v8~v11是4个int,也就是4x4=16个字节,于是data文件需要从第36个字节开始,将v12和v13覆盖为7和8,data为
digtheway-data
发现exe程序还是运行后闪退,调试发现func1函数的返回值v11还是为正
仔细看调用func0~func2这部分代码,在v12和v13分别为7和8作为参数传入func0函数交换v15和v16后,v15执行func2函数,v16执行func1函数,而在循环中v12和v13由i赋值,当执行v16(func1)函数时,v12和v13的值分别为2和3
digtheway-logic
也就是说,func1函数执行时,实际上是abs(v10+v11)-abs(v11)-abs(v10)+2,v10等于2,要使返回的值为0,则需v11为-1,于是同样利用程序data读文件的漏洞将v11覆盖为-1,data为
digtheway-data
再次运行exe程序,得到flag
digtheway-flag

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

P1umH0

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值