arm指令学习

1. arm汇编基础

1. LDMIA R0 , {R1,R2,R3,R4}

LDM为: 多寄存器”内存取”指令
IA表示每次LDM指令结束之后R0增加1个字
最终结果为R1 = [R0], R1 = [R0+#4], R1 = [R0+#8], R1 = [R0+#0xC]

2. 堆栈寻址 (FA、EA、FD、ED)

STMFD SP! , {R1-R7,LR} @ 将R1R7以及LR入栈
LDMFD SP! , {R1-R7,LR} @ 将R1
R7以及LR出栈

3. 块拷贝寻址

LDM和STM为指令前缀,表示多寄存器寻址,指令后缀(IA、DA、IB、DB)。
LDMIA R0!, {R1-R3} @从R0所指向的内存地址依次取出3个字到R1、R2、R3寄存器
STMIA R0!, {R1-R3} @将R1、R2、R3所存储的内容依次存放在R0所指向的内存。

4. 相对寻址

以当前程序计数器PC的当前值为基地址,将标号标记位置为偏移量,两者相加得
到有效地址。

BL NEXT
...
NEXT:
...

2. 指令集

1. 由于arm芯片更新很快,所以指令集很多,使用较为普遍的就是arm指令集以及Thumb指令集。





2.跳转指令

arm实现了两种跳转类型,一种是直接使用跳转指令,另外一种则是给PC寄存器直接赋值。

1. B跳转指令

结构 B{cond} label    
直接跳走,如`BNE LABEL`

2. BL跳转指令

结构 BL{cond} label    
执行BL指令时,若条件满足,则首先将当前指令的下一条指令的地址赋值给R14寄存器(LR),然
后跳转到label标记的地址处继续执行。一般用在过程调用中,过程结束之后通过`MOV PC, LR`返回

3. BX带状态切换的跳转指令

结构 BX{cond}Rm   
当执行BX指令时,如果条件满足,会判断Rm寄存器的位[0]是否为1,如果是1则会在跳转时自动将CPSR寄存器的T标志位置为1,并将目标位置处的指令解析为Thumb指令,相反,若Rm寄存器的位[0]为0,则将CPSR寄存器的T标志位复位,同时将目标位置的指令解析为arm指令。

如下:


ADR R0, thumbcode + 1
BX R0 @跳转到thumbcode。并且处理器运行为thumb模式
thumbcode:
.code 16

4.BLX带链接和状态切换的跳转指令

结构 BLX{cond}Rm
BLX指令集合了BL和BX的功能,在BX的功能上同时保存返回地址到R14(LR)

3.寄存器访问指令

存储器访问指令操作包括从存储区加载数据,存储数据到存储器,寄存器与存储器之间的数据交换等。

LDR

指令包括:
LDR{type}{cond}Rd,label
LDRD{cond}Rd,Rd2,label
其中type表示操作的数据大小,取值如下



LDRD依次加载双字的数据,用法如下:
LDRD R0, R1, label2@从标号labe2加载两个字到R0,R1中

STR

STR用于存储数据到制定地址。格式如下:
STR{type}{cond}Rd,label
STRD{cond}Rd,Rd2,label
用法如下:
STR R0,[R2,#04] 将R0的值存储到R2+4的地址处

LDM

LDM{addr_mode}{cond}Rn{!}reglist

特别注意, ! 为可选后缀。如果有 ! 则最终地址会写回到Rn寄存器

STM

STM将一个寄存器列表的数据存储到指定的地址单元中。格式如下

STM{addr_mod}{cond}Rn{!}reglist

PUSH&&POP

格式如下:
PUSH{cond}reglist
POP{cond}reglist
栈操作指令

PUSH {r0,r4-r7}
POP {r0,r4-r7}

SWP

寄存器之间的数据交换。
格式为SWP{B}{cond}Rd,Rm,[Rn]
B是可选的字节,若有B,则交换字节,否则交换字
Rd为临时存放的寄存器,Rm是要替换的值
Rn是要被替换的数据地址

其他逻辑、运算指令,具体还是看手册了解…

—未完待续—

android开发之通知栏消息

Demo功能,在A中点击按钮,弹出通知栏消息,点击消息之后转入B中,实现如下:
MainActivity的布局

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.cn.yllen.notificationtest.MainActivity">

<Button
android:id="@+id/button_notifiy"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Send notice"/>
</RelativeLayout>
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Intent;
import android.graphics.BitmapFactory;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.app.NotificationCompat;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity implements View.OnClickListener
{

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button = (Button) findViewById(R.id.button_notifiy);
button.setOnClickListener(this);
}

@Override
public void onClick(View v) {
switch(v.getId())
{
case R.id.button_notifiy:
Intent intent = new Intent(this,NotificationActivity.class);
//异步消息
PendingIntent pi = PendingIntent.getActivity(this,0,intent,0);
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
Notification notification = new NotificationCompat.Builder(this).setContentText("This is a Test Notice")
.setContentTitle("Notice").setWhen(System.currentTimeMillis())
.setSmallIcon(R.mipmap.ic_launcher)
.setLargeIcon(BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher))
.setContentIntent(pi) //延迟处理intent
.setAutoCancel(true) //点击后消失
.build();
manager.notify(1,notification);
break;
default:
break;
}
}
}

NotificationActivity的布局

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.cn.yllen.notificationtest.NotificationActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:textSize="24sp"
android:text="This is notification layout~~"/>

</RelativeLayout>

实现如下

import android.app.NotificationManager;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

public class NotificationActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_notification);
//NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
//notificationManager.cancel(1); //关闭id=1的消息框
}
}

京东数据泄露事件

京东数据泄露事件

0x01本次事件的报道者是名为【一本财经】的微信公众号所发布

0x02事件“主角”京东方面于11日在其运营的【京东黑板报】微信公众号进行了申明,申明如下:

0x03官方回复是否属实?

通过相关检索,获取到乌云网镜像数据,从中搜索京东关键字定位到官方提到的Struts 2漏洞,如下:


Struts 2 的漏洞提交从7-17开始到24日逐渐结束,共经历了9天,其中京东对提交的漏洞统一回复为:

本次京东 泄漏事件 造成的影响:

0x04 对于京东用户导致的影响:

  • 个人隐私信息泄漏,遭到骚扰或者勒索
  • 个人财产可能被盗用、窃取

0x05 小疑问:

  • 爆料者为何选择这么一个比较特殊的年底,非工作日,双十二前夕日子发难
  • 哪里有泄漏的数据?

Ref:
该漏洞官方介绍地址:
http://struts.apache.org/docs/s2-016.html

以下是 FreeBuf对3年前的漏洞相关文章http://www.freebuf.com/vuls/11220.html

FreeBuf对此事件的报道:
http://www.freebuf.com/news/122524.html

android_ptrace_inject

##0x1 前文
注入,Windows平台下一个极其有意思的一门技术,作为绝大多数Hook方案的前奏,当然是各种姿势都有,限于本人能力之有限,会逐渐记录移动端安全相关的学习过程,也希望看到本篇的朋友,能学到一些东西。废话不说了,走起!(以下代码可能存在高度风险性,请勿用于非法用途,一切使用中造成的任何后果,均与本人以及所参考的文章作者无关,继续阅读默认你同意且遵守本括号中的描述)
##0x2 ptrace
ptrace是linux系统下用于调试进程用的,具体描述如下:

AME
ptrace - process trace
SYNOPSIS
#include <sys/ptrace.h>
long ptrace(enum __ptrace_request request, pid_t pid,
void *addr, void *data);
DESCRIPTION
The ptrace() system call provides a means by which one process (the "tracer") may observe and control the execution of another process (the "tracee"), and examine and change the tracee's memory and registers. It is primarily used to implement breakpoint debugging and system call tracing.

重点在于第一个参数,可取的值如下

PTRACE_ME

PTRACE_PEEKTEXT

PTRACE_PEEKDATA

PTRACE_PEEKUSER

PTRACE_POKETEXT

PTRACE_POKEDATA

PTRACE_POKEUSER

PTRACE_GETREGS

PTRACE_GETFPREGS,

PTRACE_SETREGS

PTRACE_SETFPREGS

PTRACE_CONT

PTRACE_SYSCALL,

PTRACE_SINGLESTEP

PTRACE_DETACH

0x3 ptrace注入原理

ptrace注入的原理就是,首先attach到指定进程,这里需要给一个进程的pid,然后通过修改 IP ,指向一段load so的代码,之后获取要执行的函数地址,然后转入执行。大致有两种方式实现:一种是调用dlopen\dlsym然后执行到我们的so模块内,第二种是直接注入一段shellcode。
类似于 Windows 下的debug模式进程的注入 ,具体可以参考 http://blog.csdn.net/wowolook/article/details/10055329

0x4 测试截图

首先将编译好的inject 程序拷贝到 /data目录下(android虚拟机或者实体机,需要root权限) ,之后使用 chmod 777 inject,然后输入 ./inject回车

注入成功!

日志

需要注意本代码中注入的进程是 com.example.yllen.myapplication,所以当前进程列表中是要存在这个的,实际测试中可以替换为你需要注入的进程名称,或者可以稍微改改支持命令行输入进程名称的,想必聪明的你肯定分分钟搞定。

##0x5 注入流程

1. ptrace_attach(pid) 附加到进程
2. ptrace_getregs(pid,&CurrentRegs) 保存主线程上下文
3. GetRemoteFuncAddr(pid,libc_path,(void*)mmap); 获取mmap的地址,用来分配一段内存,主要存放so路径、要执行的远程函数名称
4.设置mmap的参数,然后调用
5.获取 dlopen、dlsym、dlclose、dlerror函数的地址
// 分别获取dlopen、dlsym、dlclose等函数的地址
dlopen_addr = GetRemoteFuncAddr(pid, linker_path, (void *)dlopen);
dlsym_addr = GetRemoteFuncAddr(pid, linker_path, (void *)dlsym);
dlclose_addr = GetRemoteFuncAddr(pid, linker_path, (void *)dlclose);
dlerror_addr = GetRemoteFuncAddr(pid, linker_path, (void *)dlerror);
6. ptrace_writedata(pid,RemoteMapMemoryAddr,LibPath,strlen(LibPath)+1) 写入要注入的so文件路径
7.设置dlopen函数的参数,然后调用,读取返回值(r0)得到起始地址
8.ptrace_writedata(pid, RemoteMapMemoryAddr + strlen(LibPath) + 2, FunctionName, strlen(FunctionName) + 1) 吸入要执行的函数地址,然后调用dlsym,通过读取返回值(r0)得到函数地址
9.传递参数(如果有),调用函数
10. 通过上述步骤,使得自己的函数在指定进程内执行,之后调用ptrace_detach断开进程连接

注:上文所述的ptrace_xx函数都是封装好的ptrace功能函数

##0x6 源代码

下面就是源码了。
源文件由4个部分组成,分别为inject.h 、inject.c、main.c、PrintLog.h构成

  1. inject.h 、inject.c 注入功能
  2. main.c 发起注入
  3. PrintLog.h 打印日志的封装

由于本人对arm汇编并不熟练,所以shellcode版的代码就不贴了,等日后逐渐熟练会一一进行分析。

以下代码为dlopen/dlsym 版ptrace 注入,至于如何防御,等以后有机会再补充。

先是简单的 PrintLog.h

 /*PrintLog.h*/
#ifndef _ANDROID_LOG_PRINT_H_
#define _ANDROID_LOG_PRINT_H_

#include <android/log.h>

#define IS_DEBUG

#ifdef IS_DEBUG

#define LOG_TAG ("INJECT")

#define LOGV(...) ((void)__android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__))

#define LOGD(...) ((void)__android_log_print(ANDROID_LOG_DEBUG , LOG_TAG, __VA_ARGS__))

#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO , LOG_TAG, __VA_ARGS__))

#define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN , LOG_TAG, __VA_ARGS__))

#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR , LOG_TAG, __VA_ARGS__))

#else

#define LOGV(LOG_TAG, ...) NULL

#define LOGD(LOG_TAG, ...) NULL

#define LOGI(LOG_TAG, ...) NULL

#define LOGW(LOG_TAG, ...) NULL

#define LOGE(LOG_TAG, ...) NULL

#endif

#endif

接下来是 inject.h

/*inject.h */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define MAX_PATH 0x100
// dlopen dlsym 注入
int inject_remote_process(pid_t pid,char* LibPath,char* FunctionName,long* FuncParameter ,long NumParameter);
/*inject.c*/
#include <stdio.h>
#include <stdlib.h>
#include <asm/user.h>
#include <asm/ptrace.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <dlfcn.h>
#include <dirent.h>
#include <unistd.h>
#include <string.h>
#include <elf.h>
#include <utils/PrintLog.h>
#include <inject.h>


#define CPSR_T_MASK ( 1u << 5 )
const char *libc_path = "/system/lib/libc.so";
const char *linker_path = "/system/bin/linker";

/*************************************************
Description: ptrace使远程进程继续运行
Input: pid表示远程进程的ID
Output: 无
Return: 返回0表示continue成功,返回-1表示失败
Others: 无
*************************************************/
int ptrace_continue(pid_t pid)
{
if (ptrace(PTRACE_CONT, pid, NULL, NULL) < 0)
{
LOGD("ptrace cont error, pid:%d", pid);
return -1;
}
return 0;
}

/*************************************************
Description: 使用ptrace Attach到指定进程
Input: pid表示远程进程的ID
Output: 无
Return: 返回0表示attach成功,返回-1表示失败
Others: 无
*************************************************/
int ptrace_attach(pid_t pid)
{
int status = 0;

if (ptrace(PTRACE_ATTACH, pid, NULL, 0) < 0) {
LOGD("attach process error, pid:%d", pid);
return -1;
}

LOGD("attach process pid:%d", pid);
waitpid(pid, &status , WUNTRACED);

return 0;
}

/*************************************************
Description: 使用ptrace detach指定进程
Input: pid表示远程进程的ID
Output: 无
Return: 返回0表示detach成功,返回-1表示失败
Others: 无
*************************************************/
int ptrace_detach(pid_t pid)
{
if (ptrace(PTRACE_DETACH, pid, NULL, 0) < 0) {
LOGD("detach process error, pid:%d", pid);
return -1;
}

LOGD("detach process pid:%d", pid);
return 0;
}

/*************************************************
Description: 使用ptrace获取远程进程的寄存器值
Input: pid表示远程进程的ID,regs为pt_regs结构,存储了寄存器值
Output: 无
Return: 返回0表示获取寄存器成功,返回-1表示失败
Others: 无
*************************************************/
int ptrace_getregs(pid_t pid, struct pt_regs *regs)
{
if (ptrace(PTRACE_GETREGS, pid, NULL, regs) < 0)
{
LOGD("Get Regs error, pid:%d", pid);
return -1;
}

return 0;
}

/*************************************************
Description: 使用ptrace设置远程进程的寄存器值
Input: pid表示远程进程的ID,regs为pt_regs结构,存储需要修改的寄存器值
Output: 无
Return: 返回0表示设置寄存器成功,返回-1表示失败
Others: 无
*************************************************/
int ptrace_setregs(pid_t pid, struct pt_regs *regs)
{
if (ptrace(PTRACE_SETREGS, pid, NULL, regs) < 0)
{
LOGD("Set Regs error, pid:%d", pid);
return -1;
}

return 0;
}

/*************************************************
Description: 获取返回值,ARM处理器中返回值存放在ARM_r0寄存器中
Input: regs存储远程进程当前的寄存器值
Output: 无
Return: 在ARM处理器下返回r0寄存器值
Others: 无
*************************************************/
long ptrace_getret(struct pt_regs * regs)
{
return regs->ARM_r0;
}

/*************************************************
Description: 获取当前执行代码的地址,ARM处理器下存放在ARM_pc中
Input: regs存储远程进程当前的寄存器值
Output: 无
Return: 在ARM处理器下返回pc寄存器值
Others: 无
*************************************************/
long ptrace_getpc(struct pt_regs * regs)
{
return regs->ARM_pc;
}

/*************************************************
Description: 使用ptrace从远程进程内存中读取数据
Input: pid表示远程进程的ID,pSrcBuf表示从远程进程读取数据的内存地址
pDestBuf表示用于存储读取出数据的地址,size表示读取数据的大小
Output: 无
Return: 返回0表示读取数据成功
Others: 无
*************************************************/
int ptrace_readdata(pid_t pid, uint8_t *pSrcBuf, uint8_t *pDestBuf, uint32_t size)
{
uint32_t nReadCount = 0;
uint32_t nRemainCount = 0;
uint8_t *pCurSrcBuf = pSrcBuf;
uint8_t *pCurDestBuf = pDestBuf;
long lTmpBuf = 0;
uint32_t i = 0;

nReadCount = size / sizeof(long);
nRemainCount = size % sizeof(long);

for (i = 0; i < nReadCount; i ++ )
{
lTmpBuf = ptrace(PTRACE_PEEKTEXT, pid, pCurSrcBuf, 0);
memcpy(pCurDestBuf, (char *)(&lTmpBuf), sizeof(long));
pCurSrcBuf += sizeof(long);
pCurDestBuf += sizeof(long);
}

if ( nRemainCount > 0 )
{
lTmpBuf = ptrace(PTRACE_PEEKTEXT, pid, pCurSrcBuf, 0);
memcpy(pCurDestBuf, (char *)(&lTmpBuf), nRemainCount);
}

return 0;
}

//
/*************************************************
Description: 使用ptrace将数据写入到远程进程空间中
Input: pid表示远程进程的ID,pWriteAddr表示写入数据到远程进程的内存地址
pWriteData用于存储写入数据的地址,size表示写入数据的大小
Output: 无
Return: 返回0表示写入数据成功,返回-1表示写入数据失败
Others: 无
*************************************************/
int ptrace_writedata(pid_t pid, uint8_t *pWriteAddr, uint8_t *pWriteData, uint32_t size)
{
uint32_t nWriteCount = 0;
uint32_t nRemainCount = 0;
uint8_t *pCurSrcBuf = pWriteData;
uint8_t *pCurDestBuf = pWriteAddr;
long lTmpBuf = 0;
uint32_t i = 0;

nWriteCount = size / sizeof(long);
nRemainCount = size % sizeof(long);

// 先讲数据以sizeof(long)字节大小为单位写入到远程进程内存空间中
for (i = 0; i < nWriteCount; i ++)
{
memcpy((void *)(&lTmpBuf), pCurSrcBuf, sizeof(long));
if (ptrace(PTRACE_POKETEXT, pid, pCurDestBuf, lTmpBuf) < 0) // PTRACE_POKETEXT表示从远程内存空间写入一个sizeof(long)大小的数据
{
LOGD("Write Remote Memory error, MemoryAddr:0x%lx", (long)pCurDestBuf);
return -1;
}
pCurSrcBuf += sizeof(long);
pCurDestBuf += sizeof(long);
}

// 将剩下的数据写入到远程进程内存空间中
if (nRemainCount > 0)
{
lTmpBuf = ptrace(PTRACE_PEEKTEXT, pid, pCurDestBuf, NULL); //先取出原内存中的数据,然后将要写入的数据以单字节形式填充到低字节处
memcpy((void *)(&lTmpBuf), pCurSrcBuf, nRemainCount);
if (ptrace(PTRACE_POKETEXT, pid, pCurDestBuf, lTmpBuf) < 0)
{
LOGD("Write Remote Memory error, MemoryAddr:0x%lx", (long)pCurDestBuf);
return -1;
}
}

return 0;
}

/*************************************************
Description: 使用ptrace远程call函数
Input: pid表示远程进程的ID,ExecuteAddr为远程进程函数的地址
parameters为函数参数的地址,regs为远程进程call函数前的寄存器环境
Output: 无
Return: 返回0表示call函数成功,返回-1表示失败
Others: 无
*************************************************/
int ptrace_call(pid_t pid, uint32_t ExecuteAddr, long *parameters, long num_params, struct pt_regs* regs)
{
int i = 0;
// ARM处理器,函数传递参数,将前四个参数放到r0-r3,剩下的参数压入栈中
for (i = 0; i < num_params && i < 4; i ++) {
regs->uregs[i] = parameters[i];
}

if (i < num_params) {
regs->ARM_sp -= (num_params - i) * sizeof(long) ; // 分配栈空间,栈的方向是从高地址到低地址
if (ptrace_writedata(pid, (void *)regs->ARM_sp, (uint8_t *)&parameters[i], (num_params - i) * sizeof(long)) == -1)
return -1;
}

regs->ARM_pc = ExecuteAddr; //设置ARM_pc寄存器为需要调用的函数地址
// 与BX跳转指令类似,判断跳转的地址位[0]是否为1,如果为1,则将CPST寄存器的标志T置位,解释为Thumb代码
// 若为0,则将CPSR寄存器的标志T复位,解释为ARM代码
if (regs->ARM_pc & 1) {
/* thumb */
regs->ARM_pc &= (~1u);
regs->ARM_cpsr |= CPSR_T_MASK;
} else {
/* arm */
regs->ARM_cpsr &= ~CPSR_T_MASK;
}

regs->ARM_lr = 0;

if (ptrace_setregs(pid, regs) == -1 || ptrace_continue(pid) == -1) {
LOGD("ptrace set regs or continue error, pid:%d", pid);
return -1;
}

int stat = 0;
// 对于使用ptrace_cont运行的子进程,它会在3种情况下进入暂停状态:①下一次系统调用;②子进程退出;③子进程的执行发生错误。
// 参数WUNTRACED表示当进程进入暂停状态后,立即返回
// 将ARM_lr(存放返回地址)设置为0,会导致子进程执行发生错误,则子进程进入暂停状态
waitpid(pid, &stat, WUNTRACED);

// 判断是否成功执行函数
LOGD("ptrace call ret status is %d\n", stat);
while (stat != 0xb7f) {
if (ptrace_continue(pid) == -1) {
LOGD("ptrace call error");
return -1;
}
waitpid(pid, &stat, WUNTRACED);
}

// 获取远程进程的寄存器值,方便获取返回值
if (ptrace_getregs(pid, regs) == -1)
{
LOGD("After call getregs error");
return -1;
}

return 0;
}

/*************************************************
Description: 在指定进程中搜索对应模块的基址
Input: pid表示远程进程的ID,若为-1表示自身进程,ModuleName表示要搜索的模块的名称
Output: 无
Return: 返回0表示获取模块基址失败,返回非0为要搜索的模块基址
Others: 无
*************************************************/
void* GetModuleBaseAddr(pid_t pid, const char* ModuleName)
{
FILE *fp = NULL;
long ModuleBaseAddr = 0;
char *ModulePath, *MapFileLineItem;
char szFileName[50] = {0};
char szMapFileLine[1024] = {0};
char szProcessInfo[1024] = {0};

// 读取"/proc/pid/maps"可以获得该进程加载的模块
if (pid < 0) {
// 枚举自身进程模块
snprintf(szFileName, sizeof(szFileName), "/proc/self/maps");
} else {
snprintf(szFileName, sizeof(szFileName), "/proc/%d/maps", pid);
}

fp = fopen(szFileName, "r");

if (fp != NULL)
{
while (fgets(szMapFileLine, sizeof(szMapFileLine), fp)) {
if (strstr(szMapFileLine, ModuleName))
{
MapFileLineItem = strtok(szMapFileLine, " \t"); // 基址信息
char *Addr = strtok(szMapFileLine, "-");
ModuleBaseAddr = strtoul(Addr, NULL, 16 );

if (ModuleBaseAddr == 0x8000)
ModuleBaseAddr = 0;

break;
}
}

fclose(fp) ;
}

return (void *)ModuleBaseAddr;
}

/*************************************************
Description: 获取远程进程与本进程都加载的模块中函数的地址
Input: pid表示远程进程的ID,ModuleName表示模块名称,LocalFuncAddr表示本地进程中该函数的地址
Output: 无
Return: 返回远程进程中对应函数的地址
Others: 无
*************************************************/
void* GetRemoteFuncAddr(pid_t pid, const char *ModuleName, void *LocalFuncAddr)
{
void *LocalModuleAddr, *RemoteModuleAddr, *RemoteFuncAddr;

LocalModuleAddr = GetModuleBaseAddr(-1, ModuleName);
RemoteModuleAddr = GetModuleBaseAddr(pid, ModuleName);

RemoteFuncAddr = (void *)((long)LocalFuncAddr - (long)LocalModuleAddr + (long)RemoteModuleAddr);

return RemoteFuncAddr;
}

/*************************************************
Description: 通过远程直接调用dlopen\dlsym的方法ptrace注入so模块到远程进程中
Input: pid表示远程进程的ID,LibPath为被远程注入的so模块路径,FunctionName为远程注入的模块后调用的函数
FuncParameter指向被远程调用函数的参数(若传递字符串,需要先将字符串写入到远程进程空间中),NumParameter为参数的个数
Output: 无
Return: 返回0表示注入成功,返回-1表示失败
Others: 无
*************************************************/

int inject_remote_process(pid_t pid,char* LibPath,char* FunctionName,long* FuncParameter ,long NumParameter)
{
int iRet = -1;
struct pt_regs CurrentRegs,OriginalRegs; //备份原始寄存器
void *mmap_addr,*dlopen_addr,*dlsym_addr,*dlclose_addr,*dlerror_addr;
void *RemoteMapMemoryAddr, *RemoteModuleAddr, *RemoteModuleFuncAddr; //远程进程内存映射地址(保存调用参数),模块地址, 远程函数地址

long parameters[6];

//1. attach到进程
if(ptrace_attach(pid) == -1)
return iRet;

//2. 保存当前环境
if(ptrace_getregs(pid,&CurrentRegs) == -1)
{
ptrace_detach(pid);
return iRet;
}
memcpy(&OriginalRegs,&CurrentRegs,sizeof(struct pt_regs));

//3.获取远程mmap函数地址
//map() creates a new mapping in the virtual address space of the calling process.
//void *mmap(void *addr, size_t length, int prot, int flags,
// int fd, off_t offset);

mmap_addr = GetRemoteFuncAddr(pid,libc_path,(void*)mmap);
LOGD("[+]mmap RemoteFuncAddr:0x%lx", (long)mmap_addr);

//4. 设置mmap函数参数

parameters[0] = 0;
parameters[1] = 0x1000;
parameters[2] = PROT_READ | PROT_WRITE | PROT_EXEC; //内存属性
parameters[3] = MAP_ANONYMOUS | MAP_PRIVATE; //匿名映射
parameters[4] = 0;
parameters[5] = 0;

if(ptrace_call(pid , (long)mmap_addr , parameters , 6 , &CurrentRegs))
{
LOGD("Call Remote mmap Func Failed");
ptrace_detach(pid);
return iRet;
}

RemoteMapMemoryAddr = (void*)ptrace_getret(&CurrentRegs); //获取分配内存的起始地址
LOGD("[+]Remote Process Map Memory Addr:0x%lx", (long)RemoteMapMemoryAddr);

// 分别获取dlopen、dlsym、dlclose等函数的地址
dlopen_addr = GetRemoteFuncAddr(pid, linker_path, (void *)dlopen);
dlsym_addr = GetRemoteFuncAddr(pid, linker_path, (void *)dlsym);
dlclose_addr = GetRemoteFuncAddr(pid, linker_path, (void *)dlclose);
dlerror_addr = GetRemoteFuncAddr(pid, linker_path, (void *)dlerror);


LOGD("[+]dlopen RemoteFuncAddr:0x%lx", (long)dlopen_addr);
LOGD("[+]dlsym RemoteFuncAddr:0x%lx", (long)dlsym_addr);
LOGD("[+]dlclose RemoteFuncAddr:0x%lx", (long)dlclose_addr);
LOGD("[+]dlerror RemoteFuncAddr:0x%lx", (long)dlerror_addr);

if(ptrace_writedata(pid,RemoteMapMemoryAddr,LibPath,strlen(LibPath)+1) == -1)
{
LOGD("Write LibPath:%s to RemoteProcess error", LibPath);
ptrace_detach(pid);
return iRet;
}

// loadlibrary
// 设置dlopen的参数,返回值为模块加载的地址
// void *dlopen(const char *filename, int flag);
parameters[0] = (long)RemoteMapMemoryAddr;
parameters[1] = RTLD_NOW| RTLD_GLOBAL;

if (ptrace_call(pid, (long)dlopen_addr, parameters, 2, &CurrentRegs) == -1)
{
LOGD("Call Remote dlopen Func Failed");
ptrace_detach(pid);
return iRet;
}

// RemoteModuleAddr为远程进程加载注入模块的地址
RemoteModuleAddr = (void *)ptrace_getret(&CurrentRegs);
LOGD("Remote Process load module Addr:0x%lx", (long)RemoteModuleAddr);

if ((long)RemoteModuleAddr == 0x0) // dlopen 错误
{
LOGD("dlopen error");
if (ptrace_call(pid, (long)dlerror_addr, parameters, 0, &CurrentRegs) == -1)
{
LOGD("Call Remote dlerror Func Failed");
ptrace_detach(pid);
return iRet;
}
char *Error = (void *)ptrace_getret(&CurrentRegs);
char LocalErrorInfo[1024] = {0};
ptrace_readdata(pid, Error, LocalErrorInfo, 1024);
LOGD("dlopen error:%s", LocalErrorInfo);
ptrace_detach(pid);
return iRet;
}

// 将so库中需要调用的函数名称写入到远程进程内存空间中
if (ptrace_writedata(pid, RemoteMapMemoryAddr + strlen(LibPath) + 2, FunctionName, strlen(FunctionName) + 1) == -1)
{
LOGD("Write FunctionName:%s to RemoteProcess error", FunctionName);
ptrace_detach(pid);
return iRet;
}
//GetProcAddress
// 设置dlsym的参数,返回值为远程进程内函数的地址
// void *dlsym(void *handle, const char *symbol);
parameters[0] = (long)RemoteModuleAddr;
parameters[1] = (long)(RemoteMapMemoryAddr + strlen(LibPath) + 2);

if (ptrace_call(pid, (long)dlsym_addr, parameters, 2, &CurrentRegs) == -1)
{
LOGD("Call Remote dlsym Func Failed");
ptrace_detach(pid);
return iRet;
}

// RemoteModuleFuncAddr为远程进程空间内获取的函数地址
RemoteModuleFuncAddr = (void *)ptrace_getret(&CurrentRegs);
LOGD("[+]Remote Process ModuleFunc Addr:0x%lx", (long)RemoteModuleFuncAddr);

// call
if (ptrace_call(pid, (long)RemoteModuleFuncAddr, FuncParameter, NumParameter, &CurrentRegs) == -1)
{
LOGD("Call Remote injected Func Failed");
ptrace_detach(pid);
return iRet;
}

if (ptrace_setregs(pid, &OriginalRegs) == -1)
{
LOGD("Recover reges failed");
ptrace_detach(pid);
return iRet;
}
LOGD("Recover Regs Success");
ptrace_getregs(pid, &CurrentRegs);
if (memcmp(&OriginalRegs, &CurrentRegs, sizeof(CurrentRegs)) != 0)
{
LOGD("Set Regs Error");
}

//Detach
if (ptrace_detach(pid) == -1)
{
LOGD("ptrace detach failed");
return iRet;
}

return 0;

}
/************************************************************
FileName: main.c
Description: ptrace注入
***********************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <asm/user.h>
#include <asm/ptrace.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <dlfcn.h>
#include <dirent.h>
#include <unistd.h>
#include <string.h>
#include <elf.h>
#include <inject.h>
#include <utils/PrintLog.h>


/*************************************************
Description: 通过进程名称定位到进程的PID
Input: process_name为要定位的进程名称
Output: 无
Return: 返回定位到的进程PID,若为-1,表示定位失败
Others: 无
*************************************************/
pid_t FindPidByProcessName(const char *process_name)
{
int ProcessDirID = 0;
pid_t pid = -1;
FILE *fp = NULL;
char filename[MAX_PATH] = {0};
char cmdline[MAX_PATH] = {0};

struct dirent * entry = NULL;

if ( process_name == NULL )
return -1;

DIR* dir = opendir( "/proc" );
if ( dir == NULL )
return -1;

while( (entry = readdir(dir)) != NULL )
{
ProcessDirID = atoi( entry->d_name );
if ( ProcessDirID != 0 )
{
snprintf(filename, MAX_PATH, "/proc/%d/cmdline", ProcessDirID);
fp = fopen( filename, "r" );
if ( fp )
{
fgets(cmdline, sizeof(cmdline), fp);
fclose(fp);

if (strncmp(process_name, cmdline, strlen(process_name)) == 0)
{
pid = ProcessDirID;
break;
}
}
}
}

closedir(dir);
return pid;
}

int main(int argc, char *argv[]) {
char InjectModuleName[MAX_PATH] = "/data/libInjectModule.so"; // 要注入模块全路径
char RemoteCallFunc[MAX_PATH] = "Inject_entry"; // 注入模块后调用模块函数名称
char InjectProcessName[MAX_PATH] = "com.example.yllen.myapplication"; // 注入进程名称

// 当前设备环境判断
#if defined(__i386__)
LOGD("Current Environment x86");
return -1;
#elif defined(__arm__)
LOGD("Current Environment ARM");
#else
LOGD("other Environment");
return -1;
#endif

pid_t pid = FindPidByProcessName(InjectProcessName);
if (pid == -1)
{
printf("Get Pid Failed");
return;
}

printf("begin inject process, RemoteProcess pid:%d, InjectModuleName:%s, RemoteCallFunc:%s\n", pid, InjectModuleName, RemoteCallFunc);
int iRet = inject_remote_process(pid, InjectModuleName, RemoteCallFunc, NULL, 0);
//int iRet = inject_remote_process_shellcode(pid, InjectModuleName, RemoteCallFunc, NULL, 0);

if (iRet == 0)
{
printf("Inject Success\n");
}
else
{
printf("Inject Failed\n");
}
printf("end inject,%d\n", pid);
return 0;
}

Android.mk

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
LOCAL_MODULE := inject
LOCAL_SRC_FILES := inject.c main.c

LOCAL_LDLIBS += -L$(SYSROOT)/usr/lib -llog

include $(BUILD_EXECUTABLE)

参考Ref:
1. http://gslab.qq.com/portal.php?mod=view&aid=162

Elf文件格式学习

背(che)景(dan)

又到了每年的中秋节了,话说从大学开始,对于节日的回忆基本都停留在了学校中,尤其是大三开始,每逢节日都会窝在宿舍中学习一些东西,大三的清明节了解了下缓冲区溢出,大四的清明节,在准备面试,也在学一些东西,貌似是Windows内核啥的。ok,时间转到了大学最后一个学期,清明节忙着写毕设,Wow,终于毕业了,今年中秋搞点啥呢…Elf文件格式。

为什么学(了)习(解)这个呢?主要是渐渐觉得要去搞移动端安全相关了,所以该有些准备知识了,话说我java也不是菜鸡(虽然N久都没看,没用了),但那又怎样,所以路线大致是Elf->Arm汇编->Dex(smail)->Apk->java(native)->android(hook、anti&prot)->android(PWN!),这个路线有些扯淡,但目前就这么定了,后面再做的调整,2333…

正(yan)文(su)

先说下文件对象:

  1. 可重定位的对象文件(Relocatable file)
    类似于编译器生产的中间文件,在链接时使用。
  2. 可执行的对象文件(Executable file)
    可以运行的文件,比如linux下的ls、vi编辑器等
  3. 可被共享的对象文件(Shared object file)
    应用程序可以将公共函数放在一个文件中(.so),这样在运行时动态导入,节省了很多磁盘空间

Elf文件格式提供了两种视图,一种是链接(文件)视图,一种是运行(内存)视图

上图左侧为链接视图,右侧为运行视图,其中主要区别在section(segment)上,以Android NDK中的helloWord jni程序编译的.so文件为例,使用readelf命令解析一下:
使用readelf -S 解析sections

可以看到文件有19个section(段或者节),其中.shstrtab是文件的字符串段,后面出现的关于字符串的字段的值都是在这个字段中的下标值。
使用readelf --segments解析运行视图


现在段变成了6个,可以观察到这些段都是前面section中的项,也就是说elf文件在运行时有些相同属性的段被放在同一个段中。

为什么要这么做呢?

原因是目前的os大多都是以页为单位管理内存的,在linux中,典型的页大小的为4096b=4kb,所以即使一个大小不够4kb的数据,都必须分配一个4kb大小的内存,这样对于超多section的视图来说就是在浪费空间,所以linux将相同flg值以及相关属性的section放在一个segment中,方便权限管理,这样内存的浪费也就减小下来,所以从这里可以看出,segment是section的一个子集。

Elf组织结构

Elf 文件的大体结构

  1. Elf header 描述整个文件的组织
  2. Elf program_header_table 描述文件的各种segment,用来告诉系/统如何创建进程映像(一个段 包含 1个或多个section)
  3. Elf section_header_table 包含文件的各个section的信息

类型

typedef	DWORD	Elf32_Addr;   
typedef WORD Elf32_Half;
typedef DWORD Elf32_Off;
typedef DWORD Elf32_Sword;
typedef DWORD Elf32_Word;

Elf header

typedef struct
{
unsigned char e_ident[EI_NIDENT];
Elf32_Half e_type;
// 该成员确定该object的类型。
// Name Value Meaning
// ==== ===== =======
// ET_NONE 0 No file type
// ET_REL 1 Relocatable file
// ET_EXEC 2 Executable file
// ET_DYN 3 Shared object file
// ET_CORE 4 Core file
// ET_LOPROC 0xff00 Processor-specific
// ET_HIPROC 0xffff Processor-specific

Elf32_Half e_machine;
// 该成员变量指出了运行该程序需要的体系结构。
// Name Value Meaning
// ==== ===== =======
// EM_NONE 0 No machine
// EM_M32 1 AT&T WE 32100
// EM_SPARC 2 SPARC
// EM_386 3 Intel 80386
// EM_68K 4 Motorola 68000
// EM_88K 5 Motorola 88000
// EM_860 7 Intel 80860
// EM_MIPS 8 MIPS RS3000

Elf32_Word e_version;
// 这个成员确定object文件的版本。
//
// Name Value Meaning
// ==== ===== =======
// EV_NONE 0 Invalid version
// EV_CURRENT 1 Current version
Elf32_Addr e_entry;
// OEP;
// 假如文件没有如何关联的入口点,该成员就保持为 0。
Elf32_Off e_phoff;
// 该成员保持着程序头表(program header table)在文件中的偏移量(以字节计数)。 same as NT HEADER
// 假如该文件没有程序头表的的话,该成员就保持为 0。
Elf32_Off e_shoff;
// 该成员保持着程序头表(section header table)在文件中的偏移量(以字节计数)。 same as NT HEADER
// 假如该文件没有程序头表的的话,该成员就保持为 0。
Elf32_Word e_flags;
// 该成员保存着相关文件的特定处理器标志。
// flag的名字来自于EF_<machine>_<flag>。看下机器信息“Machine Information”部分的flag的定义。
Elf32_Half e_ehsize;
//该成员保存着ELF头大小(以字节计数)。
Elf32_Half e_phentsize;
// 该成员保存着在文件的程序头表(program header table)中一个入口的大小
// (以字节计数)。所有的入口都是同样的大小。
Elf32_Half e_e_phnum;
// 该成员保存着在程序头表中入口的个数。
// 因此,e_phentsize和e_phnum的乘机就是表的大小(以字节计数).
// 假如没有程序头表(program header table),e_phnum变量为0。
Elf32_Half e_shentsize;
// 该成员保存着section头的大小(以字节计数)。
// 一个section头是在section头表(section header table)的一个入口;
// 所有的入口都是同样的大小。
Elf32_Half e_shnum;
// 该成员保存着在section header table中的入口数目。
// 因此,e_shentsize和e_shnum的乘积就是section头表的大小(以字节计数)。
// 假如文件没有section头表,e_shnum值为0。
Elf32_Half e_shstrndx;
// 该成员保存着跟section名字字符表相关入口的section头表(section header table)索引。
// 假如文件中没有section名字字符表,该变量值为SHN_UNDEF。
// 更详细的信息 看下面“Sections”和字符串表(“String Table”) 。

}Elf32_Ehdr;


program header


typedef struct
{
Elf32_Word p_type;
// Name Value
// ==== =====
// PT_NULL 0
// PT_LOAD 1
// PT_DYNAMIC 2
// PT_INTERP 3
// PT_NOTE 4
// PT_SHLIB 5
// PT_PHDR 6
// PT_LOPROC 0x70000000
// PT_HIPROC 0x7fffffff
Elf32_Off p_offset;
// 该成员给出了该段的驻留位置相对于文件开始处的偏移。 offset file
Elf32_Off p_vadrr;
// 该成员给出了该段在内存中的首字节地址。 rva
Elf32_Addr p_paddr;

Elf32_Word p_filesz;
// 文件映像中该段的字节数;它可能是 0 。
Elf32_Word p_memsz;
// 内存映像中该段的字节数;它可能是 0 。
Elf32_Word p_flags;
Elf32_Word p_align;
// 该成员给出了该段在内存和文件中排列值。
// 0 和 1 表示不需要排列。否则, p_align 必须为正的 2 的幂,
// 并且 p_vaddr 应当等于 p_offset 模 p_align 。
}Elf32_Phdr;

section header


typedef struct
{

Elf32_Word sh_name;
// 该成员指定了这个section的名字。
// 它的值是section报头字符表section的索引。[看以下的“String Table”], 以NULL空字符结束。
Elf32_Word sh_type;
// Section Types, sh_type
// ---------------------------
// Name Value Description
// ==== ===== ===========
// SHT_NULL 0 该值表明该section头是无效的;它没有相关的section。
// SHT_PROGBITS 1 该section保存被程序定义了的一些信息,它的格式和意义取决于程序本身。
// SHT_SYMTAB 2 该sections保存着一个符号表(symbol table)。
// SHT_STRTAB 3 该section保存着一个字符串表。
// SHT_RELA 4 该section保存着具有明确加数的重定位入口。
// SHT_HASH 5 该标号保存着一个标号的哈希(hash)表。
// SHT_DYNAMIC 6 该section保存着动态连接的信息。
// SHT_NOTE 7 该section保存着其他的一些标志文件的信息。
// SHT_NOBITS 8 该类型的section在文件中不占空间,但是类似SHT_PROGBITS。
// SHT_REL 9 该section保存着重定位的入口。
// SHT_SHLIB 10 该section类型保留但语意没有指明。包含这个类型的section的程序是不符合ABI的。
// SHT_DYNSYM 11 该sections保存着一个符号表(symbol table)。
// SHT_LOPROC 0x70000000 在这范围之间的值为特定处理器语意保留的。
// SHT_HIPROC 0x7fffffff 在这范围之间的值为特定处理器语意保留的。
// SHT_LOUSER 0x80000000 该变量为应用程序保留的索引范围的最小边界。
// SHT_HIUSER 0xffffffff 该变量为应用程序保留的索引范围的最大边界。
Elf32_Word sh_flags;
// Section Attribute Flags, sh_flags
// -----------------------------------
// Name Value Description
// ==== ===== ===========
// SHF_WRITE 0x1 该section包含了在进程执行过程中可被写的数据。
// SHF_ALLOC 0x2 该section在进程执行过程中占据着内存。
// SHF_EXECINSTR 0x4 该section包含了可执行的机器指令。
// SHF_MASKPROC 0xf0000000 所有的包括在这掩码中的位为特定处理语意保留的。
Elf32_Addr sh_addr;
// 假如该section将出现在进程的内存映象空间里,该成员给出了一个该section在内存中的位置。否则,该变量为0。
Elf32_Off sh_offset;
// 该成员变量给出了该section的字节偏移量(从文件开始计数)。
Elf32_Word sh_size;
// 该成员给你了section的字节大小。
Elf32_Word sh_link;
// 该成员保存了一个section报头表的索引连接,它的解释依靠该section的类型。
// 更多信息参见表"sh_link and sh_info Interpretation"
Elf32_Word sh_info;
// 该成员保存着额外的信息,它的解释依靠该section的类型。

// sh_link and sh_info Interpretation

// -------------------------------------------------------------------------------
// sh_type sh_link sh_info
// ======= ======= =======
// SHT_DYNAMIC The section header index of 0
// the string table used by
// entries in the section.
// -------------------------------------------------------------------------------
// SHT_HASH The section header index of 0
// the symbol table to which the
// hash table applies.
// -------------------------------------------------------------------------------
// SHT_REL The section header index of The section header index of
// SHT_RELA the associated symbol table. the section to which the
// relocation applies.
// -------------------------------------------------------------------------------
// SHT_SYMTAB The section header index of One greater than the symbol
// -------------------------------------------------------------------------------
// SHT_DYNSYM the associated string table. table index of the last local
// symbol (binding STB_LOCAL).
// -------------------------------------------------------------------------------
// other SHN_UNDEF 0
// -------------------------------------------------------------------------------
Elf32_Word sh_addralign;
// 一些sections有地址对齐的约束。
Elf32_Word sh_entsize;
// 一些sections保存着一张固定大小入口的表,就象符号表。
// 对于这样一个section来说,该成员给出了每个入口的字节大小。
// 如果该section没有保存着一张固定大小入口的表,该成员就为0。

}Elf32_Shdr;

###关键段(符号表、重定位表、GOT表)
####符号表(.dynsym)
描述了用来定位、重定位程序中所有的符号定义以及引用的信息,符号指的是经过修饰了的函数名或者变量名,修饰方法由编译器制定。
010编辑器中Elf解析模板

符号表的组织结构:

typedef struct 
{
Elf32_Word st_name; //符号表项名称。如果该值非0,则表示符号名的字
//符串表索引(offset),否则符号表项没有名称。
Elf32_Addr st_value; //符号的取值。依赖于具体的上下文,可能是一个绝对值、一个地址等等。
Elf32_Word st_size; //符号的尺寸大小。例如一个数据对象的大小是对象中包含的字节数。
unsigned char st_info; //符号的类型和绑定属性。
unsigned char st_other; //未定义。
Elf32_Half st_shndx; //每个符号表项都以和其他节区的关系的方式给出定义。
             //此成员给出相关的节区头部表索引。
} Elf32_sym;

####字符串(.dynstr)
段描述

内容


其组织形式与符号表相同
###重定位表
程序经过编辑器->编译器->链接器步骤之后,并不能直接去运行,因为很多情况之下编译器是将程序从0地址开始做为基址的,当加载到内存的基地址发生变化后,原来静态计算的变量、函数地址都发生了变化,导致程序不能继续执行,或者.so文件在被加载到一个被占用的基地址时,需要重新加载到其他空闲地址,这样也要涉及到重定位,简单来说,重定位就是将程序运行所需要的函数、变量地址都关联到实际内存地址。
重定位表的格式

typedef struct
{
Elf32_Addr r_offset; //重定位动作所适用的位置(受影响的存储单位的第一个字节的偏移或者虚拟地址)
Elf32_Word r_info; //要进行重定位的符号表索引,以及将实施的重定位类型(哪些位需要修改,以及如何计算它们的取值)
//其中 .rel.dyn 重定位类型一般为R_386_GLOB_DAT和R_386_COPY;.rel.plt为R_386_JUMP_SLOT
} Elf32_Rel;

重定位段:

未完待续……

Ref:

  1. http://blog.csdn.net/feglass/article/details/51469511

主动防御技术浅析

杀毒软件为了能实时检测到系统中任何变化,并进一步进行判断该操作是否存在危害,均采用挂钩系统关键调用方式实现该流程,该技术称为主动防御技术。

功能方面(检测和保护)有如下方面

比如监控文件变化、监控注册表变化、监控内存加载、监控进程\线程变化、实现自身进程保护、实现自身窗体保护、安全输入等。

涉及到的关键系统服务表 SSDT 、Shadow SSDT,为了更好的兼容性,一般不会直接去替换服务表中例程,而选择挂钩 KiFastCallEntry,好处有很多,至少可以自己重载一份关键系统例程,防止被在前面拦截掉。

实现时需要保证能在ring3下获取到ring0拦截到的信息,并且做出判断返回给ring0程序,继续执行下面的操作,也即保证ring3 、 ring0 程序同步。

采用方案如下:

 

1 ring3程序CreateEvent 两个事件( E1 、E2),把句柄发送给ring0

2 ring0程序调用 ObReferenceObjectByHandle 获取到事件对象

3 ring3程序创建一个线程在里面 WaitForSingleObject ( E1 )

4 ring0程序拦截到一次调用,SetEvent( E1 ),此时 ring3 程序继续执行

ring0程序 KeWaitForSingleObject( E2 )

5 ring3程序进行判断该请求,将信息发给ring0 ,并SetEvent( E2 )

6 ring0 已经有信息了,可以继续执行。
 

最后

本程序未挂钩KifastCallEntry 以及 HOOK 任何函数,只是一个小的demo,里面用到

CreateProcessNotifyFunction来实现拦截进程创建信息,后续如是要扩展,挂钩KifastCallEntry, Inline HOOK 某些API,封装事件消息,并设置一个队列来存放,依次反馈给ring3,并实现一个黑、白名单机制。
 

代码:

这里给一个简单 DEMO 关键代码:

驱动部分

.h

#include "ntddk.h"

#include <ntstrsafe.h>


#define SIOCTL_TYPE FILE_DEVICE_UNKNOWN


#define IOCTL_START \

CTL_CODE( SIOCTL_TYPE, 0x901, METHOD_BUFFERED, FILE_ANY_ACCESS )


#define IOCTL_STOP \

CTL_CODE( SIOCTL_TYPE, 0x902, METHOD_BUFFERED, FILE_ANY_ACCESS )


#define IOCTL_GET_DATA \

CTL_CODE( SIOCTL_TYPE, 0x903, METHOD_BUFFERED, FILE_ANY_ACCESS )


#define IOCTL_SET_REPLY \

CTL_CODE( SIOCTL_TYPE, 0x904, METHOD_BUFFERED, FILE_ANY_ACCESS )


#define DRIVERNAMEL "\\Device\\KernelHandle"

#define SYMBOLICNAMEL "\\DosDevices\\KernelHandle"

#define G_EVENTL "\\BaseNamedObjects\\MyEvent" /* 应用层 触发 */


typedef struct _PROCESSINFO

{
ULONGPID;

charname[16];

charPATH[256];
}PROCERSSINFO, *PPROCESSINFO;


typedef struct _HANDLEINFO

{
HANDLEarg1; /* 这个是 内核层 SET */

HANDLEarg2; /* 这个是 应用层 SET */
}HANDLEINFO, *PHANDLEINFO;


__declspec( dllimport )


NTSTATUS NTAPI

ZwTerminateProcess( IN HANDLE ProcessHandle OPTIONAL,

IN NTSTATUS ExitStatus );


NTSTATUS

PsLookupProcessByProcessId(

IN HANDLE ProcessId,

OUT PEPROCESS *Process

);

.sys


#include "zhudongfangyu.h"

BOOLEAN g_bIsNotifyRoutineSetted;

PKEVENT g_pEventObject0 = NULL; /* 内核层触发 */

PKEVENT g_pEventObject3 = NULL; /* 应用层触发 */

HANDLEg_hEvent = NULL;


BOOLEANg_bREPLY;

UNICODE_STRINGEventName;


OBJECT_HANDLE_INFORMATION g_ObjectHandleInfo;


VOID PrintIrpInfo( PIRP Irp );


VOID MyUnload( PDRIVER_OBJECT pDerverObject )


{
PDEVICE_OBJECTdevObj = pDerverObject->DeviceObject;

UNICODE_STRINGsymbolicName;

RtlInitUnicodeString( &symbolicName, SYMBOLICNAME );

IoDeleteSymbolicLink( &symbolicName );

if ( devObj != NULL )

{
IoDeleteDevice( devObj );
}

DbgPrint( "MyUnload" );
}


NTSTATUS SioctlCreateClose( PDEVICE_OBJECT DeviceObject, PIRP irp )

{
irp->IoStatus.Status = STATUS_SUCCESS;

irp->IoStatus.Information = 0;

IoCompleteRequest( irp, IO_NO_INCREMENT );

return(STATUS_SUCCESS);
}


/*
*
* PID信息
*
* CHAR g_szPIDInfo[20];
*
*/


PROCERSSINFO pi;


VOID CreateProcessNotifyFunction( IN HANDLE hParentId, IN HANDLE hProcessId, IN BOOLEAN bCreate )

{
PEPROCESSlProcess;

ANSI_STRINGExePath;

/*POBJECT_NAME_INFORMATION ExePath;*/

/* 如果是进程创建 */

if ( bCreate )

{
PsLookupProcessByProcessId( hProcessId, &lProcess );


/*
*
* 格式化字符串
*
* RtlZeroMemory(g_szPIDInfo, 20);
*
* RtlStringCchPrintfA(g_szPIDInfo, 20, "%d", (int)hProcessId);
*
*/


GetProcPath( hProcessId, &ExePath );

RtlCopyBytes( (PVOID) pi.name, (PVOID) ( (char *) lProcess + 0x16c), 15 );

RtlCopyBytes( pi.PATH, ExePath.Buffer, ExePath.Length );

pi.PID = hProcessId;


DbgPrint( " %s", pi.name );

DbgPrint( "%s", pi.PATH );

DbgPrint( "%x", pi.PID );


/*RtlStringCchPrintfA( pi.PATH, ExePath->Length, "%s" , ExePath->Buffer ); */


/* 设置事件为有信号,通知应用层 接收 数据 */

KeSetEvent( g_pEventObject0, 0, FALSE );

/* 等待应用层确认。。 */

KeClearEvent( g_pEventObject3 );

DbgPrint( "-- Ring3 应该 反馈 --" );


KeWaitForSingleObject( g_pEventObject3, Executive, KernelMode, FALSE, NULL );


DbgPrint( "-- RIng3 来了反馈--" );

if ( g_bREPLY != TRUE )

{
DbgPrint( "-- Ring3 决定结束进程 --" );


/*
*
*
*
* .... 其他操作
*
*
*
*/
}

DbgPrint( "-- Ring3 决定 放行 --" );

KeClearEvent( g_pEventObject3 );
}
}


NTSTATUS SioctlDeviceControl( PDEVICE_OBJECT DeviceObject, PIRP irp )


{
PIO_STACK_LOCATIONirpSp;

ULONGinputBuffLength;

NTSTATUSstatus = STATUS_SUCCESS;

PCHARinBuf, outBuf;

PCHARbuffer = NULL;

ULONGOutputBuffLength;

HANDLEINFOhi;

irpSp = IoGetCurrentIrpStackLocation( irp );

inputBuffLength = irpSp->Parameters.DeviceIoControl.InputBufferLength;

OutputBuffLength = irpSp->Parameters.DeviceIoControl.OutputBufferLength;


inBuf = (PCHAR) irp->AssociatedIrp.SystemBuffer;

outBuf = (PCHAR) irp->AssociatedIrp.SystemBuffer;


/*
*
* DbgPrint("-- SioctlDeviceControl --");
*
* PrintIrpInfo(irp);
*
*/


/*
*
* DbgPrint("-- 1 --");
*
* DbgPrint("IOCTL_START : %x ",IOCTL_START);
*
*/


/*
*
* if(!inputBuffLength || ! OutputBuffLength)
*
* {
*
* status = STATUS_INVALID_PARAMETER;
*
* goto END;
*
* }*/


/*
*
* DbgPrint("-- 2 --");
*
* DbgPrint("IOCTL_START : %x ",IOCTL_START);
*
*/


switch ( irpSp->Parameters.DeviceIoControl.IoControlCode )

{
case IOCTL_START:

{
DbgPrint( " -- START --" );

/* 取得句柄对象 */

hi = *(PHANDLEINFO ) inBuf;

status = ObReferenceObjectByHandle( hi.arg1, \

GENERIC_ALL, NULL, KernelMode, (PVOID *) &g_pEventObject0, &g_ObjectHandleInfo );


status = ObReferenceObjectByHandle( hi.arg2, \

GENERIC_ALL, NULL, KernelMode, (PVOID *) &g_pEventObject3, &g_ObjectHandleInfo );


KdPrint( ("g_pEventObject0 = 0x%X , g_pEventObject3 = 0x%X\n", g_pEventObject0, g_pEventObject3) );


if ( !g_bIsNotifyRoutineSetted )

{
PsSetCreateProcessNotifyRoutine( CreateProcessNotifyFunction, FALSE );

g_bIsNotifyRoutineSetted = TRUE;
}


break;
}


case IOCTL_STOP:

{
DbgPrint( " -- STOP --" );

if ( g_bIsNotifyRoutineSetted )

{
/* 移除进程创建通知函数 */

PsSetCreateProcessNotifyRoutine( CreateProcessNotifyFunction, TRUE );

g_bIsNotifyRoutineSetted = FALSE;
}


/* 释放对象引用 */

if ( g_pEventObject0 != NULL )

{
ObDereferenceObject( g_pEventObject0 );

g_pEventObject0 = NULL;
}


if ( g_pEventObject3 != NULL )

{
ObDereferenceObject( g_pEventObject3 );

g_pEventObject3 = NULL;
}


break;
}


case IOCTL_GET_DATA:

{
ULONG nLength = sizeof(pi);

DbgPrint( " -- GET_DATA --" );

if ( outBuf == NULL && (OutputBuffLength < nLength) )

{
KdPrint( ("OutputBufferSize is too small ~!\n") );

break;
}


/*复制进程PID到输出缓冲区 */

RtlCopyBytes( (PCHAR) outBuf, (PVOID) &pi, nLength );

OutputBuffLength = nLength;

break;
}


case IOCTL_SET_REPLY:

{
RtlCopyBytes( (PVOID) &g_bREPLY, inBuf, inputBuffLength ); /* 1 字节 */

DbgPrint( "IOCTL_SET_REPLY: %d ", g_bREPLY );

break;
}
}


END:

irp->IoStatus.Status = STATUS_SUCCESS;


irp->IoStatus.Information = OutputBuffLength;

IoCompleteRequest( irp, IO_NO_INCREMENT );


return(STATUS_SUCCESS);
}


NTSTATUS DriverEntry( PDRIVER_OBJECT pDriverObject, PUNICODE_STRING RegPath )


{
NTSTATUS status;


UNICODE_STRING DriverName;


UNICODE_STRING symbolicName;


PDEVICE_OBJECT deviceObject;


RtlInitUnicodeString( &DriverName, DRIVERNAME );


RtlInitUnicodeString( &symbolicName, SYMBOLICNAME );


/* */


DbgPrint( "DriverEntry" );


/* RtlInitUnicodeString(&EventName, G_EVENT); */

status = IoCreateDevice(

pDriverObject, /* Our Driver Object */

0, /* We don't use a device extension */

&DriverName, /* Device name "\Device\SIOCTL" */

FILE_DEVICE_UNKNOWN, /* Device type */

0, /* Device characteristics */

TRUE, /* Not an exclusive device */

&deviceObject ); /* Returned ptr to Device Object */


if ( !NT_SUCCESS( status ) )


{
DbgPrint( ("Couldn't create the device object %x \n"), status );


return(status);
}

g_bIsNotifyRoutineSetted = FALSE;

pDriverObject->MajorFunction[IRP_MJ_CREATE] = SioctlCreateClose;

pDriverObject->MajorFunction[IRP_MJ_CLOSE] = SioctlCreateClose;

pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = SioctlDeviceControl;

pDriverObject->DriverUnload = MyUnload;

status = IoCreateSymbolicLink( &symbolicName, &DriverName );

if ( !NT_SUCCESS( status ) )

{
DbgPrint( ("Couldn't create the symboliclink \n") );

IoDeleteDevice( deviceObject );

return(status);
}
return(STATUS_SUCCESS);
}

VOID
PrintIrpInfo(
PIRP Irp )
{
PIO_STACK_LOCATION irpSp;
irpSp = IoGetCurrentIrpStackLocation( Irp );
PAGED_CODE();
DbgPrint( "\tIrp->AssociatedIrp.SystemBuffer = 0x%p\n",Irp->AssociatedIrp.SystemBuffer );


DbgPrint( "\tIrp->UserBuffer = 0x%p\n", Irp->UserBuffer );


DbgPrint( "\tirpSp->Parameters.DeviceIoControl.Type3InputBuffer = 0x%p\n",irpSp->Parameters.DeviceIoControl.Type3InputBuffer );


DbgPrint( "\tirpSp->Parameters.DeviceIoControl.InputBufferLength = %d\n",irpSp->Parameters.DeviceIoControl.InputBufferLength );


DbgPrint( "\tirpSp->Parameters.DeviceIoControl.OutputBufferLength = %d\n",irpSp->Parameters.DeviceIoControl.OutputBufferLength );


DbgPrint( "\t irpSp->Parameters.DeviceIoControl.IoControlCode = %x\n",
irpSp->Parameters.DeviceIoControl.IoControlCode );


return;
}

APP

.h

typedef struct _PROCESSINFO

{
ULONGPID;

charname[16];

charPATH[256];
}PROCERSSINFO, *PPROCESSINFO;


typedef struct _HANDLEINFO

{
HANDLEarg1; /* 这个是 内核层 SET */

HANDLEarg2; /* 这个是 应用层 SET */
}HANDLEINFO, *PHANDLEINFO;


/* 全局事件 */

HANDLE g_EventHandle;

HANDLE g_hkEvent; /* 内核层句柄 */

bool g_bIsRunnig;

start函数

DWORD dwRet;

HANDLEINFO hi ;

hi.arg1 = g_EventHandle;

hi.arg2 = g_hkEvent;

bool bRet = IOControl(IOCTL_START,&hi ,sizeof(HANDLEINFO) ,NULL , 1024 ,&dwRet);

//创建监听线程

HANDLE hThread = CreateThread(NULL , 0 , (LPTHREAD_START_ROUTINE)&WorkThread ,NULL ,0, NULL);

g_bIsRunnig = true;

CloseHandle(hThread);

Sleep(1);

IOControl 函数

bool IOControl( int Ctl_code, LPVOID InputBuffer, int InputLen, LPVOID OutputBuffer, int OutputLen, LPDWORD dwRet )

{
bool bRet = false;

HANDLE hDevice = CreateFile("\\\\.\\KernelHandle", GENERIC_READ | GENERIC_WRITE, 0,NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );

if ( hDevice == INVALID_HANDLE_VALUE )
{
MessageBox( 0, "Failed To Open Device!", NULL, 0 );
return(false);
}

bRet = DeviceIoControl( hDevice, Ctl_code, InputBuffer, InputLen, OutputBuffer, OutputLen, dwRet, NULL );
CloseHandle( hDevice );
return(bRet);
}

线程函数

DWORD WINAPI WorkThread( LPVOID lParam )

{
DWORD dRet;

InitializeCriticalSection( &g_cs );

while ( g_bIsRunnig )

{
/* 等待同步时间 */

WaitForSingleObject( g_EventHandle, INFINITE ); /* 等待 驱动程序 发出 信号 */

char szBuffer[20] = { 0 };

WCHAR szMsgBuffer[100] = { 0 };

DWORD dwRet, nProcessId = 0;

EnterCriticalSection( &g_cs );

/* 接收到信号后 发出获取 本次信息 */

IOControl( IOCTL_GET_DATA, NULL, 0, &pi, sizeof(pi), &dwRet );


CString cs;

MsgDialog dlg;

UCHARdata = 0;

cs.Format( "%d ", pi.PID );

dlg.m_id = cs;

cs.Format( "%s ", pi.PATH );

dlg.m_path = cs;

cs.Format( "%s ", pi.name );

dlg.m_name = cs;

if ( IDOK == dlg.DoModal() ) /* 这个是一个对话框类 */

{
/* 允许 */

data = 1;

/* MessageBox ( 0, "允许" , "提示" ,MB_OK); */
}

IOControl( IOCTL_SET_REPLY, &data, 1, szBuffer, 20, &dwRet ); /* 设置好 用户 选择 */


LeaveCriticalSection( &g_cs );

SetEvent( g_hkEvent ); /* 通知内核 */


/* 设置同步事件为无信号,等待下一次通知 */

ResetEvent( g_EventHandle );
}

DeleteCriticalSection( &g_cs );


return(0);
}