BUUCTF reverse wp 56 - 60

[ACTF新生赛2020]SoulLike

BUUCTF reverse wp 56 - 60_第1张图片

__int64 __fastcall main(int a1, char **a2, char **a3)
{
  char v5; // [rsp+7h] [rbp-B9h]
  int i; // [rsp+8h] [rbp-B8h]
  int j; // [rsp+Ch] [rbp-B4h]
  int flag_content[14]; // [rsp+10h] [rbp-B0h] BYREF
  char flag[110]; // [rsp+4Ah] [rbp-76h] BYREF
  unsigned __int64 v10; // [rsp+B8h] [rbp-8h]

  v10 = __readfsqword(0x28u);
  printf("input flag:");
  scanf("%s", &flag[6]);
  strcpy(flag, "actf{");
  v5 = 1;
  for ( i = 0; i <= 4; ++i )
  {
    if ( flag[i] != flag[i + 6] )
    {
      v5 = 0;
      goto next;
    }
  }
  if ( !v5 )
    goto failed;
next:
  for ( j = 0; j <= 11; ++j )
    flag_content[j] = flag[j + 11];
  if ( (unsigned __int8)sub_83A(flag_content) && flag[23] == '}' )
  {
    printf("That's true! flag is %s", &flag[6]);
    return 0LL;
  }
  else
  {
failed:
    printf("Try another time...");
    return 0LL;
  }
}

sub_83A太大了无法分析
BUUCTF reverse wp 56 - 60_第2张图片

强行让IDA分析大函数, 在IDA安装目录的cfg文件夹找到hexrays.cfg修改配置, 把MAX_FUNCSIZE改大点
在这里插入图片描述

BUUCTF reverse wp 56 - 60_第3张图片
重启再F5出来, 得到3k+行的代码

__int64 __fastcall sub_83A(_DWORD *a1)
{
  int i; // [rsp+1Ch] [rbp-44h]
  int v3[14]; // [rsp+20h] [rbp-40h]
  unsigned __int64 v4; // [rsp+58h] [rbp-8h]

  v4 = __readfsqword(0x28u);
  *a1 ^= 0x2Bu;
  a1[1] ^= 0x6Cu;
  a1[2] ^= 0x7Eu;
  a1[3] ^= 0x56u;
  a1[4] ^= 0x39u;
  a1[5] ^= 3u;
  a1[6] ^= 0x2Du;
  a1[7] ^= 0x28u;
  a1[8] ^= 8u;
...
  v3[6] = 53;
  v3[7] = 110;
  v3[8] = 0;
  v3[9] = 19;
  v3[10] = 30;
  v3[11] = 56;
  for ( i = 0; i <= 11; ++i )
  {
    if ( v3[i] != a1[i] )
    {
      printf("wrong on #%d\n", (unsigned int)i);
      return 0LL;
    }
  }
  return 1LL;
}

基本都是xor操作, 不想硬逆的话(硬逆也不是不行, 万行以内均可硬逆), 可以copy到c代码进行爆破, 或者pwntools逐位爆破, 或者采用angr自动化分析flag
最简洁的是pwntools逐位爆破, (angr的方法内存不够跑不出

from pwn import *
import re

context.log_level = 'debug'
ended = False
flag = ['z' for _ in range(12)]
for i in range(12):
    if ended == True: break
    for ch in range(32, 127):
        io = process('./SoulLike')
        flag[i] = chr(ch)
        payload = 'actf{' + ''.join(flag) + '}'
        io.sendline(payload)
        recv_content = io.recvline()
        idx = re.findall(b'on #(.*?)\n', recv_content)[0]
        idx = int(idx)
        
        if b"That's true!" in recv_content:
            print(recv_content)
            ended = True
            break

        if idx != i: break
        io.close()    
io.close()

[GWCTF 2019]re3

$ file attachment
attachment: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=c3b276030138cc9e29a0244b296b8f005a487a77, stripped
void __fastcall __noreturn main(int a1, char **a2, char **a3)
{
  int i; // [rsp+8h] [rbp-48h]
  char s[40]; // [rsp+20h] [rbp-30h] BYREF
  unsigned __int64 v5; // [rsp+48h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  __isoc99_scanf("%39s", s);
  if ( (unsigned int)strlen(s) != 32 )
  {
    puts("Wrong!");
    exit(0);
  }
  mprotect(&dword_400000, 0xF000uLL, 7);
  for ( i = 0; i <= 223; ++i )
    *((_BYTE *)sub_402219 + i) ^= 0x99u;
  sub_40207B(&unk_603170);
  sub_402219(s);
}

unsigned __int64 __fastcall sub_40207B(__int64 a1)
{
  char v2[16]; // [rsp+10h] [rbp-50h] BYREF
  __int64 v3; // [rsp+20h] [rbp-40h] BYREF
  __int64 v4; // [rsp+30h] [rbp-30h] BYREF
  __int64 v5; // [rsp+40h] [rbp-20h] BYREF
  unsigned __int64 v6; // [rsp+58h] [rbp-8h]

  v6 = __readfsqword(0x28u);
  sub_401CF9(&BASE64_table_603120, 64LL, v2);   // MD5
  sub_401CF9(&unk_603100, 20LL, &v3);
  sub_401CF9(&Prime_Constants_char_6030C0, 53LL, &v4);
  sub_401CF9(&MD5_Constants_4025C0, 256LL, &v5);
  sub_401CF9(v2, 64LL, a1);                     // MD5
  return __readfsqword(0x28u) ^ v6;
}

写个IDC脚本解密代码段

#include 
 
static main(void){
    auto i = 0;
    auto m = 0;
    for(i = 0x402219; i <= 0x402219 + 223; i=i+1){
        m = byte(i);
        patch_byte(i,m ^ 0x99);
    }
}

如果解密之后的汇编把 push rbpmov rbp, rsp隔开看成两个函数, 就先右键undefine再c, 再全选中后p定义函数, 此时F5就可以看到解密后的反编译伪代码
BUUCTF reverse wp 56 - 60_第4张图片

__int64 __fastcall sub_402219(__int64 input)
{
  unsigned int v2; // [rsp+18h] [rbp-D8h]
  int i; // [rsp+1Ch] [rbp-D4h]
  char v4[200]; // [rsp+20h] [rbp-D0h] BYREF
  unsigned __int64 v5; // [rsp+E8h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  sub_400A71((__int64)v4, (__int64)&unk_603170);
  sub_40196E((__int64)v4, input);               // AES
  sub_40196E((__int64)v4, input + 16);          // AES
  v2 = 1;
  for ( i = 0; i <= 31; ++i )
  {
    if ( *(_BYTE *)(i + input) != byte_6030A0[i] )
      v2 = 0;
  }
  return v2;
}

最后会和byte_6030A0已知的数组进行比较, 猜测经过的一串处理是某种加密, 用findcrypt插件查看密码函数特征
在这里插入图片描述
识别出处理unk_603170的函数是MD5和AES轮密钥函数, 处理input的函数是AES, 整体分析得到流程: MD5两次base64table得到的数值经过AES轮密钥加密得到key, 然后用key对输入的字符串高16位和低16位分别加密(因为不存在iv, 所以加密模式是ECB), 最后和byte_6030A0数组进行比较, key部分可以动调得到(跳过大段逆向)
BUUCTF reverse wp 56 - 60_第5张图片

另外记得chmod +x attachment
BUUCTF reverse wp 56 - 60_第6张图片

在这里插入图片描述

用原版调试
BUUCTF reverse wp 56 - 60_第7张图片

BUUCTF reverse wp 56 - 60_第8张图片

BUUCTF reverse wp 56 - 60_第9张图片
BUUCTF reverse wp 56 - 60_第10张图片

得到key就简单了, REV

from Crypto.Cipher import AES
from Crypto.Util.number import *

key = 0xCB8D493521B47A4CC1AE7E62229266CE
testval = 0xBC0AADC0147C5ECCE0B140BC9C51D52B46B2B9434DE5324BAD7FB4B39CDB4B5B
key = long_to_bytes(key)
testval = long_to_bytes(testval)
round = AES.new(key, mode=AES.MODE_ECB)
flag = round.decrypt(testval)
print(flag.decode())

[GXYCTF2019]simple CPP

BUUCTF reverse wp 56 - 60_第11张图片

这里主函数比较长, 不贴完整代码, 因为没有壳而且处理逻辑不直观还有exe不包含恶意代码, 可以考虑直接主机IDA动调
在IDA目录下的dbgsrv文件夹, 打开win64_remote64
在这里插入图片描述

IDA debug选项设置之后就可以动调
BUUCTF reverse wp 56 - 60_第12张图片

这里动调可以确定第一个异或逻辑的v12
BUUCTF reverse wp 56 - 60_第13张图片

BUUCTF reverse wp 56 - 60_第14张图片

调试时可以分析出这一段逻辑是在对中间值自身做移位叠加(其实就是颠倒存储顺序, 左移8位就是一个字节, buf的低字节存到高地址)
BUUCTF reverse wp 56 - 60_第15张图片接着需要绕过反调试, 在test eax, eax时设置eax为0
在这里插入图片描述

根据判断条件得知需要确定v26, v25, v31, v32; v20, v33的值
BUUCTF reverse wp 56 - 60_第16张图片

逆回去分析各个值
BUUCTF reverse wp 56 - 60_第17张图片

整理得到四个变量的方程组, 用z3求解

from z3 import *
 
v13, v14, v15, v16 = BitVecs('v13 v14 v15 v16',64)
s=Solver()

s.add(v14 & ~v16 == 0x11204161012)
s.add((v14 & ~v16) | (v15 & v16) | (v14 & ~v15) | (v16 & ~v15) == 0x3E3A4717373E7F1F)
s.add((v14 & ~v15) & (v16) | (v14) & ((v15 & v16) | v15 & ~v16 | ~(v15 | v16)) == 0x8020717153E3013)
s.add(((v14 & ~v16) | (v15 & v16) | v15 & v14) == (~v16 & v14 | 0xC00020130082C0C))
s.add(v13 == 0x32310600)
# s.add(((v14 & ~v16) | (v15 & v16) | (v14 & ~v15) | (v16 & ~v15)) ^ v13 == 0x3E3A4717050F791F)

s.check()
m = s.model()
for i in m:
    print("%s = 0x%x" % (i, m[i].as_long()))

'''
v16 = 0x3e3a460533286f0d
v14 = 0x8020717153e3013
v15 = 0xc0002213008acac
v13 = 0x32310600
'''

再把v13 ~ v16作为字节串处理, 进行高低端序颠倒处理的逆过程, 然后与i_will_check_is_debug_or_not进行xor还原flag (这里已知比赛时的hint: 第二部分字符串为e!P0or_a

L = []
L.append(hex(m[v16].as_long())[2:].rjust(16, '0'))
L.append(hex(m[v15].as_long())[2:].rjust(16, '0'))
L.append(hex(m[v14].as_long())[2:].rjust(16, '0'))
L.append(hex(m[v13].as_long())[2:-2])

print(L)
temp = []
for each in L:
    if each != '323106':
        for i in range(0, 16, 2):
            tmp = each[i: i + 2]
            tmp = int(tmp, 16)
            temp.append(tmp)
    else:
        for i in range(0, 6, 2):
            tmp = each[i: i + 2]
            tmp = int(tmp, 16)
            temp.append(tmp)

print(temp, len(temp))
string = 'i_will_check_is_debug_or_not'
flag = ''
hint = 'e!P0or_a'
for i in range(len(temp)):
    if i >= 8 and i < 16:
        flag += hint[i % 8]
    else:
        flag += chr(ord(string[i]) ^ temp[i])

print('flag{' + flag + '}')

[FlareOn5]Ultimate Minesweeper

BUUCTF reverse wp 56 - 60_第18张图片

打开dnSpy进行一波C#逆向

public MainForm() // 函数入口
{
	this.InitializeComponent();
	this.MineField = new MineField(MainForm.VALLOC_NODE_LIMIT); // 地雷初始化
	this.AllocateMemory(this.MineField); // 分配空间
	this.mineFieldControl.DataSource = this.MineField;
	this.mineFieldControl.SquareRevealed += this.SquareRevealedCallback; // 事件响应
	this.mineFieldControl.FirstClick += this.FirstClickCallback; 
	this.stopwatch = new Stopwatch();
	this.FlagsRemaining = this.MineField.TotalMines;
	this.mineFieldControl.MineFlagged += this.MineFlaggedCallback;
	this.RevealedCells = new List<uint>();
}

public MineField(uint size)
{
	this.Size = size;
	this.MinesPresent = new bool[(int)size, (int)size];
	this.MinesVisible = new bool[(int)size, (int)size];
	this.MinesFlagged = new bool[(int)size, (int)size];
}

private void AllocateMemory(MineField mf)
{
	for (uint num = 0U; num < MainForm.VALLOC_NODE_LIMIT; num += 1U)
	{
		for (uint num2 = 0U; num2 < MainForm.VALLOC_NODE_LIMIT; num2 += 1U)
		{
			bool flag = true;
			uint r = num + 1U;
			uint c = num2 + 1U;
			if (this.VALLOC_TYPES.Contains(this.DeriveVallocType(r, c)))
			{
				flag = false;
			}
			mf.GarbageCollect[(int)num2, (int)num] = flag;
		}
	}
}

// Token: 0x0600000C RID: 12 RVA: 0x00002348 File Offset: 0x00000548
private void SquareRevealedCallback(uint column, uint row)
{
	if (this.MineField.BombRevealed)
	{
		this.stopwatch.Stop();
		Application.DoEvents();
		Thread.Sleep(1000);
		new FailurePopup().ShowDialog();
		Application.Exit();
	}
	this.RevealedCells.Add(row * MainForm.VALLOC_NODE_LIMIT + column);
	if (this.MineField.TotalUnrevealedEmptySquares == 0)
	{
		this.stopwatch.Stop();
		Application.DoEvents();
		Thread.Sleep(1000);
		new SuccessPopup(this.GetKey(this.RevealedCells)).ShowDialog();
		Application.Exit();
	}
}

// Token: 0x0600000D RID: 13 RVA: 0x000023E4 File Offset: 0x000005E4
private string GetKey(List<uint> revealedCells)
{
	revealedCells.Sort();
	Random random = new Random(Convert.ToInt32(revealedCells[0] << 20 | revealedCells[1] << 10 | revealedCells[2]));
	byte[] array = new byte[32];
	byte[] array2 = new byte[]
	{
		245,
		75,
		65,
		142,
		68,
		71,
		100,
		185,
		74,
		127,
		62,
		130,
		231,
		129,
		254,
		243,
		28,
		58,
		103,
		179,
		60,
		91,
		195,
		215,
		102,
		145,
		154,
		27,
		57,
		231,
		241,
		86
	};
	random.NextBytes(array);
	uint num = 0U;
	while ((ulong)num < (ulong)((long)array2.Length))
	{
		byte[] array3 = array2;
		uint num2 = num;
		array3[(int)num2] = (array3[(int)num2] ^ array[(int)num]);
		num += 1U;
	}
	return Encoding.ASCII.GetString(array2);
}

GetKey应该是跟flag有关的函数, 不过这里会和随机数组进行异或, 静态分析不通则转换思路, 游戏的目标是点出三个非雷区格子, 而且C#逆向可以直接修改源码所以采取patch的方法解决这个exe, 把踩雷之后的处理逻辑注释掉然后全部保存为_patched.exe, 暴力找出目标格子
BUUCTF reverse wp 56 - 60_第19张图片

最后再重新运行连点出三个格子就跳flag了

[MRCTF2020]PixelShooter

BUUCTF reverse wp 56 - 60_第20张图片

APK逆向, 拖入jeb (一开始用jadx分析不了非java源码
发现java代码没有关键逻辑
搜索一下得知安卓unity游戏的核心逻辑一般位于main\assets\bin\Data\Managed\Assembly-CSharp.dll
可以直接得到flag
BUUCTF reverse wp 56 - 60_第21张图片

你可能感兴趣的:(逆向工程,逆向)