‏הצגת רשומות עם תוויות c. הצג את כל הרשומות
‏הצגת רשומות עם תוויות c. הצג את כל הרשומות

יום ראשון, 26 ביולי 2015

Java Native Interface


רוב האפליקציות ב Android משתמשות בספריות Java תחת ה JVM (ליתר דיוק Dalvik) ללא צרכים מיוחדים אבל בחלק מהמקרים זה לא מספיק ונדרשת התערבות עמוקה יותר, בדומה לאחד המאמרים בו הצגתי את JavaScript Interface גם פה מדובר בממשק שהמערכת נותנת שמאפשר לנו להתחבר לעולם ה Native במכשיר ולהפעיל דרכו ספריות בעזרת שפת C.

שימו לב!

  • המאמר נכתב על Android Studio 1.0.1
  • מערכת הפעלה Windows 7 64 Bit


אם לרגע חשבתם שמעכשיו תכתבו אפליקציות ב C אז כדאי שתעצרו (למרות שזה אפשרי אבל לא חבל..), לא בשביל זה יש את ה NDK אלא עבור מקרים מאוד מיוחדים כמו למשל מוגבלויות ה API של Java או יצירת ממשק עבור חומרה חדשה, יש שימוש רחב ב NDK בתעשיית המשחקים בגלל ממשק ל OpenGL רחב יותר,

דרישות
  • Native Development Kit - NDK
  • +Java Development Kit - JDK 1.8
  • +Gradle 2.2

יש להוריד ולפרוס את ה NDK היכן שתרצו (מומלץ לשמור איפה שתיקיית ה SDK), כדאי מאוד לוודא שיש לכם את גרסת Java אחרונה, גרסאות ישנות מידי עלולות לגרום לבעיות שלא נעים להתעסק בהן, לאחר מכן יש להוסיף את הנתיבים לתיקיית ה Java וה NDK למשתנה ה Path הכללי של המערכת שניתן להגיע אליו דרך Environment Variables שנמצא ב Advanced במסך של ה System Properties.

ניצור פרויקט חדש, נגדיר פונקצית Native שאת המימוש שלה נעשה ב C:

public native String getStringFromNative();


לאחר מכן נפתח Terminal ב Android Studio ,צריך לוודא שאתם בתיקיית Main של הפרויקט ולהריץ את הפקודה הבא:

E:\NDKtest3\app\src\main>javah -d jni -classpath E:\Users\proxytype\AppData\Local\Android\sdk\platforms
\android-14\android.jar;..\..\build\intermediates\classes\debug legend.proxytype.ndktest3.MainActivity

הפקודה תיצור תיקייה בפרויקט בשם JNI בתוכה קובץ Header שמכיל את החתימה לפונקציה, ניצור בה 2 קבצים חדשים, הראשון נקרא Application.mk שמכיל את הקוד הבא:

APP_ABI := all
APP_PLATFORM := android-14

השורה הראשונה מגדירה לאיזו ארכיטקטורת מעבד תתואם הספריה החיצונית (ה NDK מגיע עם כל סוגי ה Tool Chain) במקרה של ALL תיווצר ספריה עבור כל ארכטיקטורה, השורה השנייה עבור סוג הפלטפורמה של ה Android.

הקובץ השני נקרא Android.mk שמכיל את ההגדרות של הספריה בדומה ל Makefile סטנדרטי:

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE    := MyLib
LOCAL_SRC_FILES := main.c
include $(BUILD_SHARED_LIBRARY)  

חשוב לזכור להמשך את 2 הפרמטרים הבאים, הראשון ה LOCAL_MODULE שהוא שם הספריה שנשתמש בו ב Java כפי שנראה בהמשך, ו LOCAL_SRC_FILES שמצביע לקובץ ה main.c שמכיל את המימוש לפונקציות בקובץ ה Header שנוצר בתחילת התהליך.

C

התוכנית תכיל פונקציה שתחזיר מחרוזת מספריית ה Native שנכתבה ב C לאפליקציה עצמה שנכתבה ב Java, יש ליצור קובץ main.c ולרשום בו את הקוד הבא:

 //our generated header file
#include "legend_proxytype_ndktest3_MainActivity.h"

JNIEXPORT jstring JNICALL 
Java_legend_proxytype_ndktest3_MainActivity_getStringFromNative(JNIEnv * env, jobject obj)
 {
        return (*env)->NewStringUTF(env,"Hello From JNI!");
 }
הקוד מאוד פשוט וניתן לראות את החשיפה של הפונקציה עבור מנגנון ה JNI והאפשרות לקרוא לה מבחוץ, קיים באג ב Android Studio שמחייב ליצור קובץ נוסף ריק על מנת שנוכל לבנות את הספריה.

Java

בשביל להפעיל את הפונקציה יש לטעון את הספריה באפליקציה עם השם שנתנו ב Android.mk, אח"כ נקרא לה ונקבל ממנה מחרוזת:

package legend.proxytype.ndktest3;

import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends Activity {

    //loading the library with the name from android.mk
    static
    {
       System.loadLibrary("MyLib");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        TextView lbl_jni = (TextView)findViewById(R.id.lbl_jni);
        //make the call to function
        lbl_jni.setText(getStringFromNative());

    }

    public native String getStringFromNative();
}


Gradle

אם חשבתם שסיימנו אז עוד קצת וזה נגמר, יש לערוך את קובץ build.gradle על מנת שיבנה את הספריה באופן אוטומטי בכל בנייה מחדש של האפליקציה, יש לקרוא ל Module עם אותו שם של הספריה, ולהגדיר Task שיקרא ל Shell שיפעיל את ה NDK:

import org.apache.tools.ant.taskdefs.condition.Os

apply plugin: 'com.android.application'

android {
    compileSdkVersion 22
    buildToolsVersion "22.0.1"

    defaultConfig {
        applicationId "legend.proxytype.ndktest3"
        minSdkVersion 14
        targetSdkVersion 22
        versionCode 1
        versionName "1.0"

        ndk
         {
          //same as the name in android.mk
           moduleName "MyLib"
         }
    }

    sourceSets.main {
        jniLibs.srcDir 'src/main/libs'
        jni.srcDirs = [] 
    }

    //set task for the compilation of the library
    task ndkBuild(type: Exec) {
        //hadling different host machine
        if (Os.isFamily(Os.FAMILY_WINDOWS)) {
            commandLine('ndk-build.cmd', '-C' , file('src/main').absolutePath)
        } else {
            commandLine 'ndk-build', '-C', file('src/main').absolutePath
        }
    }

    //the build of the application depend on the success of the JNI
    tasks.withType(JavaCompile) {
        compileTask -> compileTask.dependsOn ndkBuild
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:22.2.0'
}

דבר אחרון לפני סיום הוא לוודא שתיקיית ה NDK מוגדרת ב Local.Properties של Gradle:

ndk.dir=E\:\\Users\\proxytype\\AppData\\Local\\Android\\ndk



מבנה האפליקציה אחרי כל הבלאגן

סיכום

הכנת סביבה לתמיכה ב NDK זה עסק לא פשוט, למרות שיש דיבורים להפוך אותו לחלק בלתי נפרד בגרסאות הבאות של Android Studio, מי יודע מתי זה יקרה, אבל האפשרויות שהוא נותן שווים את המאמץ ויש דברים שאפשר לעשות רק איתו כפי שנראה עוד בהמשך.

100% טבעי...

יום שבת, 1 בנובמבר 2014

Deep Packet Inspection With C



מערכות FireWall בדר"כ בוחנות את החבילות שעוברות דרכן רק על ידי אימות של כתובות ופורטים מול רשימת חוקים, אבל עם הזמן הבינו שזה לא מספיק טוב ויש לנתח את המידע עצמו שעובר בחבילה על מנת לזהות התקפות שמשפיעות על השירות עצמו, נדרשה חשיבה חדשה ועם הזמן צצו מערכות IDS \ IPS שבעזרתם ניתן לבצע בדיקות שמבוססות על תבניות ולחפש קוד זדוני בתוך החבילה, פרוייקט מאוד מפורסם ונמצא בשימוש רחב במערכות כאלו נקרא Snort שמכיל המון תבניות להתקפות מוכרות.

שימו לב!
  • המאמר נכתב על Fedora 13.
  • הקוד נערך ב Eclipse.
כל חבילה שמגיעה למכונה עוברת בדיקה מול החוקים שהוגדרו ב iptables שעובד מול ה NetFilter שיושב ב Kernel, אם החוקים מאפשרים להעביר את החבילה ליעד ה Netfilter יאשר את החבילה (NF_ACCEPT) אבל במקרה שהחוקים חוסמים אותה הוא יפיל את החבילה (NF_DROP), אלה 2 הפעולות הבסיסיות שנמצאות בכל Firewall אבל זה לא מספיק וצריך לחפור יותר פנימה, ניתן לרשום חוק שמפנה את המידע (NF_QUEUE) למחסנית שמאפשרת גישה לתוכניות מעולם ה UserSpace לנתח את החבילות ולחרוץ את גורלם.

התוכנית קובעת את גורל החבילה

לצורך הדוגמה החוק הבא מכניס את כל המידע שמגיע למחסנית במיקום 0 אבל במקרה של עבודה אינטנסיבית המחסנית תתמלא עוד לפני שנספיק לנקות אותה ולכן החוק הזה לא כלכך יעיל:

#: iptables -A INPUT  -j NFQUEUE --queue-num 0 --queue-balance

עבור ניתוח יעיל יותר צריכים לעבוד במקביל, ניתן לפצל חוק בין מספר מחסניות שונות כפי שניתן לראות בדוגמה:

#: iptables -A INPUT  -j NFQUEUE --queue-balance 0:3
#: iptables -A OUTPUT  -j NFQUEUE --queue-balance 4:8


Libnetfilter_queue Library

ספריה רשמית שמאפשרת לנהל את החבילות שנמצאות במחסניות בעזרת עבודה מול Netlink דרך Unix Domain Socket, היא מחליפה ספרייה ישנה יותר שנקראת ip_queue והיא נמצאת כמעט בכל Distribution של Linux, אבל בשביל לעבוד איתה יש להוריד את ה Headers ולהכין את ה Eclipse:

#: yum install libnetfilter_queue
#: yum install libnetfilter_queue-devel


נשלב את הספריות המתאימות ב Linker:





קוד

פונקצית Main מייצרת מערך של Threads שכל אחד מהם מתחבר ל Queue אחר, כאשר תגיע חבילה לאחת מהמחסניות, פונקציית packetHandler תרוץ ודרכה ננתח את המידע, לצורך הדוגמה כאשר נזהה את המילה facebook בתוכן החבילה נפיל אותה ונחסום את הגישה.

filterQueue.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <netinet/in.h>
#include <linux/types.h>
#include <string.h>

/* for ethernet header */
#include<net/ethernet.h>

/* for UDP header */
#include<linux/udp.h>

/* for TCP header */
#include<linux/tcp.h>

/* for IP header */
#include<linux/ip.h>

/*  -20 (maximum priority) */
#include <sys/time.h>
#include <sys/resource.h>

/* for NF_ACCEPT */
#include <linux/netfilter.h>

/* for Threads */
#include <pthread.h>

/* for Queue */
#include <libnetfilter_queue/libnetfilter_queue.h>

#define NUM_THREADS     15

pthread_t threads[NUM_THREADS];

void printTCP(unsigned char *buffer) {

unsigned short iphdrlen;

struct iphdr *iph = (struct iphdr *) (buffer + sizeof(struct ethhdr));
iphdrlen = iph->ihl * 4;

struct tcphdr *tcph = (struct tcphdr *) (buffer + iphdrlen
+ sizeof(struct ethhdr));

int header_size = sizeof(struct ethhdr) + iphdrlen + tcph->doff * 4;

printf("| Packet Type: TCP \n");
printf("|-Source Port      : %u\n", ntohs(tcph->source));
printf("|-Destination Port : %u\n", ntohs(tcph->dest));
printf("|-Sequence Number    : %u\n", ntohl(tcph->seq));
printf("|-Acknowledge Number : %u\n", ntohl(tcph->ack_seq));
printf("|-Header Length      : %d DWORDS or %d BYTES\n",
(unsigned int) tcph->doff, (unsigned int) tcph->doff * 4);
printf("|-CWR Flag : %d\n", (unsigned int) tcph->cwr);
printf("|-ECN Flag : %d\n", (unsigned int) tcph->ece);
printf("|-Urgent Flag          : %d\n", (unsigned int) tcph->urg);
printf("|-Acknowledgement Flag : %d\n", (unsigned int) tcph->ack);
printf("|-Push Flag            : %d\n", (unsigned int) tcph->psh);
printf("|-Reset Flag           : %d\n", (unsigned int) tcph->rst);
printf("|-Synchronise Flag     : %d\n", (unsigned int) tcph->syn);
printf("|-Finish Flag          : %d\n", (unsigned int) tcph->fin);
printf("|-Window         : %d\n", ntohs(tcph->window));
printf("|-Checksum       : %d\n", ntohs(tcph->check));
printf("|-Urgent Pointer : %d\n", tcph->urg_ptr);
}

void printUDP(unsigned char *buffer) {
unsigned short iphdrlen;

struct iphdr *iph = (struct iphdr *) (buffer + sizeof(struct ethhdr));
iphdrlen = iph->ihl * 4;

struct udphdr *udph = (struct udphdr*) (buffer + iphdrlen
+ sizeof(struct ethhdr));

int header_size = sizeof(struct ethhdr) + iphdrlen + sizeof udph;

printf("| Packet Type: UDP \n");
printf("|-Source Port      : %u\n", ntohs(udph->source));
printf("|-Destination Port : %u\n", ntohs(udph->dest));
printf("|-UDP Length : %u\n", ntohs(udph->len));
printf("|-UDP Checksum : %u\n", ntohs(udph->check));

}

char * getText(unsigned char * data, char Size) {

char * text = malloc(Size);
int i = 0;

for (i = 0; i < Size; i++) {
if (data[i] >= 32 && data[i] <= 128)
text[i] = (unsigned char) data[i];
else
text[i] = '.';
}
return text;

}

u_int32_t analyzePacket(struct nfq_data *tb, int *blockFlag) {

//packet id in the queue
int id = 0;

//the queue header
struct nfqnl_msg_packet_hdr *ph;

//the packet
char *data;

//packet size
int ret;

//extracting the queue header
ph = nfq_get_msg_packet_hdr(tb);

//getting the id of the packet in the queue
if (ph)
id = ntohl(ph->packet_id);

//getting the length and the payload of the packet
ret = nfq_get_payload(tb, &data);
if (ret >= 0) {

printf("Packet Received: %d \n", ret);

/* extracting the ipheader from packet */
struct sockaddr_in source, dest;
unsigned short iphdrlen;

struct iphdr *iph = ((struct iphdr *) data);
iphdrlen = iph->ihl * 4;

memset(&source, 0, sizeof(source));
source.sin_addr.s_addr = iph->saddr;

memset(&dest, 0, sizeof(dest));
dest.sin_addr.s_addr = iph->daddr;

printf("|-Source IP: %s\n", inet_ntoa(source.sin_addr));
printf("|-Destination IP: %s\n", inet_ntoa(dest.sin_addr));
printf("|-Checking for Protocol: \n");

if (iph->protocol == 6) {
printTCP(data);
} else if (iph->protocol == 17) {
printUDP(data);
}

printf("|-Extracting Payload: \n");

char * text = getText(data, ret);

//filtering requests for facebook
if (text && text[0] != '\0') {
printf("\n %s \n", text);
ret = strstr(text, "facebook");
if (ret == 0)
//not found in string
*blockFlag = 0;
else
//found in string
*blockFlag = 1;
}

//release the packet
free(text);


}
//return the queue id
return id;

}

int packetHandler(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg, struct nfq_data *nfa,
void *data) {

printf("entering callback \n");

//when to drop
int blockFlag = 0;

//analyze the packet and return the packet id in the queue
u_int32_t id = analyzePacket(nfa, &blockFlag);

//this is the point where we decide the destiny of the packet
if (blockFlag == 0)
return nfq_set_verdict(qh, id, NF_ACCEPT, 0, NULL);
else
return nfq_set_verdict(qh, id, NF_DROP, 0, NULL);



}

void *QueueThread(void *threadid) {

//thread id
long tid;
tid = (long) threadid;


struct nfq_handle *h;
struct nfq_q_handle *qh;
char buf[128000] __attribute__ ((aligned));

//pointers and descriptors
int fd;
int rv;
int ql;


printf("open handle to the netfilter_queue - > Thread: %d \n", tid);
h = nfq_open();
if (!h) {
fprintf(stderr, "cannot open nfq_open()\n");
return NULL;
}

//unbinding previous procfs
if (nfq_unbind_pf(h, AF_INET) < 0) {
fprintf(stderr, "error during nfq_unbind_pf()\n");
return NULL;
}

//binding the netlink procfs
if (nfq_bind_pf(h, AF_INET) < 0) {
fprintf(stderr, "error during nfq_bind_pf()\n");
return NULL;
}

//connet the thread for specific socket
printf("binding this socket to queue '%d'\n", tid);
qh = nfq_create_queue(h, tid, &packetHandler, NULL);
if (!qh) {
fprintf(stderr, "error during nfq_create_queue()\n");
return NULL;
}

//set queue length before start dropping packages
ql = nfq_set_queue_maxlen(qh, 100000);

//set the queue for copy mode
if (nfq_set_mode(qh, NFQNL_COPY_PACKET, 0xffff) < 0) {
fprintf(stderr, "can't set packet_copy mode\n");
return NULL;
}

//getting the file descriptor
fd = nfq_fd(h);

while ((rv = recv(fd, buf, sizeof(buf), 0)) && rv >= 0) {
printf("pkt received in Thread: %d \n", tid);
nfq_handle_packet(h, buf, rv);
}

printf("unbinding from queue Thread: %d  \n", tid);
nfq_destroy_queue(qh);

printf("closing library handle\n");
nfq_close(h);

return NULL;

}

int main(int argc, char *argv[]) {

//set process priority
setpriority(PRIO_PROCESS, 0, -20);

int rc;
long balancerSocket;
for (balancerSocket = 0; balancerSocket < NUM_THREADS; balancerSocket++) {
printf("In main: creating thread %ld\n", balancerSocket);

//send the balancer socket for the queue
rc = pthread_create(&threads[balancerSocket], NULL, QueueThread,
(void *) balancerSocket);

if (rc) {
printf("ERROR; return code from pthread_create() is %d\n", rc);
exit(-1);
}
}

while (1) {
sleep(10);
}

//destroy all threads
pthread_exit(NULL);
}

טיפ קטן

אם רוצים לעקוב אחרי העומסים במחסניות ניתן לקרוא את הקובץ nfnetlink_queue:

/proc/net/netfilter


סיכום

ניתוח המידע הוא הכרחי מאוד לאור כל הסיכונים שקיימים היום, היכולת לחרוץ את גורל החבילה מחוץ ל Kernel חוסך התעסקות מיותרת והופך את העסק לפשוט יותר, על הטריק הזה ניתן לבסס מוצרי רשת שונים כמו IDS,IPS,WAF ועוד.

הורדת התוכנית


מקורות



יום שבת, 26 באפריל 2014

Linked List Linux Kernel


בהמשך למסע ב Kernel החלטתי להתמקד בקצרה על רשימות מקושרות, למי שלא מכיר או שהספיק לשכוח מדובר על דרך לחבר בין אובייקטים בעזרת מצביעים לאובייקטים אחרים בזיכרון, לכל אובייקט יש מצביע לאובייקט שלפניו ומצביע לאובייקט שאחריו כפי שניתן לראות בתמונה:



שימו לב!

  • עבודה מול ה Kernel עלולה להזיק למחשב שלך, מומלץ לעבוד על תחנה וירטואלית.
  • המאמר נכתב על Fedora 14.

מערכת ההפעלה נותנת כלים פנימיים שעוזרים לנהל את הרשימות במקום שנהל בעצמנו, קובץ list.h חושף מספר פונקציות ו Macros כפי שניתן לראות בדוגמה:


myList.c

//define module
#include <linux/module.h>
//we are in the kerenl
#include <linux/kernel.h>
//linked list header
#include <linux/list.h>

//example struct to be linked
struct element {
int id;
char name[15];
char description[50];

//creating header, next and previous pointers
struct list_head list;
};

//the list!
struct element elementsList;

void addElements(void)
{
struct element *tmp;

int listMax = 20;
int i = 0;

for(i = 0; i < listMax; i++)
{
//getting memory for the struct
tmp = kmalloc(sizeof(*tmp),GFP_KERNEL);
tmp->id = i;
strcpy(tmp->name, "Element");
strcpy(tmp->description, "just simple element inside the list");

//initializes pointers
INIT_LIST_HEAD(&tmp->list);
//add the element at the end of the list
  list_add_tail(&(tmp->list), &(elementsList.list));

}

printk(KERN_INFO "load complete\n");
}

void deleteElements(void)
{
struct element *tmp, *node;
  //if deleting elements better using this function,
  //it preserve the pointer to the next element,preventing holes inside the list. 
  list_for_each_entry_safe(node, tmp, &elementsList.list, list){
         //remove from the list
         list_del(&node->list);
         //release the memory
         kfree(node);
    }
}

void viewElements(void)
{
struct element *tmp;

//running through all elements
list_for_each_entry(tmp, &elementsList.list, list) {
printk(KERN_INFO "Element\n ID: %d; Name: %s; 
                         Description: %s\n", tmp->id, tmp->name, tmp->description);
}
}

int c_module() {

//initializes the list
INIT_LIST_HEAD(&elementsList.list);
printk(KERN_INFO "initialize list complete\n");
addElements();
viewElements();
return 0;

}

void r_module()
{
printk(KERN_INFO "delete list\n");
deleteElements();
printk(KERN_INFO "remove module complete\n");
}

module_init(c_module);
module_exit(r_module);



מגדירים מבנה עם אובייקט מיוחד מסוג list_head שמכיל בתוכו את המצביעים, מייצרים מופע כללי של המבנה שמייצג את ראש הרשימה, בשלב טעינת ה Module  נטענת הפונקציה addElements שמכניסה 20 אובייקטים לרשימה, היא מאפסת את המצביעים ושולחת כל מבנה לפונקציה (list_add_tail) שמוסיפה אותו לסוף הרשימה.

לסיום רצה הפונקציה viewElements שמציגה את הרשימה בעזרת המאקרו list_for_each_entry שדרכו ניתן לעבור על האלמנטים, בהסרת ה Module רצה הפונקציה deleteElements שמפעילה את המאקרו list_for_each_entry_safe שדואג להסרה בטוחה של אובייקטים מבלי שהיו חורים ברשימה.

סיכום

מדובר בכלי חזק אבל קצת מסורבל שעלול לגרום לכאב ראש לא קטן אם ננסה לממש אותו בעצמנו, Linux חוסכת את כל ההתעסקות הכואבת במצביעים ונותנת תשתית נוחה לבניה ולניהול רשימות מקושרות.

מידע נוסף


רשמת?




יום שבת, 12 באפריל 2014

Procsfs Linux Kernel



מערכת Linux נותנת מספר דרכים להעברת מידע בין ה User Space ל Kernel, אחת הדרכים הצגתי במאמר הפתיחה בעזרת Character Device שיוצר קובץ בתיקיית ה Dev דרכו מדברים עם ה Module, השיטה הנוספת שהחלטתי להתמקד עליה היא Procs File System שמאוד דומה רק שהקובץ ממוקם בספריית ה Proc ללא צורך רישום כ Device.

הקובץ פותח שער שדרכו כותבים וקוראים מה Module בדומה לעבודה על קבצים (פתיחה,כתיבה,קריאה וסגירה) וכך נוצר ממשק אחיד, פשוט ונוח, לצורך הדוגמה כשנריץ את הפקודה Cat על הקובץ cpuinfo בתקיית proc נקבל את הפלט הבא:

[root@localhost proc]# cat cpuinfo 
processor : 0
vendor_id : GenuineIntel
cpu family : 6
model : 26
model name : Intel(R) Core(TM) i7 CPU         950  @ 3.07GHz
stepping : 5
cpu MHz : 3158.658
cache size : 6144 KB
fdiv_bug : no
hlt_bug : no
f00f_bug : no
coma_bug : no
fpu : yes
fpu_exception : yes
cpuid level : 5
wp : yes
flags : fpu vme de pse tsc msr pae mce cx8 apic mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 syscall nx lm constant_tsc up pni monitor ssse3 lahf_lm
bogomips : 6317.31
clflush size : 64
cache_alignment : 64
address sizes : 36 bits physical, 48 bits virtual
power management:

שימו לב!
  • עבודה מול ה Kernel עלולה להזיק למחשב שלך ממולץ לעבוד על תחנה וירטואלית.
  • המאמר נכתב על Fedora 14.

מייצרים אובייקט מסוג proc_dir_entry שמייצג את הקובץ בתיקיית ה Proc, לאחר מכן דורסים את הפונקציות Read ו Write של הקובץ לפונקציות פנימיות ב Module, מגדירים Buffer עבור המידע שיעבור ואפשר להגיד שסיימנו.

procModule.c

//define module.
#include <linux/module.h>
//we are in the kernel.
#include <linux/kernel.h>
//proc header.
#include <linux/proc_fs.h>
//copy from user.
#include <asm/uaccess.h>

#define PROCMAXSIZE 4096
#define PROC_NAME "Bridge"

//pointer to the proc file.
static struct proc_dir_entry *procfile;

//buffer of the proc file.
static char procbuffer[PROCMAXSIZE];

//buffer size.
static unsigned long buffersize = 0;

//proc file new read function will overwrite the old one.
//buffer: the kernel buffer pointer.
//buffer_location: the starting point of reading
//offset:the offset from the beginning of the file.
//buffer_length: the number of chars to read.
//eof: end of file pointer if there is more to read.
//data: private data.
//return the read length value.
int procfile_read(char *buffer, char **buffer_location,off_t offset, int buffer_length, int *eof, void *data)
{
int length;

if (offset > 0) {
//nothing to read reset length read value.
length  = 0;
} else {

//copy data from kernel space to user space.
memcpy(buffer, procbuffer, buffersize);

printk(KERN_INFO "Read: %s \n", procbuffer);

//set the length read value.
length = buffersize;
}
return length;
}

//proc file new write function will overwrite the old one.
//file: open file structure.
//buffer: user space buffer.
//count: the number of chars been writes.
//data: private data.
//return the read length value.
int procfile_write(struct file *file, const char *buffer, unsigned long count, void *data)
{
//getting buffer size.
buffersize = count;

//check for overflow.
if (buffersize > PROCMAXSIZE ) {
buffersize = PROCMAXSIZE;
}

//copy data from user space buffer to kernel space buffer.
if ( copy_from_user(procbuffer, buffer, buffersize) ) {
//return error if unable.
return -EFAULT;
}

printk(KERN_INFO "Write: %s \n", procbuffer);

//return the size of writing.
return buffersize;
}


int c_module()
{
//create the proc file on module creation,
//give name and permissions.
procfile = create_proc_entry(PROC_NAME, 0644, NULL);

//if null return error handling.
if (procfile == NULL) {

remove_proc_entry(PROC_NAME, NULL);
printk(KERN_ALERT "Cannot create Proc: %s\n",PROC_NAME);
//maybe cause by out of memory.
return -ENOMEM;
}

//overwrite the original read and write functions of the file.
procfile->read_proc  = procfile_read;
procfile->write_proc = procfile_write;

printk(KERN_INFO "%s created\n", PROC_NAME);

//operation complete.
return 0;
}


void r_module()
{
//remove proc file entry from folder.
remove_proc_entry(PROC_NAME, NULL);
printk(KERN_INFO "/proc/%s removed\n", PROC_NAME);
}


module_init(c_module);
module_exit(r_module);


נעשה Compile בעזרת Makefile ונטען את ה Module לזיכרון עם הפקודה insmod, נבדוק בתיקיית ה proc שנוצר הקובץ בשם Bridge ונתחיל לדבר איתו בעזרת פקודות מה Command Line כפי שניתן לראות בדוגמה:

[root@localhost proc]# echo "send data to module" >> /proc/Bridge 
[root@localhost proc]# cat Bridge 
send data to module


סיכום

ראינו מספר דרכים לדבר עם ה Kernel בעזרת קבצים, הם מחולקים לתיקיות במערכת כמו Proc עבור ה Processes או Dev עבור Devices, יש דרכים נוספות כמו Sysfs, Debugfs ו Configfs שקצת יותר מורכבות אבל עובדות על אותו עיקרון במטרה לתת תשתית להצגה והעברת מידע נוחה ללא צורך בבנייה של ממשקים מורכבים.

בהצלחה...


יום שישי, 15 בנובמבר 2013

Kernel Hooking



זאת לא הפעם הראשונה שאני כותב על Hooking ובד"כ המושג הזה נשמע מאיים במיוחד בתחום אבטחת המידע אבל ב Linux זאת טכניקה לגיטימית בפיתוח ב Kernel ובגלל שמדובר בקוד פתוח המידע נגיש וחשוף לכולם בניגוד ל Windows שעושה את החיים לא קלים ומקשה מאוד על כתיבה ב Kernel, האפשרות לשלוט ב Kernel בצורה כלכך חופשית מאפשרת למפתחים למצוא פתרונות ופטנטים ייחודיים עבור המערכות שלהם.

שימו לב!

  • עבודה מול ה Kernel עלולה להזיק למחשב שלך, מומלץ להשתמש במכונה וירטואלית.
  • המאמר נכתב על Fedora 14 גרסת Kernel - 2.6.35.
  • אני לא אחראי על אופי השימוש בתוכן.

Hooking




אז למי ששכח (לדוג) Hooking זו שיטה שמאפשרת להחליף את הקריאות לפונקציות המקוריות במערכת עם פונקציות אחרות שנטענו ב Module, זאת נקודת בניים שהמתכנת יכול להחליט עם להעביר את המידע לפונקציה המקורית או להפעיל פונקציות אחרות, המשחק עצמו הוא סביב טבלה שמכילה את כל הכתובות לפונקציות של ה System Calls ב Windows זה (כמעט...) בלתי אפשרי למצוא אותה אבל ב Linux זה פשוט מאוד, קיים קובץ בתיקיית ה boot שמכיל את הכתובות לפונקציות של ה System Call, יש להכניס את הפקודה הבאה ב Terminal:



הכתובות לפונקציות של ה System Call נמצאות באיזור שמוגדר כ Read Only אבל בגלל שאנחנו ב Kernel אנחנו יכולים לשנות את ה Control Register שדרכו נהפוך את האיזור גם לכתיבה, נחתום את ה Pointer של הפונקציה החדשה ממקום הפונקציה הישנה.

#include <linux/init.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/kernel.h>
#include <linux/unistd.h>
#include <linux/string.h>

static char * filewatch;

//charp - type of the parameter is string
module_param(filewatch, charp,0);
MODULE_PARM_DESC(filewatch,"destination file pointer");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("simple hooking driver");

//extract using cat /boot/System.map | grep sys_call
unsigned long *syscall_table = (unsigned long *)0xc07b0328;

//original system call signature
asmlinkage int (*original_sys_open)(const char* filename,int flags,int mode);

//hook function
asmlinkage int fake_sys_open(const char* filename, int flags,int mode)
{
//check if open function requested on the filewatch
if(strcmp(filename,filewatch) == 0)
{
  //write to log
  printk("File open(\"%s\", %X, %X)\n", filename,flags,mode);

          //drop the request - operation not permitted
  return -EPERM;
}

//continue normal routine back to original function
return (*original_sys_open)(filename,flags,mode);
}

static int init_driver(void) {

    printk("load hook module\n");
 
    //change read only area to write availability
    write_cr0 (read_cr0 () & (~ 0x10000));

    //save the original __NR_write pointer using uistd.h defines
    original_sys_open = (void *)syscall_table[__NR_open];

    //write to pointer to the fake function
    syscall_table[__NR_open] = fake_sys_open;

    //change the write area back to read only
    write_cr0 (read_cr0 () | 0x10000);

    return 0;
}

static void clean_driver(void) {

    //change read only area to write availability
    write_cr0 (read_cr0 () & (~ 0x10000));

    //return the original pointer to the system call table
    syscall_table[__NR_open] = original_sys_open;

    //change the write area back to read only
    write_cr0 (read_cr0 () | 0x10000);

    printk("unload hook module\n");

    return;
}
//pointing to custom init function when the module loaded
module_init(init_driver);

//pointing to custom cleanup function when the module unloaded
module_exit(clean_driver);

התוכנית שבדוגמה מקבלת פרמטר חיצוני, בעזרת המאקרו module_param נעביר את הנתיב של הקובץ שרוצים לנעול, כאשר נפתח קובץ במערכת מופעלת הפונקציה open ב system call , בשלב טעינת ה Module נשנה את ה Pointer לפונקציה עם פונקציה מזוייפת וכאשר ינסו לפתוח את הקובץ יקבלו שגיאה, בשאר הקבצים הפונקציה תעביר את המידע לפונקציה המוקרית כאילו כלום לא קרה.



סיכום:

השליטה על ה System Call עלולה לפתוח דלת לפיתוחים זדוניים אבל מצד שני מאפשרת שליטה מלאה על המערכת, במאמרים האחרונים ראינו כיצד ניתן לממש Object Oriented Programming בסביבת ה Kernel ובעזרת C בלבד.

בהצלחה...

יום שישי, 8 בנובמבר 2013

Linux Kernel Guide



ברוב המאמרים בבלוג נגעתי בעיקר בפיתוח ב User Space והגיע הזמן להכיר את ה Kernel שמניע את כל המערכת, בסדרת המאמרים הקרובה אני אתמקד בפיתוח מול ה Kernel של Linux ואנסה לכסות כמה שאפשר מהתחום ההענק הזה, אז כמו תמיד המאמר הראשון הוא לצורך הכרות לפני שנצלול פנימה.

Kernel

אם חשבתם שאתם בעל הבית על המחשב שלכם אז טעיתם בגדול אתם סה"כ משתמשים פשוטים שנשמעים להוראות של בעל הבית האמיתי שנקרא Kernel, זו תוכנית שרצה בעצמה וממש לא צריכה את העזרה שלנו, המטרה שלה לגרום לכל העסק לתקתק, היא אחראית על הכל ואין דבר שנסתר ממנה (כמעט...).

שימו לב!
  • עבודה מול ה Kernel עלולה להזיק למחשב שלך, מומלץ להשתמש במכונה וירטואלית.
  • המאמר נכתב על Fedora 14.

תוכניות שרצות בעולם ה User Space צריכות לגשת לחומרה והן יכולות לעשות זאת רק בעזרת ה Kernel, לצורך העניין תוכנית שרוצה לקרוא מקובץ צריכה להפעיל פונקציה משכבת ה Api של ה Kernel שנקראת System Call שתפעיל את המנגנונים המתאימים על מנת לגשת לחומרה.





Example Module

הדוגמה הראשונה תעשה הכרות עם תהליך כתיבת ה Module וטעינה למערכת, התוכנית עצמה מאוד פשוטה מדובר על HelloWorld שרץ ב Kernel, אז חבל על הדיבורים וקדימה לעבודה, ניצור קובץ helloworld.c בעזרת gedit או כל עורך אחר ונעתיק את הקוד הבא:

#include <linux/module.h> //all kernel modules
#include <linux/init.h> //init and exit macros

int init_driver(void)
{
//write to kernel log
printk("Hello Kernel \n");
return 0;
}

void cleanup_driver(void)
{
//write to kernel log
printk("Goodbye Kernel \n");
}

//pointing to custom init function when the module loaded
module_init(init_driver);
//pointing to custom cleanup function when the module unloaded
module_exit(cleanup_driver);

על מנת שנוכל להפעיל את ה Module יש לבנות פונקציה Init ופונקציה Cleanup ולשלוח את ה Pointer שלהם ל Macros  מיוחדים - module_init ו module_exit , ברגע שנפעיל את ה Module הפונקציה Init תרוץ וכאשר נוריד את ה Module פונקצית ה Cleanup תרוץ ונוצרה התחלה וסוף עבור ה Module ,בעזרת הפקודה dmesg נוכל לראות את הפלט של printk.

Makefile

קובץ הגדרות עבור ה Compiler שאוסף Headers מתיקיית המקור של ה Linux ומשלב אותם בתוכנית, הקובץ עדין במרחקים בין השורות ויש להשים לב שהוא במבנה המתאים, לרוב נכתוב את הקובץ פעם אחת ונשתמש בו לאורך התוכנית.

obj-m := helloworld.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)

all:
$(MAKE) -C $(KDIR) M=$(PWD) modules

clean:
$(MAKE) -C $(KDIR) M=$(PWD) clean

יש לשמור את הקובץ בשם Makefile בתיקייה שמכילה את הקובץ helloworld.c ולאחר מכן נריץ את הפקודה make ב Terminal חשוב לוודא ש GCC מותקן על המכונה:
[root@localhost helloKernel]# make

התוצר הסופי הוא קובץ בסיומת (KO (Kernel Object שהוא ה Driver, על מנת שנטען אותו נשתמש בפקודה insmod והשם של ה Module.
[root@localhost helloKernel]# insmod helloworld.ko

על מנת להסיר את ה Driver יש להשתמש בפקודה rmmod והשם של ה Module.

[root@localhost helloKernel]# insmod helloworld.ko

העבודה עם insmod ו rmmod מאפשרת לטעון באופן דינמי Drivers ללא צורך של Compile חדש לכל ה Kernel והופכת את העבודה לנוחה יותר.

Character Device Driver

ב Linux קיימים 3 סוגים של (Device Driver (Character,Block,Network, החלטתי בהתחלה להתמקד ב Character Device שמאפשר קריאה וכתיבה של Char בודד בכל פעם, הוא נפוץ מאוד ויש לו שימושים רבים כמו במקלדות,עכברים, מודמים וכו, אבל לפני שנכנס לקוד צריך להכיר מספר דברים במערכת ההפעלה, יש משפט עתיק על Linux שאומר Everything Is A File , לכל רכיב חומרתי יש קובץ מייצג שדרכו ניתן לגשת לחומרה, הדגמתי באחד המאמרים בבלוג כיצד ניתן לשלוט בהתקנים בעזרת כתיבה או קריאה מקובץ באמצעות BeagleBone , נכנס לתיקייה dev/ ונרשום את הפקודה ls-l על מנת לראות את כל ההתקנים במערכת.



האות הראשונה בהרשאות ב Linux מייצגת את השיוך, כפי שניתן לראות ההתקן Loop7 מתחיל עם האות b כלומר מדובר ב Block Device ו lp0 מתחיל ב c כלומר Character Device,  קיימים 2 מספרים חשובים עבור כל התקן Major ו Minor שיחד הם מזהה יחודי עבור ה Device.

השלב הבא הוא יצירת ה Device File שמאפשר ל User Space לדבר עם ה Kernel Space כפי שנראה בהמשך, לא ניתן לייצר קובץ בספריית ה dev/ ככה סתם ויש להשתמש בפקודה מיוחדת שנקראת mknod כפי שניתן לראות בדוגמה:

[root@localhost dev]# mknod /dev/cdev c 89 1

בהרצת הפקודה נוצר קובץ בתיקיית dev/ מסוג Character Device עם מספרי ה Major וה Minor, יש הרשאה בלעדית ל Root לכתוב או לקרוא מהקובץ, ניתן לשנות את ההרשאות בעזרת הפקודה chmod, וניתן להסיר את הקובץ בעזרת פקודת rm פשוטה.

לאחר יצירת קובץ של Device File , נבנה את ה Driver שהולך ל"התלבש" על הקובץ, כל עוד שה Driver לא נטען למערכת לא ניתן לכתוב או לקרוא מהקובץ, יש לממש מספר פונקציות שדרכן נוכל להתממשק ל Device, הרעיון שלוקחים את ה Structure  של File Operations שמייצג קבוצה של פונקציות לעבודה עם קבצים ודורסים את הפונקציות עם פונקציות שלנו, כך שבכל פעם שמתבצעת קריאה לקובץ הפונקציות של ה Driver יקפצו ויעשו את העבודה.


#include <linux/module.h>
#include <linux/string.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <linux/init.h>

//must write license
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("simple character device");

#define MAX 100
#define MIN 0
static char buffer[MAX] = {MIN};

//prototype of the overridden functions
static int c_open(struct  inode * , struct file *);
static int c_close(struct  inode * , struct file *);
static ssize_t c_read(struct file *, char *,size_t,loff_t *);
static ssize_t c_write(struct file *, const char *,size_t,loff_t *);

//overridden file operations structure
static struct file_operations fops = 
{
.read = c_read,
.open = c_open,
.write = c_write,
.release = c_close,
};

//load module and override file operation structure for the file with new methods
int init_driver(void)
{
int check = register_chrdev(89,"cdev",&fops);
if(check<0){
printk("Error register device \n");}
else
{
printk("Device register complete \n");
}
return check;
}

//remove module
void cleanup_driver(void)
{
unregister_chrdev(89,"cdev");
}

static ssize_t c_read(struct file *dfile, char *buff,size_t len,loff_t *off)
{
printk("File device Read \n");
unsigned short ret;

//get the minimum number between 2 numbers
int bytes = min(MAX - (int)(*off),(int)len);
//no more bytes to read
if(bytes == MIN){
return MIN;}

//copy from kernel space address to user space address
ret = copy_to_user(buff,*off+buffer,bytes);
if(ret){
//error: bad address
return -EFAULT;
}
else{
//change pointer offset position by bytes to reads
*off = *off + bytes;
return bytes;
}
   
}

static ssize_t c_write(struct file *dfile,const char *buff,size_t len,loff_t *off)
{

  printk("File device Write \n");
unsigned short ret;

//any write reset buffer
memset(buffer,MIN,MAX);
//copy from user space address to kernel space address
ret = copy_from_user(*off+buffer,buff,len);
if(ret){
 //error: bad address
         return -EFAULT;}
else {
//change pointer offset position by length
*off = *off + len;
return len;
}
}

static int c_open(struct inode *inod, struct file *flip)
{
   printk("Device file open \n");
   return MIN;
}

static int c_close(struct inode *inod, struct file *flip)
{
   printk("Device file closed \n");
   return MIN;
}

//pointing to custom init function when the module loaded
module_init(init_driver);
//pointing to custom cleanup function when the module unloaded
module_exit(cleanup_driver);

ההתנהגות לחומרה כקובץ זה אחד היתרונות החזקים ב Kernel של Linux וזה יוצר סביבה גנרית להתממשקות כפי שניתן לראות בקוד נדרסו 4 פונקציות (Open, Close, Read, Write) ובכל רגע שתוכנית מה User Space תעבוד מול ה Device File הפונקציות יופעלו, חשוב מאוד שבשלב טעינת ה Module להתחבר ל Device File בעזרת הפונקציה register_chrdev שולחים את מספר ה Major ,שם ה Device File ואת ה File Operations Structure על מנת שנדרוס את הפונקציות,סגירת ה Device File מתבצעת בעזרת הפונקציה unregister_chrdev.


User Space

ניתן לכתוב או לקרוא מה Device File בעזרת הכלים הבסיסיים ב Console כמו cat ו echo אבל על מנת להשלים את התמונה ניצור תוכנית ב User Space שתכתוב ותקרא מהחומרה כאילו זה קובץ ב File System:

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <sys/stat.h>

#define MAX 100
int main(int args, char *argv[]) {

//the name of the application will be first
if(args == 1)
{
printf("no args...\n");
return EXIT_SUCCESS;
}

char buffer[MAX];
memset(buffer,0,MAX);

printf("input from user space:%s \n",argv[1]);

//open function in the driver execute
int fd = open("/dev/cdev", O_RDWR);

if(fd !=-1)
{
//write function in the driver execute
write(fd,argv[1],strlen(argv[1]));

//set position of the offset pointer back to beginning of file,
//function not overridden in the file operations structure
lseek(fd,0,SEEK_SET);

//read function in the driver execute
   read(fd,&buffer,strlen(argv[1]));

   //close function in the driver execute
   close(fd);

printf("output from kernel space:%s \n",&buffer);

}

return EXIT_SUCCESS;
}



סיכום

המאמר הזה הוא טיפה בים הענק והמסובך הזה שנקרא Kernel, במהלך המאמרים הבאים אני יעבור על מספר מנגנונים שנמצאים ב Kernel ועל סוגי התקנים נוספים מה שבטוח זה לא יהיה קל אבל מאוד מעניין.

מקורות מידע