pythonでバイナリファイルを16進ダンプ ＆ バイナリファイルを画像化

# やったこと
pythonで読み込んだファイルを16進ダンプして、ついでにASCIIコード(基本的には英数字のみ)を表示するようにした。
~~pythonのプログラムなので、基本的にOSに依存しないはずです(CTFやる際にちょっと便利)。~~

```
C:\Users\User\Desktop>python read_bin.py ChromeSetup.exe

['read_bin.py', 'ChromeSetup.exe'] is exist


#### BINARY TO HEX DUMP - USING PYTHON3.6 ####

Offset 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F Encode to ASCII

000000 4d 5a 90 00 03 00 00 00 04 00 00 00 ff ff 00 00 MZ..............
000010 b8 00 00 00 00 00 00 00 40 00 00 00 00 00 00 00 ........@.......
000020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000030 00 00 00 00 00 00 00 00 00 00 00 00 18 01 00 00 ................
000040 0e 1f ba 0e 00 b4 09 cd 21 b8 01 4c cd 21 54 68 ........!..L.!Th
000050 69 73 20 70 72 6f 67 72 61 6d 20 63 61 6e 6e 6f is.program.canno
000060 74 20 62 65 20 72 75 6e 20 69 6e 20 44 4f 53 20 t.be.run.in.DOS.
000070 6d 6f 64 65 2e 0d 0d 0a 24 00 00 00 00 00 00 00 mode....$.......
000080 04 62 8c 75 40 03 e2 26 40 03 e2 26 40 03 e2 26 .b.u@..&@..&@..&
000090 f4 9f 13 26 49 03 e2 26 f4 9f 11 26 3c 03 e2 26 ...&I..&...&<..&
0000A0 f4 9f 10 26 58 03 e2 26 7b 5d e1 27 51 03 e2 26 ...&X..&{].'Q..&
0000B0 7b 5d e7 27 64 03 e2 26 7b 5d e6 27 51 03 e2 26 {].'d..&{].'Q..&
```

## 以下プログラム

```.py
import sys
import os.path
import binascii
import string

def check_file_provided():
	# 指定した先にファイルが存在するかをチェック
	default_path = "./test.exe"

	if (len(sys.argv) < 2):
		print("")
		print("Warning")
		print("Correct Usage : python read_bin.py <file_name>")
		print("")
		print("call {0}".format(default_path))
		if not os.path.isfile(default_path):
			print("")
			print("Error - The file provided does not exist")
			print("")
			sys.exit(0)
		else:
			print("{} is exist".format(default_path))
			print("")

			return default_path
	else:
		print("")
		print("{} is exist".format(sys.argv))
		print("")

		return sys.argv[1]

def read_bytes(filename, chunksize=8192):
	# バイナリファイルをバイトごとに抽出
	try:
		with open(filename, "r+b") as f:
			while True:
				chunk = f.read(chunksize)
				if chunk:
					for b in chunk:
						#print(type(b))
						#print(b)
						yield b
				else:
					break
	except IOError:
		print("")
		print("Error - The file provided can't open")
		print("")
		sys.exit(0)

def is_character_printable(s):
	## asciiの英数字・記号の文字列かどうかを判別し、真偽値を返します。
	if s < 126 and s >= 33:
		return True 
  
def print_headers():
	## とりあえずフォーマット的なものを表示する
	print("")
	print("#### BINARY TO HEX DUMP - USING PYTHON3.6 ####")
	print("")
	print("Offset 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F Encode to ASCII")
	print("")
  
def validate_byte_as_printable(byte):
	## ascii 文字列化をチェック. asciiでなければ '.' を返す ##
	if is_character_printable(byte):
		return byte
	else:
		return 46

def main():
	file_path = check_file_provided()
	memory_address = 0
	ascii_string = ""
	print_headers()



	## 読み込んだバイナリファイルを表示し終わるまでループします
	for byte in read_bytes(file_path):
		ascii_string = ascii_string + chr(validate_byte_as_printable(byte))
		if memory_address%16 == 0:
			print(format(memory_address, '06X'),end = '')
			print(" " + hex(byte)[2:].zfill(2), end='')
		elif memory_address%16 == 15:
			print(" " + hex(byte)[2:].zfill(2),end='')
			print(" " + ascii_string)
			ascii_string = ""
		else:
			print(" " + hex(byte)[2:].zfill(2), end='')
		memory_address = memory_address + 1

if __name__ == '__main__':
	main()
```

## ちょっと解説
### 16進ダンプ
[バイナリデータ](https://ja.wikipedia.org/wiki/%E3%83%90%E3%82%A4%E3%83%8A%E3%83%AA)とよばれるものをバイト単位で16進に表示させたものです。表記方法としては、一番左のカラムがアドレス、真ん中が16進、右が文字列として表示されることが多いです。

pythonでバイナリから16進の変換方法は、対象ファイルを```rb```または```r+b```で開きます。
※```r+```で対象ファイルに対して読み込み兼書き込みで開く為、```rb```と```r+b```は同じではありません。

```.py
with open(filename, "r+b") as f:
            while True:
                chunk = f.read(chunksize)
                if chunk:
                    for b in chunk:
```

このとき、```chunk```には以下のようなバイナリデータを取得することが出来ます。

```
b'MZ\x90\x00\x03\x00\x00\x00\x04\x00\x00\x00\xff\xff....
```

```for b in chunk:```にてインスタンス```b```に対して１バイトごとにデータを渡しています。何故かはわかりませんがこのとき、インスタンス```b```はint型で10進数のデータを保持している状態になります。(今後の課題とさせて頂きます。)

幸いなことに、int型で1バイトのバイナリデータを取得できたので

```.py
hex(byte)
```
で10進数のデータを16進として表示します。

### ASCIIコード
[ASCII](https://ja.wikipedia.org/wiki/ASCII)コードとはアルファベットや数字、記号などの文字コードの一つで、各文字に割り当てられた番号と対応した[対応表](http://www3.nit.ac.jp/~tamura/embedded/ascii.html)があります。

例えば、10進で77だとASCIIに対応する文字はMになります。
今回作成した16進ダンプでは、取得した1バイト分のデータ(int型の10進)が対応表33～126までのうちのどれかに対応すれば文字として表示、それ以外であれば```.```として表示するようになっています。

```.py
if s < 126 and s >= 33:
```

## 最後に宣伝
技術書典6にてサークル名**味噌トントロ定食**で「バイナリ解析入門」を出典する予定です。CTF形式で[radare2](https://rada.re/r/)を使ったでバイナリを解析の内容となります。趣味でやってる分野になりますがバイナリ解析に興味を持ってくれると嬉しいです。

## おまけ(バイナリファイルを画像化する)

以下ソースコードと出力された画像

```.py
import os
import numpy as np
from PIL import Image
import cv2

# バイナリデータ読み込み
def read_bin(filename):
  arr = [[]]
  data = []
  n = 0
  
  with open(filename, 'rb') as f:
    byte = f.read()
    #num = prime_factors(len(byte))
    num = 1000

    print(len(byte))
    j = 0
    cnt = 0
    for i in byte:
      data.append(int(i));
      if j % num == 0 :
        arr.append(data)
        data = []
        cnt+=1
        #print(cnt)
        if cnt == 1001:
          break
      j+=1

  arr = arr[2:]
  print(len(arr))

  return arr

# 画像書き込み
def write_img(arr):
  im = np.array(arr)
  pil_img = Image.fromarray(im)
  cv2.imwrite('test.jpg', im)

# 素因数分解より画像サイズを求める
def prime_factors(n):
  i = 2
  small_factors = 1
  factors = []
  while i * i <= n:
    if n % i:
      i += 1
    else:
      n //= i
      factors.append(i)
    
  if n > 1:
    factors.append(n)

  print(factors)
  for i in range(len(factors)-1):
    small_factors = small_factors * factors[i]

  if small_factors < factors[len(factors)-1]:
    print(small_factors)
    return small_factors
  elif len(factors) > 2:
    print(factors[0] * factors[len(factors)-1])
    return (factors[0]  *factors[len(factors)-1])
  else :
    return factors[0]  

def main(file_path):
  arr = read_bin(file_path)
  write_img(arr)


if __name__ == '__main__':
  file_path = './test.exe'
  main(file_path)
```

![test.jpg](https://qiita-image-store.s3.amazonaws.com/0/154154/ea07c20f-bb10-391a-78fd-b92c474d2456.jpeg)

~~機械学習させたい！！~~