sakのノート

興味のあることについて色々と

iPhoneのアクティベーションロックをバイパスしてiPodとして使えるようにする方法

iOSアクティベーションロックはAndroidとは違って強固で、ネットで中古品を買ってロックがかかってしまうと普通は文鎮になってしまいますが、今回はそのアクティベーションロックをいい感じに突破してiPodとして使えるようにする方法を紹介していきたいと思います。

注意点

以下の点をよく読んでオッケーという方のみ次へ進んでください。

  • 今回紹介する方法では脱獄を行う
  • A11チップまでのモデルが対象(iPhoneXs~は不可)
  • SIM, 電話, icloudなどが使えない(脱獄をすると使えなくなるため)
  • 本体を再起動すると再度ロックがかかるため、基本的に再起動はできない(再脱獄すればまた使えるようになります)
  • ここで紹介している内容は、全て自己責任で行ってください

準備するもの


アクティベーションロックのかかったiPhoneヤフオクで安く売られており、まだまだ現役で使えるiPhone6sでも1000~2000円で買えたりするので、興味のある方は買ってみてはいかがでしょうか?


f:id:sak-39:20201128011320p:plain

ちなみに、実験専用に買う場合はiPhone6がおすすめです

1. LinuxのLive USBの作成

今回はUbuntuを使用します。
Ubuntuの公式サイトubuntu.com
からDesktop版のisoをダウンロードし、rufusbalenaEtcherなどを使ってUSBにインストールします。

2. checkra1nのインストール

事前に作成したUbuntuのLive USBを起動します。
今回はとりあえずOSのインストールは行わずにTry UbuntuでLiveのまま続行します。

起動後、firefoxからcheckra1nの公式サイトを開きます。
Latest ReleaseのSee all releasesへ進み、Download for Linux (CLI, x86_64) からダウンロードします(執筆時点では0.12.1 beta)
f:id:sak-39:20201128012337p:plain

ダウンロードが完了したら、checkra1nをダウンロードしたフォルダから右クリックでターミナルを開き、

chmod +x ./checkra1n
を実行します。

3. iPhoneの脱獄

iPhoneをPCに接続し、

sudo ./checkra1n
でcheckra1nを起動します。

ここで、iPhone8やXを脱獄させる場合は [options] から下の2つの項目にxを入れてください。

  • Allow untested iOS/iPadOS/tvOS versions
  • Skip A11 BPR check

f:id:sak-39:20201128114131p:plain

起動後は指示にしたがって操作し、All doneと出れば完了です。
ここで注意ですが、脱獄が完了してもiPhoneはPCと接続したままにしてください。外してしまった場合は、再度脱獄を行ってください。

再起動し、次はWindowsを起動します。

4. アクティベーションロックのバイパス

Windowsで、Sliver 12.4.7 を起動します。
[Bypass iOS 12.4.7] を押し、しばらく待ちます。
successというメッセージが出たら、Wi-Fiの設定画面で "iTunes(またはPC)に接続" を押し、アクティベーションの画面をすり抜けられれば成功です。(これ以降はiPhoneはPCから外してしまって大丈夫です)
後は適当に初期設定を済ませます。

上手くいくと下の画像のようにホーム画面が開き、iPodのような感じで使えるようになります。

f:id:sak-39:20201128031052p:plain

途中で失敗したときは

iTunesを使うとOSを修復して再度初期状態からやり直せるので、うまくバイパスできないときや、リンゴループになってしまった場合には試してみてください。

おまけ

アプリのインストールについて

脱獄を行うと通常の方法ではサインインできなくなるため、アプリのインストールもできません。
しかし、App Storeを経由して以下の手順を踏むとサインインすることができ、アプリがインストールできるようになります。

  • App Storeを開き、右上の青いアイコンを押してアカウントの設定画面を開きます。

f:id:sak-39:20201128031159p:plain

  • ここの画面からはサインインをすることができます。

f:id:sak-39:20201128031225j:plain

サインインすると、登録したApple IDがダウンロードしたことのあるアプリはインストールすることができるようになります。

バッテリーを長持ちさせるコツ

使っていないときは、

  • 機内モードオン
  • Wi-Fi オフ
  • バッテリー -> 低電力モード オン
  • バッテリー -> バッテリーの状態 -> 最適化されたバッテリー充電 オフ (バッテリー最大まで充電するため)

と設定しておくと数日間は持ちます。(バッテリーの劣化具合により個体差あり)

NITAC miniCTF writeup

明石高専で開催されたminiCTFにチームBriskNinjaで参加しました。
結果は得点を獲得した32チーム中11位でした。
今回はある程度の問題数を解くことができたのでWriteupを書こうと思います。

[Binary 100] signature

ELFのファイルが渡されます。
問題文には開けないと書いてあったのでバイナリエディタで開いてみると、どうやらスクリーンショットの画像ファイルのようです。
そこで、マジックナンバーpngのものに書き換えてみたところフラグが得られました。
f:id:sak-39:20200126163557p:plain

NITAC{dr4win9}

[Crypto 100] base64

以下の暗号文が渡されます。

TklUQUN7RE9fWU9VX0tOT1dfQkFTRTY0P30K

普通にbase64デコードをしたらフラグが得られました。
www.base64decode.org

NITAC{DO_YOU_KNOW_BASE64?}

[Network 100] Teacher's Server

pcapngファイルが渡されます。
しばらくパケットを読んでいると、怪しい文字列が見つかりました。

flag: JZEVIQKDPNEVGQKPL5EVGX2NIFKEQRKNIFKESQ2JIFHH2===

あとはこれをデコードしたらフラグが得られました。

NITAC{ISAO_IS_MATHEMATICIAN}

[Network 100] JWT_auth

この問題もpcapngが渡されます。
パスワードの部分を読むと、上のものからヒントが順番に書かれているので、それに従い以下の内容を送信したらフラグが得られました。

username: admin
password: qwerty
verification code: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1ODAwMjIxNjMsImlhdCI6MTU4MDAyMTg2MywibmJmIjoxNTgwMDIxODYzLCJpZGVudGl0eSI6NX0.GXGHSkBaOsgw0pRfwLW-M53LZk7TCz9fSWr53LDQP1Y
NITAC{usin9_0n1y_jwt_is_uns3cur3}

[Web 100] Admin Portal 1

問題ページのソースなどが渡されます。
試しにlogin.phpからregister.phpへ飛び、エラーメッセージを出した後にLoginしてみたところ、フラグが得られました。(適当すぎてごめんなさい)

NITAC{00f_r3g1str4t10n_st1ll_w0rks}

[Misc 200] taiwan

この問題では1枚の画像ファイルが渡されます。f:id:sak-39:20200126172906j:plain
問題文を見た感じ台湾のどこかのようです。
最初にTwitterで作問者の方などが同じような画像を上げてないか見てみましたが、空振りでした。

次にGoogleの画像検索にかけてみましたが、関係ない画像ばかりでこれも空振りでした。

ここでちょっと方法を変えて、画像を下のように加工してみます。
f:id:sak-39:20200126175253j:plain

このカラフルなポール、結構特徴的なものなのでひっかかるかなと思い、Googleと今度はYandexにも投げてみたところ、Yandexのほうで見事に当たりました。

f:id:sak-39:20200126175620p:plain
下の部分がレンガになっているものは全部同じ場所のものですね。

ここで引っかかった画像をさらに検索にかけていくと、徐々に場所を絞り込めます。

f:id:sak-39:20200126180055p:plain

ここからブログなどに飛んで情報を集めていき、最終的に下のように場所が割り出せました。

f:id:sak-39:20200126180647p:plain

あとはここに書いてある場所をGoogleストリートビューでお散歩すればフラグが得られます。

f:id:sak-39:20200126181205p:plain

f:id:sak-39:20200126181227p:plain

ありました!
ということでフラグは"ATP1"です。

NITAC{ATP1}

感想

今回のCTFは初心者から上級者まで楽しめるちょうど良い難易度だったのでとても良かったです。来年も開催されるならぜひ参加したいですね。

ただ、Webが1問しか解けなかったのが悔しい。。

高専セキュリティコンテスト 2019 Writeup

10/26に開催された高専セキュリティコンテストに参加しました。
残念ながらあまり問題を解くことができませんでしたが、復習も兼ねてWriteupを書きます。

03 [Crypto100] 20回もやれば、暗号と認めてやっても良いだろう

問題文でかなり長い暗号化(?)された文字列が渡されます。
問題名と暗号文からBase64と推測できるので、以下のサイトで20回デコードしてみたらフラグが得られました。
www.base64decode.org

CTFKIT{20_times_base64!}

06 [Binary 100] 無言のELF

ELFのバイナリが渡されます。
stringsで見てみると、"kosensecuritycontest2019"の文字列が見えたので実行時にこれを入力してみたらフラグが得られました。

CTFKIT{hpb5iphbr_et3phet5o} 

09 [Misc 50] プログラミン言語を当てよう

何の言語かなぁと問題を見てみると今話題のBlawnでした。
(問題を忘れたので情報量ゼロですみません。。)

CTFKIT{BLAWN}

12 [NetWork 50] 大人たちの無意味な慣習

pcapファイルが渡されたのでWiresharkで見てみると、zipファイルがあったので取り出してみましたが鍵がかかっていました。
もう一度パケットを見直すと、"zlap7b0p"というパスワードくさい文字列があったので打ち込んでみたら無事解除できてフラグが得られました。

CTFKIT{majide_muimina_shuukan_dayonee}

14 [Forensics 100] お茶をさぐれ

apkファイルが渡されます。
とりあえず展開して、classes.dexをdex2jarでjarファイルに変換しjdで見てみると、LoginActivity.classが怪しいです。

package com.example.ureshino.ctfmondai_android;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.annotation.TargetApi;
import android.app.LoaderManager;
import android.app.LoaderManager.LoaderCallbacks;
import android.content.CursorLoader;
import android.content.Loader;
import android.content.res.Resources;
import android.database.Cursor;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build.VERSION;
import android.os.Bundle;
import android.provider.ContactsContract.Profile;
import android.support.annotation.NonNull;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.view.KeyEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewPropertyAnimator;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;
import java.util.ArrayList;
import java.util.List;

public class LoginActivity
  extends AppCompatActivity
  implements LoaderManager.LoaderCallbacks<Cursor>
{
  private static final String[] DUMMY_CREDENTIALS = { "herfuvab@sr2b3.wc:tenaoyhr" };
  private static final int REQUEST_READ_CONTACTS = 0;
  private UserLoginTask mAuthTask = null;
  private AutoCompleteTextView mEmailView;
  private View mLoginFormView;
  private EditText mPasswordView;
  private View mProgressView;
  
  private boolean LoginTask(String paramString1, String paramString2)
  {
    String[] arrayOfString1 = DUMMY_CREDENTIALS;
    int j = arrayOfString1.length;
    boolean bool = false;
    int i = 0;
    while (i < j)
    {
      String[] arrayOfString2 = nyaho(arrayOfString1[i]).split(":");
      if (arrayOfString2[0].equals(paramString1)) {
        bool = arrayOfString2[1].equals(paramString2);
      }
      i += 1;
    }
    return bool;
  }
  
  private void addEmailsToAutoComplete(List<String> paramList)
  {
    paramList = new ArrayAdapter(this, 17367050, paramList);
    this.mEmailView.setAdapter(paramList);
  }
  
  private void attemptLogin()
  {
    if (this.mAuthTask != null) {
      return;
    }
    this.mEmailView.setError(null);
    this.mPasswordView.setError(null);
    Object localObject3 = this.mEmailView.getText().toString();
    String str = this.mPasswordView.getText().toString();
    int j = 0;
    Object localObject2 = null;
    int i = j;
    Object localObject1 = localObject2;
    if (!TextUtils.isEmpty(str))
    {
      i = j;
      localObject1 = localObject2;
      if (!isPasswordValid(str))
      {
        this.mPasswordView.setError(getString(2131558449));
        localObject1 = this.mPasswordView;
        i = 1;
      }
    }
    if (TextUtils.isEmpty((CharSequence)localObject3))
    {
      this.mEmailView.setError(getString(2131558446));
      localObject1 = this.mEmailView;
      i = 1;
    }
    else if (!isEmailValid((String)localObject3))
    {
      this.mEmailView.setError(getString(2131558448));
      localObject1 = this.mEmailView;
      i = 1;
    }
    if (i != 0)
    {
      ((View)localObject1).requestFocus();
      return;
    }
    if (LoginTask((String)localObject3, str))
    {
      localObject1 = (TextView)findViewById(2131230792);
      ((TextView)localObject1).setVisibility(0);
      localObject2 = new StringBuilder();
      ((StringBuilder)localObject2).append(f());
      ((StringBuilder)localObject2).append(l());
      ((StringBuilder)localObject2).append(o());
      ((StringBuilder)localObject2).append(g());
      ((StringBuilder)localObject2).append(g2());
      ((StringBuilder)localObject2).append(e());
      ((StringBuilder)localObject2).append(h());
      ((StringBuilder)localObject2).append(i());
      ((StringBuilder)localObject2).append(j());
      ((StringBuilder)localObject2).append(k());
      ((StringBuilder)localObject2).append(l2());
      ((StringBuilder)localObject2).append(n());
      ((StringBuilder)localObject2).append(o2());
      localObject2 = ((StringBuilder)localObject2).toString();
      localObject3 = new StringBuilder();
      ((StringBuilder)localObject3).append("CTFKIT{");
      ((StringBuilder)localObject3).append(nyaho((String)localObject2));
      ((StringBuilder)localObject3).append("}");
      ((TextView)localObject1).setText(((StringBuilder)localObject3).toString());
      return;
    }
    this.mEmailView.setError("Login Failed.... ((((((((((������)�� ");
    ((TextView)findViewById(2131230792)).setVisibility(4);
    this.mEmailView.requestFocus();
  }
  
  private boolean isEmailValid(String paramString)
  {
    return paramString.contains("@");
  }
  
  private boolean isPasswordValid(String paramString)
  {
    return paramString.length() > 4;
  }
  
  private boolean mayRequestContacts()
  {
    if (Build.VERSION.SDK_INT < 23) {
      return true;
    }
    if (checkSelfPermission("android.permission.READ_CONTACTS") == 0) {
      return true;
    }
    if (shouldShowRequestPermissionRationale("android.permission.READ_CONTACTS"))
    {
      Snackbar.make(this.mEmailView, 2131558459, -2).setAction(17039370, new View.OnClickListener()
      {
        @TargetApi(23)
        public void onClick(View paramAnonymousView)
        {
          LoginActivity.this.requestPermissions(new String[] { "android.permission.READ_CONTACTS" }, 0);
        }
      });
      return false;
    }
    requestPermissions(new String[] { "android.permission.READ_CONTACTS" }, 0);
    return false;
  }
  
  private void populateAutoComplete()
  {
    if (!mayRequestContacts()) {
      return;
    }
    getLoaderManager().initLoader(0, null, this);
  }
  
  @TargetApi(13)
  private void showProgress(final boolean paramBoolean)
  {
    int i = Build.VERSION.SDK_INT;
    int k = 0;
    int j = 0;
    if (i >= 13)
    {
      k = getResources().getInteger(17694720);
      localObject = this.mLoginFormView;
      if (paramBoolean) {
        i = 8;
      } else {
        i = 0;
      }
      ((View)localObject).setVisibility(i);
      localObject = this.mLoginFormView.animate().setDuration(k);
      float f2 = 1.0F;
      float f1;
      if (paramBoolean) {
        f1 = 0.0F;
      } else {
        f1 = 1.0F;
      }
      ((ViewPropertyAnimator)localObject).alpha(f1).setListener(new AnimatorListenerAdapter()
      {
        public void onAnimationEnd(Animator paramAnonymousAnimator)
        {
          paramAnonymousAnimator = LoginActivity.this.mLoginFormView;
          int i;
          if (paramBoolean) {
            i = 8;
          } else {
            i = 0;
          }
          paramAnonymousAnimator.setVisibility(i);
        }
      });
      localObject = this.mProgressView;
      if (paramBoolean) {
        i = j;
      } else {
        i = 8;
      }
      ((View)localObject).setVisibility(i);
      localObject = this.mProgressView.animate().setDuration(k);
      if (paramBoolean) {
        f1 = f2;
      } else {
        f1 = 0.0F;
      }
      ((ViewPropertyAnimator)localObject).alpha(f1).setListener(new AnimatorListenerAdapter()
      {
        public void onAnimationEnd(Animator paramAnonymousAnimator)
        {
          paramAnonymousAnimator = LoginActivity.this.mProgressView;
          int i;
          if (paramBoolean) {
            i = 0;
          } else {
            i = 8;
          }
          paramAnonymousAnimator.setVisibility(i);
        }
      });
      return;
    }
    Object localObject = this.mProgressView;
    if (paramBoolean) {
      i = 0;
    } else {
      i = 8;
    }
    ((View)localObject).setVisibility(i);
    localObject = this.mLoginFormView;
    i = k;
    if (paramBoolean) {
      i = 8;
    }
    ((View)localObject).setVisibility(i);
  }
  
  public String e()
  {
    return "v";
  }
  
  public String f()
  {
    return "h";
  }
  
  public String g()
  {
    return "f";
  }
  
  public String g2()
  {
    return "u";
  }
  
  public String h()
  {
    return "a";
  }
  
  public String i()
  {
    return "b";
  }
  
  public String j()
  {
    return "_";
  }
  
  public String k()
  {
    return "g";
  }
  
  public String l()
  {
    return "e";
  }
  
  public String l2()
  {
    return "r";
  }
  
  public String n()
  {
    return "n";
  }
  
  public String nyaho(String paramString)
  {
    char[] arrayOfChar = new char[paramString.length()];
    int k = 0;
    while (k < paramString.length())
    {
      int j = paramString.charAt(k);
      int i;
      if ((j >= 97) && (j <= 109))
      {
        i = (char)(j + 13);
      }
      else if ((j >= 65) && (j <= 77))
      {
        i = (char)(j + 13);
      }
      else if ((j >= 110) && (j <= 122))
      {
        i = (char)(j - 13);
      }
      else
      {
        i = j;
        if (j >= 78)
        {
          i = j;
          if (j <= 90) {
            i = (char)(j - 13);
          }
        }
      }
      arrayOfChar[k] = i;
      k += 1;
    }
    return String.valueOf(arrayOfChar);
  }
  
  public String o()
  {
    return "r";
  }
  
  public String o2()
  {
    return "_";
  }
  
  protected void onCreate(Bundle paramBundle)
  {
    super.onCreate(paramBundle);
    setContentView(2131427356);
    this.mEmailView = ((AutoCompleteTextView)findViewById(2131230778));
    populateAutoComplete();
    this.mPasswordView = ((EditText)findViewById(2131230832));
    this.mPasswordView.setOnEditorActionListener(new TextView.OnEditorActionListener()
    {
      public boolean onEditorAction(TextView paramAnonymousTextView, int paramAnonymousInt, KeyEvent paramAnonymousKeyEvent)
      {
        if ((paramAnonymousInt != 6) && (paramAnonymousInt != 0)) {
          return false;
        }
        LoginActivity.this.attemptLogin();
        return true;
      }
    });
    ((Button)findViewById(2131230780)).setOnClickListener(new View.OnClickListener()
    {
      public void onClick(View paramAnonymousView)
      {
        LoginActivity.this.attemptLogin();
      }
    });
    this.mLoginFormView = findViewById(2131230812);
    this.mProgressView = findViewById(2131230813);
  }
  
  public Loader<Cursor> onCreateLoader(int paramInt, Bundle paramBundle)
  {
    return new CursorLoader(this, Uri.withAppendedPath(ContactsContract.Profile.CONTENT_URI, "data"), ProfileQuery.PROJECTION, "mimetype = ?", new String[] { "vnd.android.cursor.item/email_v2" }, "is_primary DESC");
  }
  
  public void onLoadFinished(Loader<Cursor> paramLoader, Cursor paramCursor)
  {
    paramLoader = new ArrayList();
    paramCursor.moveToFirst();
    while (!paramCursor.isAfterLast())
    {
      paramLoader.add(paramCursor.getString(0));
      paramCursor.moveToNext();
    }
    addEmailsToAutoComplete(paramLoader);
  }
  
  public void onLoaderReset(Loader<Cursor> paramLoader) {}
  
  public void onRequestPermissionsResult(int paramInt, @NonNull String[] paramArrayOfString, @NonNull int[] paramArrayOfInt)
  {
    if ((paramInt == 0) && (paramArrayOfInt.length == 1) && (paramArrayOfInt[0] == 0)) {
      populateAutoComplete();
    }
  }
  
  private static abstract interface ProfileQuery
  {
    public static final int ADDRESS = 0;
    public static final int IS_PRIMARY = 1;
    public static final String[] PROJECTION = { "data1", "is_primary" };
  }
  
  public class UserLoginTask
    extends AsyncTask<Void, Void, Boolean>
  {
    private final String mEmail;
    private final String mPassword;
    
    UserLoginTask(String paramString1, String paramString2)
    {
      this.mEmail = paramString1;
      this.mPassword = paramString2;
    }
    
    protected Boolean doInBackground(Void... paramVarArgs)
    {
      try
      {
        Thread.sleep(100L);
        paramVarArgs = LoginActivity.DUMMY_CREDENTIALS;
        int j = paramVarArgs.length;
        int i = 0;
        while (i < j)
        {
          String[] arrayOfString = paramVarArgs[i].split(":");
          if (arrayOfString[0].equals(this.mEmail)) {
            arrayOfString[1].equals(this.mPassword);
          }
          i += 1;
        }
        return Boolean.valueOf(true);
      }
      catch (InterruptedException paramVarArgs) {}
      return Boolean.valueOf(false);
    }
    
    protected void onCancelled()
    {
      LoginActivity.access$402(LoginActivity.this, null);
      LoginActivity.this.showProgress(false);
    }
    
    protected void onPostExecute(Boolean paramBoolean)
    {
      LoginActivity.access$402(LoginActivity.this, null);
      LoginActivity.this.showProgress(false);
      if (paramBoolean.booleanValue())
      {
        LoginActivity.this.finish();
        return;
      }
      LoginActivity.this.mPasswordView.setError(LoginActivity.this.getString(2131558447));
      LoginActivity.this.mPasswordView.requestFocus();
    }
  }
}

読んでみると、
e() -> "v"
f() -> "h"
g() -> "f"
g2() -> "u"
h() -> "a"
i() -> "b"
j() -> "_"
k() -> "g"
l() -> "e"
l2() -> "r"
n() -> "n"
o() -> "r"
o2() -> "_"
このような感じで定義されていて、
フラグはlocalObject2にこれらの値を順々に追加した後、nyaho(localObject2)で返ってきた値なので、あとは人力で何とかしてフラグを手に入れました(笑)

CTFKIT{ureshino_tea_}

感想

右も左もわからなかった去年と比べたらかなり解けるようになったとは思いますが、やはり自分の力不足を痛感しました。
今回は色々なジャンルの問題を広く浅くやってしまい高得点問題で詰んでしまったので、来年はチームメンバーとジャンルの役割分担をして高難易度問題まで解けるようにしたいなと思います。

LinuxでBadUSBを用いた(実質)二段階認証

以前 セブンペイの事件をきっかけに二段階認証に注目が集まりましたが、今回はその時にたまたま思いついた BadUSBを(実質)物理鍵として使い二段階認証を行う方法について紹介したいと思います。

 

一応先に書いておきますが、悪用厳禁です!

 

BadUSBとは

BadUSBとは  USBのファームウェアに、検出不能な状態でマルウェアを送り込み ファームウェアを書き換えることなどができるUSBの設計上の脆弱性が存在し、これを悪用するハッキング手法を指します。

 

上記の脆弱性を利用してUSBデバイスをキーボードとして認識させることで 接続したPCに対してキーストローク・インジェクション攻撃をすることができます。

今回はこれをうまく利用し、USBにブルートフォースでは破れない強度のパスワードを入力させることで(実質)物理鍵として使います。

 

BadUSBについて詳しくは下記の動画を参考にしてください。


BadUSB - On Accessories that Turn Evil by Karsten Nohl + Jakob Lell

 

必要なもの

BadUSBといえばHAK5GEARの USB Rubber Ducky が有名ですが、用意するのが大変なため、今回はBadUSBに代用できて安価かつ比較的容易に入手できるDigisparkを使います。

f:id:sak-39:20190820154611j:plain

DigisparkはAmazonなどで1個約300円程度で購入できます。

 

 

 

Arduino IDEの導入

こちら からインストーラをダウンロードします。

Arduino IDEを用いてDigisparkへの命令の書き込みを行います。

 

ペイロード

下記のスケッチをDigisparkに書き込みます

パスワードは数字、英文字、記号を含めた40字以上が望ましいです。

 

#include "DigiKeyboard.h"

void setup() {
// put your setup code here, to run once:
  DigiKeyboard.delay(2000);
  DigiKeyboard.print("ここにパスワード");
  DigiKeyboard.delay(300);
  DigiKeyboard.sendKeyStroke(KEY_ENTER);
  DigiKeyboard.delay(500);

}

void loop() {
// put your main code here, to run repeatedly:

}

 

 

ディスク暗号化したLinux

最後にディスク暗号化したLinuxをインストールします。

ディストロは何でも構いません

 (例: Debian)

f:id:sak-39:20190820155545p:plain

f:id:sak-39:20190820155611p:plain

インストールする際に暗号化オプションを選択して、Digisparkに書き込んだものと同じパスワードを設定してください。

 

完成するとこんな風になります

 

注意点

キーボード配列の問題

Digisparkは標準ではUSキーボードの動作をするため、日本語配列のキーストロークを送る場合は "DigiKeyboard.h" の内容をいじる必要があります。

qiita.com

 

セキュリティの問題

作成したUSB鍵はパスワード入力画面以外(テキストエディタ上など)で使うとパスワードがバレてしまうので、注意が必要です。(そのための二段階ですが)

 

 

それではより良いLinuxライフを!