Core dump

〜戦わなければ生き残れない〜

SYNスキャンをするプログラム

HACKING:美しき策謀を読んでいるのですが、この本のネットワークの章で取り扱われてるlibnetライブラリは古いバージョンの1.0で、現在入手できるバージョンのものとは互換性がないようです。

構造体の名前や関数名とその仕様が一新されている部分が多く、この本の通りにプログラムを実装することはできません。

なので新しいバージョンのlibnetの使い方を調べた上で、今回は本の中でも紹介されているSYNスキャンを行うプログラムを作成しようと思います。SYNスキャンはターゲット上の特定のポートにSYNパケットを送り、SYN/ACKが帰ってきたらそのポートが空いていると判断するというものです。
通常通りの3wayハンドシェイクの流れですね。
今回のプログラムでは一応最後にRSTパケットを送信していますが、私の勉強不足もあり、この処理が有効に作用するものであるか正直自身がありません。

そのような者のプログラムですので、拙いものになりますが、あしからず。

#include<stdio.h>
#include<libnet.h>
#include<pcap.h>

void usage(char *name){
	fprintf(stderr, "usage: %s <target IP> <target port> <device>\n", name);
}

//libnetコンテキストを破棄
void quit(libnet_t *l){
	libnet_destroy(l);
	printf("quit\n");
	exit(0);
}

//受信したパケットにACKフラグが立っているか確認
int check_ctrflag(const u_char *header_start){
	u_int header_size;
	const struct libnet_tcp_hdr *tcp_header;
	uint8_t ctrflag;

	tcp_header = (const struct libnet_tcp_hdr *)header_start;
	if(tcp_header->th_flags & TH_ACK)
		return 1;
	else
		return 0;
}

void pcap_fatal(const char *failed_in, const char *errbuf){
	printf("fatal error: %s: %s\n", failed_in, errbuf);
	exit(1);
}

//キャプチャしている間に表示するスピナー(全く必要ない処理ですが何かカッコイイのでつけました)
void efect(int second){
	int cnt = 0;
	char mark[5] = {'|', '/', '-', '\\'};
	printf("capturing packet....");
	for(cnt=0; cnt<second; cnt++){
		//\033[?25l はカーソルを非表示にする
		printf("\033[?25l");
		//\e[1D でカーソルの左一文字を削除し、スピナーを上書きする 
		fprintf(stdout, "%c\e[1D", mark[cnt%4]);
		fflush(stdout);
		usleep(80000);
	}
	// \e[2K\r で一行を削除し、行頭までカーソルを移動
	fprintf(stdout, "\e[2K\r");
	fflush(stdout);
	// \033[?25h でカーソルの表示を復活
	printf("\n\033[?25h");
}


int main(int argc, char *argv[]){
	int flag = 0;
	libnet_t *l;
	char libnet_errbuf[LIBNET_ERRBUF_SIZE];
	uint32_t dstip;
	u_int16_t dport;
	libnet_ptag_t tcp = 0;
	libnet_ptag_t ip = 0;

	struct pcap_pkthdr header;
	const u_char *packet;
	char pcap_errbuf[PCAP_ERRBUF_SIZE];
	char *device = argv[3];
	pcap_t *pcap_handle;

	if(argc != 4){
		usage(argv[0]);	
		exit(0);
	}

	//syn packetを作る
	l = libnet_init(LIBNET_RAW4, "enp0s8", libnet_errbuf);
	if(l==NULL){
		fprintf(stderr, "Error opening context: %s", libnet_errbuf);
		exit(1);
	}

	dstip = libnet_name2addr4(l, argv[1], LIBNET_DONT_RESOLVE);
	dport = (u_int16_t)atoi(argv[2]);


	tcp = libnet_build_tcp(
			libnet_get_prand(LIBNET_PRu16),
			dport,
			libnet_get_prand(LIBNET_PRu32),
			libnet_get_prand(LIBNET_PRu32),
			TH_SYN,
			libnet_get_prand(LIBNET_PRu16),
			0,
			0,
			libnet_get_prand(LIBNET_PRu16),
			0,
			0,
			l,
			0);

	if(tcp == -1){
		fprintf(stderr, "Unable to build TCP header: %s\n", libnet_geterror(l));
		exit(1);
	}


	ip = libnet_autobuild_ipv4(
			libnet_get_prand(LIBNET_PRu16),
			IPPROTO_TCP,
			dstip,
			l);

	if(ip == -1){
		fprintf(stderr, "Unable to build IP header: %s\n", libnet_geterror(l));
		exit(1);
	}

	pcap_handle = pcap_open_live(device, 4096, 0, 0, pcap_errbuf);
	if(pcap_handle == NULL)
		pcap_fatal("pcap_open_live", pcap_errbuf);

	if(libnet_write(l) == -1){
		fprintf(stderr, "Unable to send packet: %s\n", libnet_geterror(l));
		exit(1);
	}

	//1個目のパケットは自分が送ったパケットなので、2個目のパケットを受信
	packet = pcap_next(pcap_handle, &header);
	efect(30);
	packet = pcap_next(pcap_handle, &header);
	int count =0;
	
	flag = check_ctrflag(packet+34);
	if(flag == 1){
		printf("this port is open!\n");

		//RSTパケットを送信
		//正直ここはいらないかもしれない
		tcp = libnet_build_tcp(
			libnet_get_prand(LIBNET_PRu16),
			dport,
			libnet_get_prand(LIBNET_PRu32),
			libnet_get_prand(LIBNET_PRu32),
			TH_RST,
			libnet_get_prand(LIBNET_PRu16),
			0,
			0,
			libnet_get_prand(LIBNET_PRu16),
			0,
			0,
			l,
			0);

		if(tcp == -1){
			fprintf(stderr, "Unable to build TCP header: %s\n", libnet_geterror(l));
			exit(1);
		}


		ip = libnet_autobuild_ipv4(
			libnet_get_prand(LIBNET_PRu16),
			IPPROTO_TCP,
			dstip,
			l);

		if(ip == -1){
			fprintf(stderr, "Unable to build IP header: %s\n", libnet_geterror(l));
			exit(1);
		}

		if(libnet_write(l) == -1){
			fprintf(stderr, "Unable to send packet: %s\n", libnet_geterror(l));
			exit(1);
		}
	}
	else
		printf("this port is closed :(\n");	
	
	

	pcap_close(pcap_handle);
	libnet_destroy(l);
	return 0;
}

libnetでパケットを送信するにはまず、libnet_init関数でlibnet_t型のlibnetコンテキストを作成しなければならないようです。
deviceにはインターフェース名を指定してください。(ifconfigでみれます)
その後、TCPヘッダ->IPヘッダの順にヘッダを作ります。
これにはlibnet_build_tcpやlibnet_build_ip関数を使用します。この関数にはそれぞれのヘッダ要素に値を設定していきますが、今回はほとんどの要素にlibnet_get_prand関数でランダムな値を設定しています。
ヘッダを作ったらlibnet_write関数で実際にパケットを送信します。エラーが発生すると-1を返すようです。

次はlibcapライブラリを使ってターゲットからの応答をキャプチャします。
libcapについては本で使われているバージョンと同じ記法で使えるようなので苦労しませんでした。なので省きます。
キャプチャしたパケットの先頭から14(etherヘッダの長さ) + 20(ipヘッダの長さ) = 34バイト分進んだ部分から最後までをTCPヘッダとして扱います。これは、libnetが用意するlibnet_tcp_hdr型に変換することで行います。
そしてlibnet_tcp_hdr構造体のth_flags要素とTH_ACKを&演算子で比較し、ACKフラグが立っているか確認します。

以上がSYNスキャンプログラムになります。このような処理はnmap -sSで簡単に行えますが、自分で作ってみると色々学べるものがあります。 今後も色々作って行こうと思います。

ここまで見ていただき、ありがとうございました。

ksnctfの回答: Easy Cipher

最近ksnctfを解き始めた.

ctfには以前から興味があったが、ガチ勢の雰囲気に萎縮して距離を置いていた.

しかし, やはりセキュリティの勉強はしていきたいし, 書籍での勉強だけではなかなかやった気にならんということでctfを始める.

 

参考までに私のスキルを以下に記す.

●プログラミング:

 主にJava (たまにCを使う).

 一応基本文法と簡単なアルゴリズムの勉強をしている程度.

 

●セキュリティ知識:

 用語とその内容は知っている(情報処理技術者試験とかで勉強した程度).

 システムの脆弱性をついたクラッキングなどもちろんできない.

 

本題:

●Easy Cipher

 問題文:

 "EBG KVVV vf n fvzcyr yrggre fhofgvghgvba pvcure gung ercynprf n yrggre jvgu gur yrggre KVVV yrggref nsgre vg va gur nycunorg. EBG KVVV vf na rknzcyr bs gur Pnrfne pvcure, qrirybcrq va napvrag Ebzr. Synt vf SYNTFjmtkOWFNZdjkkNH. Vafreg na haqrefpber vzzrqvngryl nsgre SYNT. "

 

 問題文がChipherとなっているし, 何かの暗号文だろう.

 暗号化手法はEasyなものらしいからシーザー暗号かな?ってことでまずはシーザー暗号として対処してみる.

 シーザー暗号ってのは文字列を構成する文字達を何文字かずつずらすことで暗号化するやつだ.

 問題は何文字ずらすのかということ.

 

 この手の暗号は"a"に注目するのだという(前になんかの本で読んだ).

 "英文でアルファベット一文字だけが独立しているのは前置詞の"a"くらいだから"みたいな論調だったはず.

 よってアルファベット一文字だけが独立している部分を探す.

 1行目に"ercynprf n yrggre"という部分がある.

 おそらくこの"n" が暗号化された"a"であろうと予想.

 "a" → "n"ってことはアルファベット順で考えて13文字づつずらしたのか.

 よって問題文の文字を全て13文字ずつずらせば復号できそうだ.

 

 インターネットにはこの手の初歩的な暗号を解読できる便利なサイトがあることだろう.

 しかし私は上記のとおりプログラミングも勉強中だ.

 よって今回はこの初歩的な暗号を解くプログラムを書くことにした.

 

 書いたプログラムは以下の2つ.

  ●文字列を小文字化した上でシーザー暗号を解くやつ(Ceaser.java)

  ●取得したflagを大文字化するやつ(Lower_to_Upper.java)

●Ceaser.java

import java.util.Scanner;
import java.util.HashMap;

public class Ceaser{
    public static void main(String[] args){
        
        HashMap<String, Integer> alphabet = new HashMap<String, Integer>();
        alphabet.put("a",1);
        alphabet.put("b",2);
        alphabet.put("c",3);
        alphabet.put("d",4);
        alphabet.put("e",5);
        alphabet.put("f",6);
        alphabet.put("g",7);
        alphabet.put("h",8);
        alphabet.put("i",9);
        alphabet.put("j",10);
        alphabet.put("k",11);
        alphabet.put("l",12);
        alphabet.put("m",13);
        alphabet.put("n",14);
        alphabet.put("o",15);
        alphabet.put("p",16);
        alphabet.put("q",17);
        alphabet.put("r",18);
        alphabet.put("s",19);
        alphabet.put("t",20);
        alphabet.put("u",21);
        alphabet.put("v",22);
        alphabet.put("w",23);
        alphabet.put("x",24);
        alphabet.put("y",25);
        alphabet.put("z",26);

        HashMap<Integer, String> re_alphabet = new HashMap<Integer, String>();
        re_alphabet.put(1,"a");
        re_alphabet.put(2,"b");
        re_alphabet.put(3,"c");
        re_alphabet.put(4,"d");
        re_alphabet.put(5,"e");
        re_alphabet.put(6,"f");
        re_alphabet.put(7,"g");
        re_alphabet.put(8,"h");
        re_alphabet.put(9,"i");
        re_alphabet.put(10,"j");
        re_alphabet.put(11,"k");
        re_alphabet.put(12,"l");
        re_alphabet.put(13,"m");
        re_alphabet.put(14,"n");
        re_alphabet.put(15,"o");
        re_alphabet.put(16,"p");
        re_alphabet.put(17,"q");
        re_alphabet.put(18,"r");
        re_alphabet.put(19,"s");
        re_alphabet.put(20,"t");
        re_alphabet.put(21,"u");
        re_alphabet.put(22,"v");
        re_alphabet.put(23,"w");
        re_alphabet.put(24,"x");
        re_alphabet.put(25,"y");
        re_alphabet.put(26,"z");

        boolean choice = false;
        Scanner sc = new Scanner(System.in);
        System.out.println("暗号文を入力してください:");
        String crypted_text = sc.nextLine();
        crypted_text = crypted_text.toLowerCase();
        System.out.println("\n============================");
        System.out.println("解読する文字列:");
        System.out.println(crypted_text);
        System.out.println("==============================");
        String[] str_array = crypted_text.split("");
        System.out.print("\nずらす文字数を入力してください:");
        int shift_num = sc.nextInt();
        for(int i=0; i<str_array.length; i++){
            if(alphabet.get(str_array[i]) == null)
                continue;
            else{
                int num = alphabet.get(str_array[i]);
                if(num-shift_num > 0)
                    str_array[i] = re_alphabet.get(num-13);
                else
                    str_array[i] = re_alphabet.get(num-13 + 26);
            }
        }
        String flag = comb_string(str_array);
        System.out.println("\n============================");
        System.out.println("解読された文字列:");
        System.out.println(flag);
        System.out.println("==============================");

        System.out.println("\nBye");
    } 


    static String comb_string(String[] str_array){
        String out = "";
        for(String x: str_array)
            out+= x;
        return out;
    }
}

やってることは単純だ.

単純なのにこんなに長いコードになったのはJavaと私のせいだ(Java : 私 = 6 : 4 くらいの割合でJavaが悪い).

HashMap(Javaの辞書)は値からキー値を参照することができない. このせいでHashMapを二つ作るはめになったのだ.

まあいいや. まず, 暗号文と動かす文字数を入力で受付け, それぞれ文字列と整数として格納. このとき, 文字列を小文字にする.(大文字まで対応するとなると, 大文字に対応した辞書をつくらないといけないから. 大文字の部分を元に戻すのは後に示す別の関数で対応することにした)

その後文字列を一文字ずつ切り離し, 配列に格納.

そして, 配列に格納された文字を一文字ずつ取り出し, 事前にHashMapとして登録しておいたアルファベットとその番号の表を参照してその番号を確認.

その番号から13を引いた番号に対応するアルファベットに置き換える.

これで配列の要素の入れ替えは完了したので, 最後に配列の要素を一文字ずつ取り出し, 文字列として結合して終わり.

出力された結果を以下に示す.

●得られた復号文

rot xiii is a simple letter substitution cipher that replaces a letter with the letter xiii letters after it in the alphabet. rot xiii is an example of the caesar cipher, developed in ancient rome. flag is flagswzgxbjsamqwxxau. insert an underscore immediately after flag.

13文字ずつずらすこの暗号はrot13というもので, 古代ローマ時代に...フラグはflagswzgxbjsamqwxxauだよ. flagの後ろに"_"をつけてね とある.

この段階で"flag_swzgxbjsamqwxxau"を送信したところダメだった. やはり大文字部分を元に戻さないといけないか.

よって今度はこれをやるプログラムを書く.

●Lower_to_Upper.java

import java.util.Scanner;
public class Lower_to_Upper{
    public static void main(String[] args){
        Scanner sc = new Scanner(System.in);
        System.out.println("参照する文字列を入力してください:");
        String ref = sc.nextLine();
        System.out.println("\n============================");
        System.out.println("参照する文字列:");
        System.out.println(ref);
        System.out.println("============================");
        
        System.out.println("対象の文字列を入力してください:");
        String flag = sc.nextLine();
        System.out.println("\n============================");
        System.out.println("対象の文字列:");
        System.out.println(flag);
        System.out.println("============================");
        
        System.out.println("\n============================");
        System.out.println("返還後の文字列:");
        System.out.println(conv_string(ref, flag));
        System.out.println("============================");
        System.out.println("\nBye");
    }


    static String conv_string(String ref, String flag){
        char[] ref_string = ref.toCharArray();
        String[] flag_string = flag.split("");
        int count = 0;

        for(char buf: ref_string){
            if(Character.isUpperCase(buf)){
                flag_string[count] = flag_string[count].toUpperCase();
                count++;
            }
            else
                count++;
        }
        String out = "";
        for(String buf: flag_string)
            out += buf;
        return out;
    }
}

復号する前の文字列からフラグの部分を抜き出すと, "SYNTFjmtkOWFNZdjkkNH"となっている.

この文字と"flagswzgxbjsamqwxxau"を比較し, 復号前の文字列で大文字になっている部分に対応する文字を大文字化するとよさそう.

二つの文字列をそれぞれ配列に格納することで互いに同じ位置の文字の比較を行う.

配列をChar と Stringに分けているのは, isUpperCase()という文字が大文字であるか判定するメソッドがChar型しか使えないからだ

このプログラムによって出力された結果を以下に示す.

●得られた文字列

FLAGSwzgxBJSAMqwxxAU

あとはFLAGの後ろに"_"をつけろとあったので, "FLAG_SwzgxBJSAMqwxxAU"を送信すると正解したようだ. (CTFの回答ブログでは答えをそのまま書くのがタブーなのか, どのサイトもフラグの部分だけ隠してあったが, 今回はかなり初歩的な問題だし, いまさらだよねってことでそのまま載せます)

●感想

自前のプログラムを書いてたからかなり時間がかかったが, プログラムの練習にはなった.

これからは気になった問題だけピックアップし, 回答をのっけようと思う. 疲れた.

ブログ始めました

初めまして.

普段は情報学部で学んでいますが, わからないことだらけで狂いそうなので学んだことを備忘録として綴っていきます.

テーマとかはとくに絞ってません. OSとかセキュリティとか, どれもまだ未熟だけど色々学んでいこうと思っています.

 

とりあえずすぐ止めないように頑張ろう