REVERSE-PRACTICE-BUUCTF-19

[RoarCTF2019]polyre

elf文件,无壳,用ida分析
main函数的结构,多重循环,是控制流平坦化,参考:利用符号执行去除控制流平坦化
polyre-main
装好angr,使用脚本deflat.py去除控制流平坦化,脚本取自:deflat
polyre-deflat
polyre-deflat
将去除控制流平坦化的文件attachment_recovered拖入ida中分析
main函数还是不好分析,有很多while和do while循环,实际上是虚假控制流,使用脚本去除虚假控制流,在ida的Script command执行

def patch_nop(start,end):
    for i in range(start,end):
        PatchByte(i, 0x90)

def next_instr(addr):
    return addr+ItemSize(addr)
    
st = 0x0000000000401117
end = 0x0000000000402144

addr = st
while(addr<end):
    next = next_instr(addr)
    if "ds:dword_603054" in GetDisasm(addr):
        while(True):
            addr = next
            next = next_instr(addr)
            if "jnz" in GetDisasm(addr):
                dest = GetOperandValue(addr, 0)
                PatchByte(addr, 0xe9)
                PatchByte(addr+5, 0x90) 
                offset = dest - (addr + 5)
                PatchDword(addr + 1, offset)
                print("patch bcf: 0x%x"%addr)
                addr = next
                break
    else:
        addr = next

去除虚假控制流之后的main函数
主要逻辑为
输入的字符每8个字节为1组,组成1个64位的signed int v4,对v4进行64次循环运算,如果v4为非负,则v4乘以2,相当于左移1位,最高位的0移出,最低位补零,v4必为偶数(如果左移一位后最高位为1,则v4变成了负数值),如果v4为负,则先左移1位,再与0xB0004B7679FA26B3异或,左移一位时,最高位的1被移出了(写逆运算脚本时需要加回来最高位的1),最低位补零,由于3的二进制为0011,异或后v4必为奇数

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  signed __int64 v4; // [rsp+1E0h] [rbp-110h]
  signed int j; // [rsp+1E8h] [rbp-108h]
  signed int i; // [rsp+1ECh] [rbp-104h]
  signed int k; // [rsp+1ECh] [rbp-104h]
  char s1[48]; // [rsp+1F0h] [rbp-100h]
  char input[60]; // [rsp+220h] [rbp-D0h]
  unsigned int v10; // [rsp+25Ch] [rbp-94h]
  char *v11; // [rsp+260h] [rbp-90h]
  int v12; // [rsp+26Ch] [rbp-84h]
  bool v13; // [rsp+272h] [rbp-7Eh]
  unsigned __int8 v14; // [rsp+273h] [rbp-7Dh]
  int v15; // [rsp+274h] [rbp-7Ch]
  char *v16; // [rsp+278h] [rbp-78h]
  int v17; // [rsp+284h] [rbp-6Ch]
  int v18; // [rsp+288h] [rbp-68h]
  bool v19; // [rsp+28Fh] [rbp-61h]
  char *v20; // [rsp+290h] [rbp-60h]
  int v21; // [rsp+298h] [rbp-58h]
  bool v22; // [rsp+29Fh] [rbp-51h]
  __int64 v23; // [rsp+2A0h] [rbp-50h]
  bool v24; // [rsp+2AFh] [rbp-41h]
  __int64 v25; // [rsp+2B0h] [rbp-40h]
  __int64 v26; // [rsp+2B8h] [rbp-38h]
  __int64 v27; // [rsp+2C0h] [rbp-30h]
  __int64 v28; // [rsp+2C8h] [rbp-28h]
  int v29; // [rsp+2D0h] [rbp-20h]
  int v30; // [rsp+2D4h] [rbp-1Ch]
  char *v31; // [rsp+2D8h] [rbp-18h]
  int v32; // [rsp+2E0h] [rbp-10h]
  int v33; // [rsp+2E4h] [rbp-Ch]
  bool v34; // [rsp+2EBh] [rbp-5h]

  v10 = 0;
  memset(input, 0, 0x30uLL);
  memset(s1, 0, 0x30uLL);
  printf("Input:", 0LL);
  v11 = input;
  __isoc99_scanf("%s", input);
  for ( i = 0; ; ++i )
  {
    v12 = i;
    v13 = i < 64;
    if ( i >= 64 )
      break;
    v14 = input[i];
    v15 = v14;
    if ( v14 == '\n' )                          // 换行符'\n'改成字符串结束符'\0'
    {
      v16 = &input[i];
      *v16 = 0;
      break;
    }
    v17 = i + 1;
  }
  for ( j = 0; ; ++j )
  {
    v18 = j;
    v19 = j < 6;
    if ( j >= 6 )                               // 循环6次
      break;
    v20 = input;
    v4 = *(_QWORD *)&input[8 * j];              // 输入的每8个字节一组,组成1个64位的v4,小端序
    for ( k = 0; ; ++k )
    {
      v21 = k;
      v22 = k < 64;
      if ( k >= 64 )                            // 循环64次
        break;
      v23 = v4;
      v24 = v4 < 0;
      if ( v4 >= 0 )                            // 如果v4非负,v4*=2,相当于左移一位,结果必为偶数,如果左移一位后最高位为1,则v4变成了负数
      {
        v27 = v4;
        v28 = 2 * v4;
        v4 *= 2LL;
      }
      else                                      // 如果v4为负,v4乘2后再异或0xB0004B7679FA26B3,相当于先左移一位再异或,结果必为奇数,注意这时最高位的1被移出了
      {
        v25 = 2 * v4;
        v26 = 2 * v4;
        v4 = 2 * v4 ^ 0xB0004B7679FA26B3LL;
      }
      v29 = k;
    }
    v30 = 8 * j;
    v31 = &s1[8 * j];                           // 每组完成64次循环后的值赋给s1
    *(_QWORD *)v31 = v4;
    v32 = j + 1;
  }
  v33 = memcmp(s1, &unk_402170, 48uLL);         // s1和已知值比较,小端序
  v34 = v33 != 0;
  if ( v33 != 0 )
    puts("Wrong!");
  else
    puts("Correct!");
  return v10;
}

写逆运算脚本即可得到flag

#coding:utf-8
arr=[0x96, 0x62, 0x53, 0x43, 0x6D, 0xF2, 0x8F, 0xBC, 0x16, 0xEE,
  0x30, 0x05, 0x78, 0x00, 0x01, 0x52, 0xEC, 0x08, 0x5F, 0x93,
  0xEA, 0xB5, 0xC0, 0x4D, 0x50, 0xF4, 0x53, 0xD8, 0xAF, 0x90,
  0x2B, 0x34, 0x81, 0x36, 0x2C, 0xAA, 0xBC, 0x0E, 0x25, 0x8B,
  0xE4, 0x8A, 0xC6, 0xA2, 0x81, 0x9F, 0x75, 0x55]
enc=[]
for i in range(6):#以小端序的方式读值
    tmp='0x'
    for j in range(7,-1,-1):
        tmp+=hex(arr[i*8+j]).replace('0x','').zfill(2)
    enc.append(tmp)
flag_data=[]
for i in range(len(enc)):
    tmp=int(enc[i],16)
    for j in range(64):#循环64次
        sign=tmp&1      #判断当前v4的符号 &1后为1即为奇数 为0即为偶数
        if sign:        #奇数要异或回去
            tmp^=0xB0004B7679FA26B3
        tmp//=2          #无论正负都要右移一位回去
        if sign:        #奇数右移一位后还要补回左移一位时移出的1
            tmp|=0x8000000000000000
    flag_data.append(hex(tmp).replace('0x','').replace('L',''))
flag_str=""
for i in range(6):#由于存储方式是小端序 所以需要从后往前的读
    tmp=flag_data[i]
    for j in range(len(tmp)-2,-1,-2):
        num=int('0x'+tmp[j:j+2],16)
        flag_str+=chr(num)
print(flag_str)
#flag{6ff29390-6c20-4c56-ba70-a95758e3d1f8}

[安洵杯 2019]game

elf文件,无壳,ida分析
main函数调用的许多函数都有控制流平坦化混淆,用defalt.py脚本一个一个去平坦化
main函数,获取输入,general_inspection函数利用sudoku做一些运算,不太明白作用,不过影响不大,trace函数完全填充sudoku,动调可得到完全填充的结果,check函数没什么作用,check1函数对输入进行变换,最后进入check3函数验证输入
game-main
进入check1函数,对input进行变换,首先input前半部分和后半部分交换,然后input两个一组,两两交换,最后再对input进行变换,input最后的变换结果均为数字字符
game-check1
check3->check2函数,首先将变换后的input中的数字字符转成普通数字,存储到v11,然后v11顺序地填充到D0g3中元素为0的位置,未完全填充的D0g3也可通过动调得到,最后比较D0g3和sudoku
game-check2
动调得到未完全填充的D0g3和已完全填充的sudoku后,写逆运算脚本即可得到flag

#coding:utf-8
sudoku=[
1,4,5,3,2,7,6,9,8,
8,3,9,6,5,4,1,2,7,
6,7,2,8,1,9,5,4,3,
4,9,6,1,8,5,3,7,2,
2,1,8,4,7,3,9,5,6,
7,5,3,2,9,6,4,8,1,
3,6,7,5,4,2,8,1,9,
9,8,4,7,6,1,2,3,5,
5,2,1,9,3,8,7,6,4]
D0g3=[
1,0,5,3,2,7,0,0,8,
8,0,9,0,5,0,0,2,0,
0,7,0,0,1,0,5,0,3,
4,9,0,1,0,0,3,0,0,
0,1,0,0,7,0,9,0,6,
7,0,3,2,9,0,4,8,0,
0,6,0,5,4,0,8,0,9,
0,0,4,0,0,1,0,3,0,
0,2,1,0,3,0,7,0,4]
arr=[]
for i in range(81):
    if D0g3[i]==0:  #input填充到D0g3中元素为0的位置
        arr.append(sudoku[i])
for i in range(len(arr)):
    for j in range(32,128):#对input的第三次变换通过爆破来解
        if arr[i]+48==(j & 0xF3 | ~j & 0xC) - 20:
            arr[i]=j
for i in range(0,len(arr),2): #两个一组,两两交换
    arr[i],arr[i+1]=arr[i+1],arr[i]
arr[0:20],arr[20:40]=arr[20:40],arr[0:20]#input前半部分和后半部分交换
flag="".join(chr(i) for i in arr)
print(flag)
#KDEEIFGKIJ@AFGEJAEF@FDKADFGIJFA@FDE@JG@J

运行elf,输入,验证正确
game-right

[SCTF2019]Strange apk

apk文件,jadx-gui打开
主要逻辑在sctf.hello.c类中
data是apk自带的资源文件,在assets目录下可找到,先对data进行__方法处理,再进行_方法处理
strangeapk-logic
__方法就是读取data文件的字节
strangeapk-__
_方法是对data文件的字节进行0方法的处理,再将结果写到另一个文件中
strangeapk-_
0方法,可知是对data文件读取到的字节与"syclover"做循环异或运算,结果写到另一个文件中
在这里插入图片描述
脚本:

f= open("D:\\ctfdownloadfiles\\data", "rb")
res= open("D:\\ctfdownloadfiles\\res", "wb")
data=f.read()
s="syclover"
res_data=""
count=0
for c in data:
    res_data+=chr(ord(c)^ord(s[count%len(s)]))
    count+=1
res.write(res_data)
res.close()

将输出的文件后缀名改为apk后,再用jadx-gui分析
先看sctf.demo.myapplication.s类,验证输入的长度是否为30,对输入的前12个字符做base64编码后与已知字符串比较,后18个字符赋给data_return
strangeapk-s
再看sctf.demo.myapplication.t类,key为"syclover"的md5的十六进制摘要,输入的后18个字符与key作为参数传入f.encode方法,返回的字符串与已知字符串比较
strangeapk-t
再看sctf.demo.myapplication.f类,str为输入的后18个字符,长度s为18,key为"syclover"的md5的十六进制摘要,为"8bfc8af07bca146c937f283b8ec768d4",长度c为32,for循环中,t先append一个输入字符,再append一个key中的字符,由于f<s<c,故t从key中append的字符总是第0个字符,即’8’,同时也知道返回字符串中某字符的下标为偶数时,其即为输入的字符
strangeapk-f
写脚本即可得到flag

import base64
s=base64.b64decode("c2N0ZntXM2xjMG1l")
res="~8t808_8A8n848r808i8d8-8w808r8l8d8}8"
for i in range(0,len(res),2):
    s+=res[i]
print(s)
#sctf{W3lc0me~t0_An4r0id-w0rld}

[CFI-CTF 2018]IntroToPE

exe程序,运行后输入password,点击验证
查壳发现时.Net程序,用dnSpy打开
来到ValidatePasswd.verifyPasswd()方法,简单的base64编码验证
introtope-logic
解base64即可得到flag
introtope-flag

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

P1umH0

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

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

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

打赏作者

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

抵扣说明:

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

余额充值