[ACTF新生赛2020]SoulLike
__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
太大了无法分析
强行让IDA分析大函数, 在IDA安装目录的cfg文件夹找到hexrays.cfg
修改配置, 把MAX_FUNCSIZE
改大点
重启再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 <idc.idc>
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 rbp
和 mov rbp, rsp
隔开看成两个函数, 就先右键undefine再c, 再全选中后p定义函数, 此时F5就可以看到解密后的反编译伪代码
__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部分可以动调得到(跳过大段逆向)
另外记得chmod +x attachment
用原版调试
得到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
这里主函数比较长, 不贴完整代码, 因为没有壳而且处理逻辑不直观还有exe不包含恶意代码, 可以考虑直接主机IDA动调
在IDA目录下的dbgsrv文件夹, 打开win64_remote64
IDA debug选项设置之后就可以动调
这里动调可以确定第一个异或逻辑的v12
调试时可以分析出这一段逻辑是在对中间值自身做移位叠加(其实就是颠倒存储顺序, 左移8位就是一个字节, buf的低字节存到高地址)
接着需要绕过反调试, 在test eax, eax
时设置eax为0
根据判断条件得知需要确定v26, v25, v31, v32; v20, v33
的值
逆回去分析各个值
整理得到四个变量的方程组, 用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
打开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
, 暴力找出目标格子
最后再重新运行连点出三个格子就跳flag了
[MRCTF2020]PixelShooter
APK逆向, 拖入jeb (一开始用jadx分析不了非java源码
发现java代码没有关键逻辑
搜索一下得知安卓unity游戏的核心逻辑一般位于main\assets\bin\Data\Managed\Assembly-CSharp.dll
可以直接得到flag