יום ראשון, 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% טבעי...

יום שני, 13 ביולי 2015

MySql Proxy



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

דרישות:

שימו לב!
  • המאמר נכתב על Windows 7 64 Bit
לפני שנתחיל לצלול פנימה יש להוריד את ולפרוס את קבצי MySql Proxy באיזה מקום שתרצו על השרת, לאחר מכן יש להוריד ולהתקין את Lua, שפת סקריפטים מאוד מהירה, היא פופלרית בעיקר ב Embedded ומשחקי מחשב, היה בה שימוש רחב ב World Of Warcraft.

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

בקשה ישירה למסד הנתונים

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

Proxy

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


על מנת להפעיל את ה Proxy יש לכתוב את הפקודה הבאה:

C:\mySqlProxy\bin>mysql-proxy.exe --proxy-backend-addresses=127.0.0.1:3306 --proxy-address=127.0.0.1:4040 --proxy-lua-script=all-hooks.lua

הפקודה מחברת את שרת ה Proxy למסד הנתונים ובנוסף מוסיפים קריאה לקובץ סקריפט של Lua, מאותו רגע כל בקשה שתגיע לפורט 4040 תפעיל מספר פונקציות ונוכל לדוג אותן, לדוגמה ברגע שיבצעו שיאלתא, הפונקציה read_query תרוץ ונוכל להחליט אם להעביר את השאילתא או לא ובמקרה של תשובה מהשרת הפונקציה read_query_result תרוץ ובה נוכל לשנות את המידע שחוזר.

Hooks


-- all-hooks.lua
local access_ndx = 0

-- fired when client connect to server
function connect_server()
    print_access ('inside connect_server')
end

-- fired when starting the handshake
function read_handshake( auth )
    print_access ('inside read_handshake' )
end

-- fired when starting the authentication
function read_auth( auth )
    print_access ('inside read_auth ')
end

--fired when authentication finished
function read_auth_result( auth )
    print_access ('inside read_auth_result')
end

--fired when client disconnected
function disconnect_client()
    print_access('inside disconnect_client')
end

--fired when query requested
function read_query (packet)
proxy.queries:append(1, packet,{ resultset_is_needed = true })
return proxy.PROXY_SEND_QUERY
end

--fired when query result
function read_query_result (inj)

--create result set containes columns and rows
proxy.response.resultset = {fields = {}, rows = {}}

--checking if the query holding creditcard table name
if(string.match(inj.query,"creditcard")) then

for n = 1, #inj.resultset.fields do
-- insert column to table
table.insert(proxy.response.resultset.fields, {type
             =inj.resultset.fields[n].type,name = inj.resultset.fields[n].name})
end


for row in inj.resultset.rows do
if(row ~= nil) then
 for i,v in pairs(row) do
--looking for creditcard pattern
if string.match(v,'%d%d%d%d%-%d%d%d%d%
                                                                 -%d%d%d%d%-%d%d%d%d') then
print_access("found visa - column position: " .. i 
                                                                      .. " column value: " .. v)
row[i] = "mask credit card"
end
 end
table.insert(proxy.response.resultset.rows, row)
end
end

--overwrite results
proxy.response.type = proxy.MYSQLD_PACKET_OK
return proxy.PROXY_SEND_RESULT
end
end

-- simple print message
function print_access(msg)
    access_ndx = access_ndx + 1
    print( string.format('%3d %-30s',access_ndx,msg))
end



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

התוצאות שחוזרות מה Proxy


סיכום

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

לא תשתמש בו?

יום שבת, 7 במרץ 2015

Android Music Player



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

שימו לב!

  • המחלקה נכתבה על Android Studio.
  • המחלקה תואמת ל Api 14.
Music Search

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

    private class SongDetails {
        String songTitle = "";
        String songArtist = "";
        //song location on the device
        String songData = "";
    }

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

 private void getAllSongs()
    {
       //creating selection for the database
        String selection = MediaStore.Audio.Media.IS_MUSIC + " != 0";
        final String[] projection = new String[] {
                MediaStore.Audio.Media.DISPLAY_NAME,
                MediaStore.Audio.Media.ARTIST,
                MediaStore.Audio.Media.DATA};
        
        //creating sort by for database
        final String sortOrder = MediaStore.Audio.AudioColumns.TITLE
                + " COLLATE LOCALIZED ASC";

        //stating pointer
        Cursor cursor = null;

        try {
            //the table for query
            Uri uri = android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
            // query the db
            cursor = getBaseContext().getContentResolver().query(uri,
                    projection, selection, null, sortOrder);
            if (cursor != null) {
                
                //create array for incoming songs
                songs = new ArrayList<SongDetails>(cursor.getCount());
                
                //go to the first row
                cursor.moveToFirst();

                SongDetails details;

                while (!cursor.isAfterLast()) {
                    //collecting song information and store in array,
                    //moving to the next row
                    details = new SongDetails();
                    details.songTitle = cursor.getString(0);
                    details.songArtist = cursor.getString(1);
                    details.songData = cursor.getString(2);
                    songs.add(details);
                    cursor.moveToNext();
                    
                }
            }
        } catch (Exception ex) {

        } finally {
            if (cursor != null) {
                cursor.close();
            }
        }
    }



Simple Player

עד עכשיו היה החלק הטכני שבו אנחנו מקבלים את חומרי הגלם ועכשיו אפשר להתחיל לעבוד איתם בעזרת מחלקה מאוד פשוטה שעושה פעולות (Stop, Play, Pause, Forward, Backward, Seek) כמו כל נגן סטנדרטי , על מנת לעדכן את ה Gui נעזר ב Interface משותף כפי שניתן לראות בדוגמה:

public interface OnPlayerEventListener {
    void onPlayerComplete();
    void onPlayerStart(String Title,int songDuration,int songPosition);
}


המחלקה מקבלת את המופע של ה Activity שמפעיל אותה, מאתחלים אותה עם רשימת השרים ששלפנו מה Database ומחכים לפקודות מה Gui.

public class simplePlayer
{
    OnPlayerEventListener mListener;
    Activity mActivity;

    //give access to the gui
    public ArrayList<songDetails> songs = null;
    public boolean isPaused = false;
    public int currentPosition = 0;
    public int currentDuration = 0;

    //single instance
    public static MediaPlayer player;

   //getting gui player interface
   public  simplePlayer(Activity ma)
   {
       mActivity = ma;
       mListener = (OnPlayerEventListener) mActivity;
   }

    //initialize the player
    public void init(ArrayList<songDetails>_songs)
    {
        songs = _songs;
        currentPosition = 0;


        if(player == null)
        {
            player = new MediaPlayer();
            player.setAudioStreamType(AudioManager.STREAM_MUSIC);
            player.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
                @Override
                public void onCompletion(MediaPlayer mp) {

                    player.stop();
                    player.reset();

                    nextSong();
                    mListener.onPlayerSongComplete();

                }
            });
        }
    }

    //stop the current song
    public void stop()
    {
        if(player != null)
        {
            if(player.isPlaying())
                //stop music
                player.stop();

            //reset pointer
            player.reset();
        }
    }

    //pause the current song
    public void pause()
    {
        if(!isPaused && player != null)
        {
            player.pause();
            isPaused = true;
        }
    }

    //playing the current song
    public void play()
    {
        if(player != null)
        {
            if(!isPaused && !player.isPlaying())
            {
                if(songs != null)
                {
                    if(songs.size() > 0)
                    {
                        try {
                            //getting file path from data
                            Uri u = Uri.fromFile(new File(songs.get(currentPosition).songData));

                            //set player file
                            player.setDataSource(mActivity,u);
                            //loading the file
                            player.prepare();
                            //getting song total time in milliseconds
                            currentDuration = player.getDuration();
                          
                            //start playing music!
                            player.start();
                            mListener.onPlayerSongStart("Now Playing: "
                                    + songs.get(currentPosition).songArtist
                                    + " - "+ songs.get(currentPosition).songTitle
                                    ,currentDuration,currentPosition);
                        }
                        catch (Exception ex)
                        {
                            ex.printStackTrace();
                        }
                    }
                }
                else
                {
                    //continue playing, reset the flag
                    player.start();
                    isPaused = false;
                }
            }
        }
    }

    //playing the next song in the array
    public  void nextSong()
    {
        if(player != null)
        {
            if(isPaused)
                isPaused = false;

            if(player.isPlaying())
                player.stop();

            player.reset();

            if((currentPosition + 1) == songs.size())
                currentPosition = 0;
            else
                currentPosition  = currentPosition + 1;

            play();
        }
    }

    //playing the previous song in the array
    public void previousSong()
    {
        if(player != null)
        {
            if(isPaused)
                isPaused = false;

            if(player.isPlaying())
                player.stop();

            player.reset();

            if(currentPosition - 1 < 0)
                currentPosition = songs.size();
            else
                currentPosition = currentPosition -1;

            play();
        }
    }

    //getting new position for playing by the gui seek bar
    public void setSeekPosition(int msec)
    {
        if(player != null)
            player.seekTo(msec);
    }

    //getting the current duration of music
    public int getSeekPosition()
    {
        if(player != null)
            return  player.getDuration();
        else
            return -1;
    }
}

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

public class MainActivity extends ActionBarActivity 
implements OnPlayerEventListener {

    simplePlayer player;

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

        //getting all songs in the device
        getAllSongs();

        if (player == null) {
            //create new instance and send the listener
            player = new simplePlayer(this);
            //initialize the simplePlayer
            player.init(songs);
            //start playing the first song
            player.play();

            seekBar.setMax(player.currentDuration);
        }
    }

    @Override
    public void onPlayerSongComplete() {

    }

    @Override
    public void onPlayerSongStart(String Title, int songDuration, int songPosition) {
        this.setTitle(Title);
        seekBar.setMax(songDuration);
        seekBar.setProgress(0);
    }
}

סיכום:

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

אל תפריעו לשכנים...

יום שבת, 20 בדצמבר 2014

Teensy 3.1 - Comparison




אחת המגבלות ב Arduino היא המעבד שלו, הוא איטי ויחסית פרימטיבי אבל זול ועונה על חלק מהצרכים אבל כאשר רוצים משהו מתוחכם יותר ניתן להשתמש בגרסת Arduino Due המסיבית עם מעבד 84 Mhz או בגרסת  ה YUN עם מעבד 400MHZ ומערכת הפעלה מורכבת שלפעמים זה יותר מידי, בסה"כ רצית שהיה קטן ומהיר יותר מאשר ה 16 MHZ הקלאסי שבגרסת ה Nano.

Teensy 3.1

נחלצת לעזרתנו חברה יחסית אנונימית שלא עושה יותר מידי רעש, לחברה קוראים PJRC ויש לה סדרת לוחות תואמי Arduino, הדגם האחרון בסדרה מבוסס על מעבד Arm Cortex M-4 שיכול להגיע למהירות של 96 MHZ ועם כל מיני תוספות:
  • CPU: Cortex M-4 72/(o.c)96 Mhz 
  • Flash Memory:256KB
  • RAM: 64 kb
  • Memory Access Chanel:16
  • Digital I/O: 34, 3.3v,5V Tolerant
  • Analog Inputs: 21
  • Interfaces: USB x 1, SPI x 1, CAN x 1,UART x 3,I2S x 2
https://www.pjrc.com/teensy/teensy31.html



כל העסק קטן יותר מ Arduino Nano, וגם מחיר לא רע של 20 דולר ליחידה שאפשר לרכוש ב SparkFun, בנוסף הוא יכול לחקות התקני HID כמו עכבר ומקלדת, חשוב להוריד את התוסף של הלוח לסביבת Arduino מאתר היצרן.

שימו לב!
  • התוסף ירוץ רק על הגרסה 1.0.5 של ה Arduino IDE.
  • אין לחבר מתח מעל 5.5v.
הגודל כן קובע


הלוח תומך בספריות הרגילות של Arduino ככה שלא צריך להסתבך יותר מידי, לצורך ההשוואה אני הולך לחבר מסך TFT בדומה למאמר האחרון, ולהדגים את הבדלי הביצועים בניהם, נשתמש בספריית UTFT ונפתח את הדוגמה עבור המסך  UTFT_Demo_320x240_Serial תחת הספרייה Arduino (ARM) + Teensy.

Teeny 3.1 Pinouts

//11-> SDI
//13 ->SCK
//10 -> CS
//8 -> RST
//9 ->DC
UTFT myGLCD(TFT01_22SP,11,13,10,8,9);




Results

Arduino Nano 16 Mhz full animation
2.39 min

Teensy 3.1 96 Mhz full animation
37 sec

סיכום

לוח קטן, זול ואלטרנטיבה נוחה ל Arduino למי שצריך מהירות מבלי לשנות את הקוד או את סביבת העבודה, אומנם מדובר בלוח יחסית אנונימי ביחס ל Arduino אבל אם מחפשים טוב אפשר למצוא מוצרים שמבוססים עליו כמו Phantom Keyboard Kit ועוד המון פרוייקטים נוספים.


Arduino Killer?

יום שבת, 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 ועוד.

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


מקורות