初赛
Don't DeBugMe
动态获得数据解密即可
##include <stdio.h>
int main(void) {
unsigned int ret = 0x685ee20;
//printf("%x\n", ret);
for (int i = 0; i < 20; i++) {
cipher[i] ^= 0xee20;
cipher[i] -= 0x0685;
}
printf("%s", cipher);
}
BasicLoader
程序装载了反调试,在TLS回调函数中patch掉反调试检测后即可正常调试,同时TLS初始化了一块地址
int __fastcall main(int argc, const char **argv, const char **envp)
{
const char **envp_1; // r8
HANDLE hHandle; // [rsp+30h] [rbp-108h]
_BYTE Parameter[112]; // [rsp+40h] [rbp-F8h] BYREF
_BYTE v7[112]; // [rsp+B0h] [rbp-88h] BYREF
memset(Parameter, 0, 0x64uLL);
sub_7FF769DFD920(std::cout, "input flag> ", envp);
std::istream::getline(std::cin, v7, 99LL);
if ( sub_7FF769DD1140(v7, Parameter) )
{
hHandle = CreateThread(0LL, 0x1000uLL, StartAddress, Parameter, 0, 0LL);
WaitForSingleObject(hHandle, 0xFFFFFFFF);
}
else
{
sub_7FF769DFD920(std::cout, "wrong flag!\n", envp_1);
}
return 0;
}
发现main函数会创造线程,进入StartAddress,在TLS初始化的shellcode中可以看到,发现是rc4解密出来数据
##include <stdio.h>
##include <string.h>
unsigned char data[] = {
0x68, 0x60, 0x0C, 0x1B, 0x2A, 0xB3, 0xEE, 0x4A, 0x17, 0x7C, 0xB7, 0xF6, 0x91, 0xEA, 0x92, 0x2D,
0x6B, 0xAD, 0x61, 0xC2, 0x5F, 0x70, 0x2C, 0x14, 0x74, 0x0E, 0xA2, 0xAF, 0x8A, 0x57, 0xFF, 0x16,
0xD2, 0x18, 0xDF, 0x4C, 0xB4, 0x4D, 0x80, 0x8C, 0xDA, 0xB0, 0x81, 0x41, 0xB5, 0x64, 0x8B, 0x71,
0xE5, 0x36, 0x39, 0x46, 0x10, 0xF2, 0x97, 0x25, 0xB0, 0x05, 0x10, 0x00, 0x7F, 0x96, 0xE4, 0x64,
0x0C, 0x0B, 0x14, 0xBC, 0x52, 0xEA, 0x64, 0xB6, 0xE5, 0xDE, 0x03, 0xB5, 0x52, 0x4E, 0x8D, 0x1F,
0x66, 0xCD, 0x68, 0x19, 0x65, 0x93, 0x5F, 0xC1, 0x30, 0xBC, 0xD0, 0x52, 0x86, 0x01, 0x4D, 0xB6,
0x99, 0x45, 0x40, 0x66, 0x3B, 0xBE, 0x13, 0x42, 0x4E, 0x9B, 0x18, 0x6D, 0xBA, 0x00, 0x74, 0x99,
0xB2, 0x65, 0xEC, 0x6C, 0xDF, 0x51, 0x17, 0x8A, 0x84, 0x3A, 0xF3, 0x5D, 0xC8, 0xE9, 0x88, 0x65,
0x9D, 0x5B, 0x4F, 0x1D, 0xC1, 0x16, 0xB5, 0x96, 0xC4, 0x8C, 0xFB, 0xEA, 0xA2, 0x16, 0x23, 0x38,
0x8E, 0xE4, 0x09, 0x99, 0x55, 0x58, 0x4A, 0x4F
};
static const size_t data_len = sizeof(data);
// RC4密钥调度算法 (KSA)
void RC4_KSA(unsigned char *key, int keyLength, unsigned char *S) {
int i, j = 0;
unsigned char temp;
for (i = 0; i < 256; i++) {
S[i] = i;
}
for (i = 0; i < 256; i++) {
j = (j + S[i] + key[i % keyLength]) % 256;
// 交换 S[i] 和 S[j]
temp = S[i];
S[i] = S[j];
S[j] = temp;
}
}
// RC4伪随机生成算法 (PRGA)
void RC4_PRGA(unsigned char *S, unsigned char *data, int dataLength) {
int i = 0, j = 0, k;
unsigned char temp;
for (k = 0; k < dataLength; k++) {
i = (i + 1) % 256;
j = (j + S[i]) % 256;
// 交换 S[i] 和 S[j]
temp = S[i];
S[i] = S[j];
S[j] = temp;
// 获取伪随机字节并与数据进行异或
data[k] ^= S[(S[i] + S[j]) % 256];
}
}
int main() {
unsigned char key[] = "babyflag"; // 密钥
int keyLength = strlen((char *)key);
int dataLength = sizeof(data) / sizeof(data[0]);
unsigned char S[256]; // 状态向量
// 密钥调度算法 (KSA)
RC4_KSA(key, keyLength, S);
// 伪随机生成算法 (PRGA) 用于加密数据
RC4_PRGA(S, data, dataLength);
printf("Encrypted text: ");
for (int i = 0; i < dataLength; i++) {
printf("%02X ", data[i]);
}
printf("\n");
// 解密时,直接使用相同的加密过程
RC4_KSA(key, keyLength, S);
RC4_PRGA(S, data, dataLength);
printf("Decrypted text: %s\n", data);
return 0;
}
把解密出来的shellcode二进制写入文件,再拖入ida反编译
char __fastcall sub_0(__int64 a1, __int64 a2, __int64 a3, __int64 a4, __int64 a5, __int64 a6, int key)
{
unsigned __int64 i; // r8
_DWORD v9[8]; // [rsp+0h] [rbp-20h] BYREF
if ( !a4 )
LABEL_6:
JUMPOUT(0x98LL);
i = 0LL;
v9[0] = 536952951;
v9[1] = 1997148275;
v9[2] = 2081112864;
v9[3] = 1934771314;
v9[4] = 1934627112;
v9[5] = 542524535;
v9[6] = 1996755745;
v9[7] = 1917994785;
key = 1144201745;
do
{
if ( *(v9 + i) != (*(v9 + i + a4 - v9) ^ *(&key + (i & 3))) )
goto LABEL_6;
++i;
}
while ( i < 0x20 );
return 1;
}
解密脚本
##include <stdio.h>
int main() {
unsigned v9[8];
v9[0] = 0x20014077;
v9[1] = 0x770A1073;
v9[2] = 0x7C0B4320;
v9[3] = 0x73524472;
v9[4] = 0x73501128;
v9[5] = 0x20564477;
v9[6] = 0x77041321;
v9[7] = 0x72524721;
unsigned int key = 0x44332211;
for (int i = 0; i < 8; i++) {
v9[i] ^= key;
}
printf("%.32s", v9);
}
Chal
程序分析
发现附件提供了一个output_file文件,猜测是会读取一个文件加密后产生outpu_file
在ida中搜索可以input_file字符串,跟踪后打断点可以发现主程序
跟踪程序可以发现开始是取了头四个字节作为密钥进行了rc4加密
// Hidden C++ exception states: ##wind=6
_OWORD *__fastcall RC4_KSA(_OWORD *Sbox, _BYTE *input, __int64 length_n4)
{
_BYTE *inp_end; // r15
unsigned __int8 tmp; // al
_BYTE *p_input_start; // rcx
_BYTE *p_input_end; // rdx
__int64 i; // rdi
char sbox_i; // r8
memset(Sbox, 0, 0x102uLL);
*Sbox = xmmword_7FF703BB5E70;
Sbox[1] = xmmword_7FF703BB5E80;
qmemcpy(Sbox + 2, " !\"##$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmno", 80);
Sbox[7] = xmmword_7FF703BB5EE0;
Sbox[8] = xmmword_7FF703BB5EF0;
Sbox[9] = xmmword_7FF703BB5F00;
Sbox[10] = xmmword_7FF703BB5F10;
Sbox[11] = xmmword_7FF703BB5F20;
Sbox[12] = xmmword_7FF703BB5F30;
Sbox[13] = xmmword_7FF703BB5F40;
Sbox[14] = xmmword_7FF703BB5F50;
Sbox[15] = xmmword_7FF703BB5F60;
// 初始化sbox
if ( length_n4 )
{
inp_end = &input[length_n4];
tmp = 0;
p_input_start = input;
p_input_end = inp_end;
for ( i = 0LL; i != 256; ++i )
{
if ( p_input_start == p_input_end )
{
p_input_end = inp_end;
p_input_start = input;
}
sbox_i = *(Sbox + i);
tmp += sbox_i + *p_input_start;
*(Sbox + i) = *(Sbox + tmp);
++p_input_start;
*(Sbox + tmp) = sbox_i;
}
}
return Sbox;
}
// Hidden C++ exception states: ##wind=6
__int64 __fastcall RC4_PRGA(_BYTE *sbox)
{
unsigned __int8 v1; // al
__int64 v2; // r8
char v3; // dl
unsigned __int8 v4; // al
v1 = sbox[256] + 1;
sbox[256] = v1;
v2 = v1;
v3 = sbox[v1];
v4 = v3 + sbox[257];
sbox[257] = v4;
sbox[v2] = sbox[v4];
sbox[v4] = v3;
return sbox[(sbox[v2] + v3)];
}
之后通过FindCrypt插件可以发现还有一个salsa20加密算法,通过动态调试可以发现是继续取到头四个字节重复4次继续作为key,并且生成了一个随机的nounce,但是发现会将这个nounce追加到output_file文件的末尾输出,然后解密即可
解密流程
修改程序中的nounce为文件末尾的数据,再修改key为DASC.
经过程序运算后取rc4加密后的数据和salsa20加密后的数据,两者异或运算后可以得到keystream
keystream = [0x66,0x38,0x1,0x5d,0x8c,0x31,0xbe,0x78,0x6,0x9b,0x5f,0x73,0x5e,0x4b,0xf1,0x90,0xac,0x30,0xf8,0x47,0xb0,0x76,0x89,0xd,0x8,0xea,0x3b,0x32,0xfb,0x80,0x9,0x24,0xb2,0x17,0xef,0xad,0xd3,0x3a,0xc2,0xca]
拿着这个keystream以后outp_file程序中的内容,再进行rc4解密即可得到flag
def KSA(key):
""" KSA 密钥拓展 """
""" 用来扩散密钥 """
S = list(range(256))
tmp = 0
for i in range(256):
tmp = (tmp + S[i] + key[i % len(key)]) % 256
S[i], S[tmp] = S[tmp], S[i]
return S
def PRGA(S):
""" PRGA 伪随机数生成算法 """
i, j = 0, 0
while True:
i = (i + 1) % 256
j = (j + S[i]) % 256
S[i], S[j] = S[j], S[i]
K = S[(S[i] + S[j]) % 256]
yield K
def RC4(key, text):
""" RC4 加密流程 """
S = KSA(key)
keystream = PRGA(S)
res = []
for char in text:
res.append(char ^ next(keystream))
return bytes(res)
def main():
buffer = b''.join([
b'\xD3\x7E\x95\x3D\xC6\xB4\xE2\x87\xBC\x95\x7D\xA9\x3B\x60\x45\x7B',
b'\x9B\xC4\x99\x80\xC6\x00\xF0\x0E\x0F\x4B\x0D\xDD\xF1\x4D\x2F\x80',
b'\xAF\x31\x92\xA1\xE7\x76\xC9\x32'])
keystream = [0x66,0x38,0x1,0x5d,0x8c,0x31,0xbe,0x78,0x6,0x9b,0x5f,0x73,0x5e,0x4b,0xf1,0x90,0xac,0x30,0xf8,0x47,0xb0,0x76,0x89,0xd,0x8,0xea,0x3b,0x32,0xfb,0x80,0x9,0x24,0xb2,0x17,0xef,0xad,0xd3,0x3a,0xc2,0xca]
target = []
for i in range(len(buffer)):
target.append(buffer[i] ^ keystream[i])
out = RC4(b"DASC", bytes(target))
print(out)
if __name__ == "__main__":
main()
DASCTF{6d175aa7-84cb-4d2d-81ae-f7b984440229}
决赛
天命人
根据算法dump出解密即可,比较简单
cipher = [0x44,0x40,0x51,0x40,0x50,0x43,0x7d,0x3e,0x38,0x6c,0x3a,0x3f,0x3e,0x6f,0x38,0x6d,0x23,0x21,0x24,0x20,0x2d,0x74,0x20,0x74,0x2c,0x2f,0x2e,0x28,0x25,0x25,0x78,0x2d,0x12,0x43,0x44,0x47,0x10,0x15,0x40,0x5a]
flag = []
for i in range(0, len(cipher)):
print(chr(cipher[i] ^ i),end='')
Androidtest
跟踪到这里面重写的OnClick函数

fridahook字符串得到换过表的base32
Java.perform(function() {
var MainActivity = Java.use("com.example.myapplication.MainActivity")
var str = MainActivity.z;
console.log("str is:" + str);
})
ABCDEFGHIJKLMNOPQRSTUVWXYZ234567=
lib中可以看到ret_str函数,发现是和一个编码的字符串比较,从中dump出数据换表后异或即可
用cyberchef解密
DASCTF{android_anti_and_test_is_interesting}
- 这道题目我发现jeb似乎在java层反编译上和jadx差不多,而且还是比较完善的,以后还是多用jeb吧
- frida hook还是不够熟练,当时折腾了好久才hook成功
- 是时候搞搞安卓的动调了
Warning
一道虚拟机的题目,比赛的时候没有做出来,赛后复现一下.
附件里面可以看到一个warning文件和program,初步分析后发现这一个应该是一个虚拟机程序,program里面存储的是程序的字节码.
__int64 __fastcall main(int a1, char **a2, char **a3)
{
FILE *stream; // rbp
int n; // ebx
char *src; // rbp
size_t input_len; // rax
int input_len_1; // ebx
__int64 v8; // rdx
__int64 v9; // r8
__int64 v10; // r9
int dest_1[1552]; // [rsp-1C40h] [rbp-34A8h] BYREF
_BYTE buf[1024]; // [rsp-400h] [rbp-1C68h] BYREF
_BYTE dest[6208]; // [rsp+0h] [rbp-1868h] BYREF
__int64 v15; // [rsp+1840h] [rbp-28h] BYREF
if ( a1 <= 1 )
return 0xFFFFFFFFLL;
memset(dest, 0, sizeof(dest));
stream = fopen("program", "rb");
// 移动到文件开头
fseek(stream, 0LL, 2);
n = ftell(stream);
fseek(stream, 0LL, 0);
memset(buf, 0, sizeof(buf));
fread(buf, 1uLL, n, stream);
memcpy(dest, buf, n);
src = a2[1];
if ( !*src )
return 0xFFFFFFFFLL;
// 获取输入参数的长度
input_len = strlen(a2[1]);
input_len_1 = input_len;
if ( input_len > 0x40 )
return 0xFFFFFFFFLL;
memcpy(&dest[2048], src, input_len);
qmemcpy(dest_1, dest, sizeof(dest_1));
func(input_len_1, &v15, v8, 0LL, v9, v10, dest_1[0]);
return 0LL;
}
这里是主函数,发现从program文件中读取数据后调用func函数,步入func函数查看一下.

反编译有点难看,但是大概能够看出来是一个虚拟机的opcode处理逻辑(当时看到这里直接微距了...)
没办法只能动调了,当时我动调了好久都没有看出来逻辑...看了writeup直接trace后面的异或运算了...
case 5:
v18 = *(&program + STACK[0x1010] + 1) & 7;
input_str[v18] ^= *(&STACK[0x1010] + ((*(&program + STACK[0x1010] + 1) >> 3) & 7));
goto LABEL_8;
发现这里应该是异或运算,在这里打下条件断点,看看运算的字符
FirstEncode
0x31 xor 0x0
0x31 xor 0x1
0x31 xor 0x2
0x31 xor 0x3
0x32 xor 0x4
0x32 xor 0x5
0x32 xor 0x6
0x32 xor 0x7
0x33 xor 0x8
0x33 xor 0x9
0x33 xor 0xa
0x33 xor 0xb
0x34 xor 0xc
0x34 xor 0xd
0x34 xor 0xe
0x34 xor 0xf
0x31 xor 0x10
0x31 xor 0x11
0x31 xor 0x12
0x31 xor 0x13
0x32 xor 0x14
0x32 xor 0x15
0x32 xor 0x16
0x32 xor 0x17
0x33 xor 0x18
0x33 xor 0x19
0x33 xor 0x1a
0x33 xor 0x1b
0x34 xor 0x1c
0x34 xor 0x1d
0x34 xor 0x1e
0x34 xor 0x1f
0x31 xor 0x20
0x31 xor 0x21
0x31 xor 0x22
0x31 xor 0x23
0x32 xor 0x24
0x32 xor 0x25
0x32 xor 0x26
0x32 xor 0x27
0x33 xor 0x28
0x33 xor 0x29
0x33 xor 0x2a
0x33 xor 0x2b
0x34 xor 0x2c
0x34 xor 0x2d
0x34 xor 0x2e
0x34 xor 0x2f
SecondEncode
0x31 xor 0x50
0x30 xor 0x67
0x33 xor 0x21
0x32 xor 0x2b
0x36 xor 0xce
0x37 xor 0xd7
0x34 xor 0x84
0x35 xor 0x3a
0x3b xor 0xf5
0x3a xor 0xc2
0x39 xor 0xc2
0x38 xor 0x22
0x38 xor 0x48
0x39 xor 0x1d
0x3a xor 0x14
0x3b xor 0x2a
0x21 xor 0x71
0x20 xor 0x25
0x23 xor 0xa7
0x22 xor 0x73
0x26 xor 0x3c
0x27 xor 0x9c
0x24 xor 0x72
0x25 xor 0xfe
0x2b xor 0x3c
0x2a xor 0xb7
0x29 xor 0xad
0x28 xor 0x80
0x28 xor 0xa9
0x29 xor 0x6f
0x2a xor 0x37
0x2b xor 0x46
0x11 xor 0x91
0x10 xor 0x32
0x13 xor 0xb4
0x12 xor 0xf7
0x16 xor 0xa5
0x17 xor 0xad
0x14 xor 0xd8
0x15 xor 0x6b
0x1b xor 0x35
0x1a xor 0x8c
0x19 xor 0xe4
0x18 xor 0x0
0x18 xor 0x20
0x19 xor 0x2e
然后发现就是一个流加密,对输入先进行从0-len(input)的字节异或,然后再和一段keystream异或......(我真没招了,我真的好蠢)
int __fastcall sub_5555555555C0(__int64 a1)
{
__int64 n46; // rax
int v2; // edx
bool v3; // cl
__m128i si128; // [rsp+0h] [rbp-38h]
__m128i v6[2]; // [rsp+10h] [rbp-28h]
n46 = 0LL;
LOBYTE(v2) = 1;
si128 = _mm_load_si128(&xmmword_555555556060);
v6[0] = _mm_load_si128(&xmmword_555555556070);
*(v6 + 14) = _mm_load_si128(&xmmword_555555556080);
do
{
v3 = si128.m128i_i8[n46] == *(a1 + n46);
++n46;
v2 = v3 & v2;
}
while ( n46 != 46 );
if ( v2 )
return puts("Correct!");
else
return puts("Try Again!");
}
最后这里的这个函数动调得到密文解密就可以了...哎,怎么就没做出来呢...
解密脚本
key_stream = [0x50,0x67,0x21,0x2b,0xce,0xd7,0x84,0x3a,0xf5,0xc2,0xc2,0x22,0x48,0x1d,0x14,0x2a,0x71,0x25,0xa7,0x73,0x3c,0x9c,0x72,0xfe,0x3c,0xb7,0xad,0x80,0xa9,0x6f,0x37,0x46,0x91,0x32,0xb4,0xf7,0xa5,0xad,0xd8,0x6b,0x35,0x8c,0xe4,0x00,0x20,0x2e]
cipher = [0x14, 0x27, 0x70, 0x6B, 0x9E, 0x94, 0xF9, 0x51, 0x8E, 0x9F, 0xB2, 0x51, 0x69, 0x73, 0x2F, 0x46, 0x53, 0x5, 0xDC, 0x36, 0x69, 0xA4, 0x3, 0x86, 0x4B, 0xC4, 0xC6, 0xD5, 0xE6, 0x5F, 0x50, 0x37, 0xF7, 0x5C, 0xC6, 0x86, 0xF9, 0xA5, 0xCA, 0x74, 0x24, 0xCC, 0xBA, 0x7E, 0x64, 0x7E]
print(f"cipher length: {len(cipher)}\nkey_stream length: {len(key_stream)}")
list = []
for i in range(46):
list.append(cipher[i] ^ key_stream[i])
print(len(list))
for i in range(46):
print(chr(list[i]^i), end="")
DASCTF{lsTzx-c5c21iVA-goojqNS-ynFOPRx-489itUh}最后还是遗憾了,只拿到了二等奖,要是能解出这道就拿一等了...- 还是要仔细研究一下VM是怎么实现的,我对里面的一些代码的写法都不熟悉,都不知道它是干嘛的...