PicoCTF - PowerAnalysis Part 1 / Part 2

PicoCTF - PowerAnalysis Part 1 / Part 2

Background

Simple Welcome 0x13(2023 HW - Power Analysis):two:

Recon

這一題幾乎就和上課教的差不多,只是因為有雜訊,所以要慎選trace point,我是直接看第一個trace的分布,決定採用300~400的point,而不管是利用自己刻的correlation coefficient還是用scipy的pearsonr都一樣可以順利解出key但是如果像homework一樣用numpy的corrcoef會有兩個bytes和正解不一樣,超哭,找超久(10/18更新,如果用自己刻的也是要碰用氣,所以如果可以的話,多送幾個trace,或者多用幾個算correlation coefficient的library)

  • Part 2的部分幾乎一模一樣,就只是他先幫你紀錄好所有的trace,再讓我們做後續的分析

Exploit

  • 首先先把資料從server拉下來,在存成json
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
      from pwn import *
      from string import ascii_letters, digits
      import json
      from tqdm import trange
    
    
      def gen_plaintext(length):
          return ''.join(random.choice(ascii_letters + digits) for _ in range(length))
    
    
      pt = [gen_plaintext(16) for _ in range(50)]
      print(pt)
      json_file = [None] * len(pt)
    
      for i in trange(len(pt)):
          r = remote('saturn.picoctf.net', 52339)
          r.sendlineafter(b'hex: ', pt[i].encode('utf-8').hex().encode())
          r.recvuntil(b'power measurement result:  ')
          pm = r.recvline().decode().strip()
          json_file[i] = {}
          json_file[i]["pt"] = [ord(digit) for digit in pt[i]]
          json_file[i]["pm"] = pm
    
          r.close()
    
      json_object = json.dumps(json_file)
      with open("./Crypto/PowerAnalysis- Part 1/trace.json", 'w') as outfile:
          outfile.write(json_object)
    
  • 然後再去解析AES key
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
      import json
      from tqdm import trange
      import numpy as np
      import copy
      from string import ascii_letters, digits
      from numpy import corrcoef
      import matplotlib.pyplot as plt
      from scipy.stats import pearsonr
    
      jsonFile = open('./PicoCTF/Crypto/PowerAnalysis- Part 1/trace.json', 'r')
      j = json.load(jsonFile)
    
      s_box = [
          [0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76],
          [0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0],
          [0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15],
          [0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75],
          [0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84],
          [0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF],
          [0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8],
          [0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2],
          [0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73],
          [0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB],
          [0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79],
          [0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08],
          [0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A],
          [0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E],
          [0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF],
          [0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16]
      ]
    
      def data_preprocess(json_data):
          pt_col = []
          # ct_col = []
          trace_col = []
          for bytes in range(16):
              tmp_pt_col = []
              # tmp_ct_col = []
              for trace_idx in range(len(json_data)):
                  tmp_pt_col.append(json_data[trace_idx]['pt'][bytes])
                  # tmp_ct_col.append(json_data[trace_idx]['ct'][bytes])
              pt_col.append(tmp_pt_col)
              # ct_col.append(tmp_ct_col)
          for point in range(300, 400):#len(json_data[0]['pm'])
              tmp_trace_col = []
              for trace_idx in range(len(json_data)):
                  tmp_trace_col.append(json_data[trace_idx]['pm'][point])
              trace_col.append(tmp_trace_col)
    
          return pt_col, trace_col
    
      def sbox_preprocess(pt_col):
          sbox_result_tmp = []
          for sbox_key in range(256): # 總共有256個sbox key
              tmp = []
              for trace in range(len(pt_col)): # 有50個trace
                  tmp.append(pt_col[trace] ^ sbox_key)
              sbox_result_tmp.append(tmp)
          return sbox_result_tmp
    
      def choose_sbox(sbox_result_tmp):
          sbox_result = copy.deepcopy(sbox_result_tmp)
          for sbox_key in range(256):
              for trace in range(50):
                  hex_value = '{0:0>2x}'.format(sbox_result_tmp[sbox_key][trace])
                  x, y = hex_value[0], hex_value[1]
                  sbox_result[sbox_key][trace] = s_box[int(x, 16)][int(y, 16)]
    
          return sbox_result
    
      def cal_hamming_weight(sbox_result_col):
          hw_model = copy.deepcopy(sbox_result_col)
          for i in range(len(sbox_result_col)):   # 256
              for j in range(len(sbox_result_col[i])):    # 50
                  hw_model[i][j] = bin(sbox_result_col[i][j]).count('1')
    
          return hw_model
    
      def cal_correlation(hw_model_col_result, trace_col):
          correlation_result = []
          for i in trange(len(hw_model_col_result)):#(ascii_letters + digits).encode():
              for j in range(biggest_length):#len(trace_col)
                  # correlation_result.append(corrcoef(hw_model_col_result[i], trace_col[j])[0, -1])
                  # correlation_result.append(pearsonr(hw_model_col_result[i], trace_col[j])[0])
                  correlation_result.append(run_pearson_correlation(hw_model_col_result[i], trace_col[j]))
          return correlation_result
    
      def run_pearson_correlation(x, y):
          mean_x = np.mean(x)
          mean_y = np.mean(y)
    
          covariance = np.sum((x - mean_x) * (y - mean_y))
    
          std_dev_x = np.sqrt(np.sum((x - mean_x)**2))
          std_dev_y = np.sqrt(np.sum((y - mean_y)**2))
    
          correlation = covariance / (std_dev_x * std_dev_y)
    
          return correlation
    
      def display_pt(offset:int, data_offset = (0, len(j[0]["pm"]))):
          plt.plot(range(data_offset[0], data_offset[1]), j[offset]["pm"][data_offset[0]:data_offset[1]])
          plt.savefig(fname="./PicoCTF/Crypto/PowerAnalysis- Part 1/pt_" + str(offset) + ".jpg")
          plt.clf()
    
      # display_pt(1, (0, 700))
      # display_pt(1)
      pt_col, trace_col = data_preprocess(j)
      flag = ''
      biggest_length = 100#len(trace_col)
      for idx in trange(16):
          sbox_preprocess_result = sbox_preprocess(pt_col[idx])
          choose_sbox_result = choose_sbox(sbox_preprocess_result)
          hw_model_col_result = cal_hamming_weight(choose_sbox_result)
          correlation_result = cal_correlation(hw_model_col_result, trace_col)
          key_idx = correlation_result.index(max(correlation_result))
          # flag += (ascii_letters + digits)[key_idx // biggest_length]
          from Crypto.Util.number import long_to_bytes
          flag += long_to_bytes(key_idx // biggest_length).hex()
    
    
      print('The key of AES is: ' + flag )
    

Flag: picoCTF{4999139026d84bf20427eb48d4edec53}


Exploit Part 2

為了不要讓主程式的變化太大,因此我有調整了一下data preprocessing的部分,還找到了一個bug, :::spoiler Exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
import json
from tqdm import trange
import numpy as np
import copy
from pathlib import Path
from numpy import corrcoef
import matplotlib.pyplot as plt
from scipy.stats import pearsonr
import ast

# root = "./PicoCTF/Crypto/PowerAnalysis- Part 1/"
# jsonFile = open(root + 'traces.json', 'r')
# j = json.load(jsonFile)

s_box = [
    [0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76],
    [0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0],
    [0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15],
    [0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75],
    [0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84],
    [0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF],
    [0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8],
    [0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2],
    [0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73],
    [0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB],
    [0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79],
    [0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08],
    [0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A],
    [0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E],
    [0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF],
    [0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16]
]

def data_prepreprocess():
    pts = []
    traces = []
    for f in Path("./PicoCTF/Crypto/PowerAnalysis- Part 2/traces").iterdir():
        l = f.read_text().splitlines()
        pt = bytes.fromhex(l[0].split(": ")[1])
        trace = ast.literal_eval(l[1].split(": ")[1])
        pts.append(pt.hex())
        traces.append(trace)
    return pts, traces

def data_preprocess(pts, traces):
    pt_col = []
    trace_col = []
    for bytes in range(16):
        tmp_pt_col = []
        for trace_idx in range(len(pts)):
            tmp_pt_col.append(int(pts[trace_idx][bytes*2:(bytes*2)+2], 16))
        pt_col.append(tmp_pt_col)
    for point in range(300, 400):#len(json_data[0]['pm'])
        tmp_trace_col = []
        for trace_idx in range(len(pts)):
            tmp_trace_col.append(traces[trace_idx][point])
        trace_col.append(tmp_trace_col)
    
    return pt_col, trace_col

def sbox_preprocess(pt_col):
    sbox_result_tmp = []
    for sbox_key in range(256): # 總共有256個sbox key
        tmp = []
        for trace in range(len(pt_col)): # 有50個trace
            tmp.append(pt_col[trace] ^ sbox_key)
        sbox_result_tmp.append(tmp)
    return sbox_result_tmp

def choose_sbox(sbox_result_tmp):
    sbox_result = copy.deepcopy(sbox_result_tmp)
    for sbox_key in range(256):
        for trace in range(len(sbox_result_tmp[0])):
            hex_value = '{0:0>2x}'.format(sbox_result_tmp[sbox_key][trace])
            x, y = hex_value[0], hex_value[1]
            sbox_result[sbox_key][trace] = s_box[int(x, 16)][int(y, 16)]
    
    return sbox_result

def cal_hamming_weight(sbox_result_col):
    hw_model = copy.deepcopy(sbox_result_col)
    for i in range(len(sbox_result_col)):   # 256
        for j in range(len(sbox_result_col[i])):    # 50
            hw_model[i][j] = bin(sbox_result_col[i][j]).count('1')
    
    return hw_model

def cal_correlation(hw_model_col_result, trace_col):
    correlation_result = []
    for i in trange(len(hw_model_col_result)):#(ascii_letters + digits).encode():
        for j in range(biggest_length):#len(trace_col)
            # correlation_result.append(corrcoef(hw_model_col_result[i], trace_col[j])[0, -1])
            # correlation_result.append(pearsonr(hw_model_col_result[i], trace_col[j])[0])
            correlation_result.append(run_pearson_correlation(hw_model_col_result[i], trace_col[j]))
    return correlation_result
            
def run_pearson_correlation(x, y):
    mean_x = np.mean(x)
    mean_y = np.mean(y)
    
    covariance = np.sum((x - mean_x) * (y - mean_y))
    
    std_dev_x = np.sqrt(np.sum((x - mean_x)**2))
    std_dev_y = np.sqrt(np.sum((y - mean_y)**2))
    
    correlation = covariance / (std_dev_x * std_dev_y)
    
    return correlation

def display_pt(offset:int, traces, data_offset = (0, 2666)):
    plt.plot(range(data_offset[0], data_offset[1]), traces[offset][data_offset[0]:data_offset[1]])
    plt.savefig(fname="./PicoCTF/Crypto/PowerAnalysis- Part 2/pt_" + str(offset) + ".jpg")
    plt.clf()

pts, traces = data_prepreprocess()
# display_pt(1, (0, 700))
# display_pt(1, traces, (0, 2666))
pt_col, trace_col = data_preprocess(pts, traces)
flag = ''
biggest_length = 100#len(trace_col)
for idx in trange(16):
    sbox_preprocess_result = sbox_preprocess(pt_col[idx])
    choose_sbox_result = choose_sbox(sbox_preprocess_result)
    hw_model_col_result = cal_hamming_weight(choose_sbox_result)
    correlation_result = cal_correlation(hw_model_col_result, trace_col)
    key_idx = correlation_result.index(max(correlation_result))
    # flag += (ascii_letters + digits)[key_idx // biggest_length]
    from Crypto.Util.number import long_to_bytes
    flag += long_to_bytes(key_idx // biggest_length).hex()


print('The key of AES is: ' + flag )

:::

Flag: picoCTF{b7698f76b7e524ee7cd80dbde0cdff59}