Quick Password Recovery PRO 1.7のセキュリティ研究

みなさんこんにちは。 今日は、Quick Password Recovery Pro 1.7.1の例を使用して、いくつかの興味深いトリックとハッシュ関数を使用したセキュリティ研究を示します。





目的: Quick Password Recovery PRO 1.7.1(www.passdecrypt.ru)

保護: HWID + MD5

ツール: Die 0.65 + KANAL 2.92プラグイン、OllyDbg 1.10(さらにOlly)+ mapimpプラグイン(tport.orgにあります)、IDR(Interactive Delphi Reconstructor)、Keygener Assistant 1.7、Borland Delphi 7(keygenの作成用)。

「患者検査」を始めましょう。

画像

プログラムはBorland Delphiで書かれています[ver:2006] | Object Pascalなので、すぐにIDRにロードして、Ollyのマップファイルを分析および作成します。

Kanalプラグインは、いくつかの暗号署名を検出します。

画像

プログラムを起動すると、ウィンドウが表示され、これは未登録バージョンであり、一部の機能が利用できないというメッセージが表示されます。 これを自分で確認できます。

画像



画像



[登録]ボタンをクリックします。

画像

登録するには、名前とシリアル番号が必要です。これはおそらくシステム識別子(以下、HWID)に依存します。



たとえば、登録データを入力します。

名前:ds@mail.ru

シリアル:abcdefghigklmnop



「登録」ボタンをクリックして実行されるIDRコードを調べますが、レジストリにエントリのペアを作成することに加えて、他には何も起こりません。

画像



画像

プログラムはそれを再起動することを提案します。つまり、チェックはプログラムの開始時にどこかで実行されます。 メインフォームの作成時に検証が実行されると仮定します(OnCreateイベント)。 IDRで呼び出し場所を探し、Ollyの関数の先頭にブレークポイントを配置します。

画像



デバッガーからプログラムを開始し、ここで停止します。

Copy Source | Copy HTML<br/>0046EC94 > . 55 PUSH EBP ; Unit1.TForm1.FormCreate <br/>





最初の興味深い電話に行きます:

画像

F7を入力し、ここに到達するまでさらにトレースします。

画像

少し先に進み、HWID生成とシリアルの最初の部分の分離が隠されているのはここにあると言います。

繰り返しになりますが、F7に進み、アドレス46537FでHWIDが生成されていることを確認します。これは、デバッガーの下で関数を実行することで簡単に推測できます。

Copy Source | Copy HTML<br/>0046537F |. E8 3CFCFFFF CALL <QuickPas._Unit32.sub_00464FC0> ; << Get Hwid <br/>





画像

しかし、その後、00464FC0で関数を注意深く調べて、そこでコードを見つけます。

Copy Source | Copy HTML<br/>00465072 | > /8B45 FC /MOV EAX,DWORD PTR SS:[EBP-4]<br/>00465075 |. |0FB64418 FF |MOVZX EAX,BYTE PTR DS:[EAX+EBX-1]<br/>0046507A |. |8BD3 |MOV EDX,EBX<br/>0046507C |. |03D2 |ADD EDX,EDX<br/>0046507E |. |03C2 |ADD EAX,EDX<br/>00465080 |. |8D55 DC |LEA EDX,DWORD PTR SS:[EBP-24]<br/>00465083 |. |E8 3441FAFF |CALL < QuickPas.SysUtils.IntToStr > <br/>00465088 |. |8B55 DC |MOV EDX,DWORD PTR SS:[EBP-24]<br/>0046508B |. |8D45 F4 |LEA EAX,DWORD PTR SS:[EBP-C]<br/>0046508E |. |E8 6D01FAFF |CALL < QuickPas.system. @LStrCat > <br/>00465093 |. |43 |INC EBX<br/>00465094 |. |83FB 04 |CMP EBX,4<br/>00465097 |.^\75 D9 \JNZ SHORT QuickPas.00465072<br/>00465099 |. 8D45 F8 LEA EAX,DWORD PTR SS:[EBP-8]<br/>0046509C |. E8 93FEF9FF CALL < QuickPas.system. @LStrClr > <br/>004650A1 |. BB 01000000 MOV EBX,1<br/>004650A6 | > 8D45 D8 /LEA EAX,DWORD PTR SS:[EBP-28]<br/>004650A9 |. 8B55 F4 |MOV EDX,DWORD PTR SS:[EBP-C]<br/>004650AC |. 0FB6541A FF |MOVZX EDX,BYTE PTR DS:[EDX+EBX-1]<br/>004650B1 |. E8 6600FAFF |CALL < QuickPas.system. @LStrFromChar > <br/>004650B6 |. 8B55 D8 |MOV EDX,DWORD PTR SS:[EBP-28]<br/>004650B9 |. 8D45 F8 |LEA EAX,DWORD PTR SS:[EBP-8]<br/>004650BC |. E8 3F01FAFF |CALL < QuickPas.system. @LStrCat > <br/>004650C1 |. 43 |INC EBX<br/>004650C2 |. 83FB 06 |CMP EBX,6<br/>004650C5 |.^ 75 DF \JNZ SHORT QuickPas.004650A6 <br/>





ここでは、最初の3文字のコードが取得され、+ 2で乗算されたループカウンターの値が文字列に接着され、その後、最初の5文字のみが取得されます(ハッシュが考慮されるSystemBiosDateパラメーターは、レジストリ、より具体的にはHKEY_LOCAL_MACHINE \ HARDWARE \ DESCRIPTIONにあります) \システム)

画像

パスカルでは、これは次のように書くことができます。

Copy Source | Copy HTML<br/>md5_sbd:=md5( SystemBiosDate)<br/> for i:=1 to 3 do<br/> begin <br/> result := result + IntToStr(ord(md5_sbd[i]) + i*2;);<br/> end ; <br/>





HWID取得機能を終了した後、次のステップに進みます。

Copy Source | Copy HTML<br/>00465389 |> /8D45 F8 /LEA EAX,DWORD PTR SS:[EBP-8]<br/>0046538C |. |E8 BF00FAFF | CALL <QuickPas.system.@UniqueStringA><br/>00465391 |. |BA 06000000 |MOV EDX,6<br/>00465396 |. |2BD3 |SUB EDX,EBX<br/>00465398 |. |8B4D FC |MOV ECX,DWORD PTR SS:[EBP-4]<br/>0046539B |. |0FB64C19 FF |MOVZX ECX,BYTE PTR DS:[ECX+EBX-1]<br/>004653A0 |. |884C10 FF |MOV BYTE PTR DS:[EAX+EDX-1],CL<br/>004653A4 |. |43 |INC EBX<br/>004653A5 |. |83FB 06 |CMP EBX,6<br/>004653A8 |.^\75 DF \JNZ SHORT QuickPas.00465389<br/>004653AA |. BB 01000000 MOV EBX,1<br/>004653AF |> 8D45 F0 /LEA EAX,DWORD PTR SS:[EBP-10]<br/>004653B2 |. 8B55 F8 |MOV EDX,DWORD PTR SS:[EBP-8]<br/>004653B5 |. 0FB6541A FF |MOVZX EDX,BYTE PTR DS:[EDX+EBX-1]<br/>004653BA |. E8 5DFDF9FF | CALL <QuickPas.system.@LStrFromChar><br/>004653BF |. 8B45 F0 |MOV EAX,DWORD PTR SS:[EBP-10]<br/>004653C2 |. E8 313FFAFF | CALL <QuickPas.SysUtils.StrToInt><br/>004653C7 |. 8BF0 |MOV ESI,EAX<br/>004653C9 |. 33F3 |XOR ESI,EBX<br/>004653CB |. 8D55 F4 |LEA EDX,DWORD PTR SS:[EBP-C]<br/>004653CE |. 8BC6 |MOV EAX,ESI<br/>004653D0 |. E8 E73DFAFF | CALL <QuickPas.SysUtils.IntToStr><br/>004653D5 |. 8D45 F8 |LEA EAX,DWORD PTR SS:[EBP-8]<br/>004653D8 |. E8 7300FAFF | CALL <QuickPas.system.@UniqueStringA><br/>004653DD |. 8B55 F4 |MOV EDX,DWORD PTR SS:[EBP-C]<br/>004653E0 |. 0FB612 |MOVZX EDX,BYTE PTR DS:[EDX]<br/>004653E3 |. 885418 FF |MOV BYTE PTR DS:[EAX+EBX-1],DL<br/>004653E7 |. 43 |INC EBX<br/>004653E8 |. 83FB 06 |CMP EBX,6<br/>004653EB |.^ 75 C2 \JNZ SHORT QuickPas.004653AF <br/>





ここにいくつかの変換などがあります。 繰り返しますが、私はPascalでのみ翻訳した次のコードを表しています。

Copy Source | Copy HTML<br/>part_of_serial:=ReverseString(hwid);<br/>tmp:= '' ;<br/> for i:= 1 to 5 do<br/> begin <br/> tmp:=tmp + copy(IntToStr(StrToInt(part_of_serial[i]) xor i),1,1);<br/> end ;<br/>part_of_serial:=copy(tmp,1,5); <br/>





私の場合、結果は41413です

どうぞ...

画像

プログラムがハッシュ関数md5を使用していることを知って、仮定を確認します。

md5(41413)= FFA54840A3C240E0725C16C7FA48281C

画像

これはスタックウィンドウにはっきりと表示されます。

画像

関数を終了し、トレースを続行して、入力されたシリアルの最初の5文字からmd5ハッシュを取得し、以前に受信したものと比較します(プリミティブですが、少なくとも何らかの方法で)。

md5(abcde)= AB56B4D92B40713ACC5AF89985D4B786

画像

これで、正しいシリアルの最初の文字(私の場合)は41413になります。

上記を考慮して、プログラムを新しいシリアルに登録しようとしています。

41413abcdefghijklmnop、電子メールは以前と同じです(ds@mail.ru)。

現在、次の場所で既に検証中です。

Copy Source | Copy HTML<br/>00465563 |. E8 DCFDF9FF CALL <QuickPas.system.@LStrCmp> <br/>





プログラムは警告なしで起動し、[バージョン情報]ウィンドウにすべてが正常であることを書き込みますが、プログラムの機能ボタンをクリックすると、未登録バージョンが表示されます。



OK、テストに合格したところから続けます。 00465563にブレークポイントを設定し、さらにトレースします。

次の2つの非常に単純なコードが表示されます。

Copy Source | Copy HTML<br/>00465595 |. BB 01000000 MOV EBX,1<br/>0046559A | > /8B45 EC /MOV EAX,DWORD PTR SS:[EBP-14]<br/>0046559D |. |0FB64418 FF |MOVZX EAX,BYTE PTR DS:[EAX+EBX-1]<br/>004655A2 |. |0145 E4 |ADD DWORD PTR SS:[EBP-1C],EAX<br/>004655A5 |. |43 |INC EBX<br/>004655A6 |. |83FB 06 |CMP EBX,6<br/>004655A9 |.^\75 EF \JNZ SHORT QuickPas.0046559A <br/>





Copy Source | Copy HTML<br/>004655BC |. BB 01000000 MOV EBX,1<br/>004655C1 | > 8B55 FC /MOV EDX,DWORD PTR SS:[EBP-4]<br/>004655C4 |. 0FB6541A FF |MOVZX EDX,BYTE PTR DS:[EDX+EBX-1]<br/>004655C9 |. 0155 E8 |ADD DWORD PTR SS:[EBP-18],EDX<br/>004655CC |. 43 |INC EBX<br/>004655CD |. 48 |DEC EAX<br/>004655CE |.^ 75 F1 \JNZ SHORT QuickPas.004655C1 <br/>





これらには、いくつかの計算(最初に入力されたシリアルの6〜10番目の文字)、および入力された電子メールが含まれます。



このすべての結果として、番号1353とfghijklmnopが比較されます。これはシリアル番号の3番目の部分である可能性があることは論理的です。

新しいシリアル番号を試す:41413abcde1353

すべてがうまくいくように見えますが、1つの「しかし」があります!

復元するためにファンクションキーをクリックすると、次のような奇妙な文字が表示されます。

画像

結果を出力する前にどこかで問題が発生することがわかりました。 IDRのイベントを見てみましょう。たとえば、クリックしてThe Batパスワードをリセットし、ブレークポイントを手順の先頭に設定します。

画像

次に、アドレス465250の関数に入り、スタックウィンドウを注意深く見て、同時にF8を押します

シリアルとHWIDの最初の5文字が一緒に接着されていることに気付かないのは難しいことです。その後、受信した文字列からmd5ハッシュが考慮されます。



md5(4141369735)= 3354B017EB74FB4DC20A1B48491D1431



そして、受信したハッシュから特定の量が考慮されます:



Copy Source | Copy HTML<br/> for i:=4 to 7 do<br/> begin <br/> tmp := tmp + IntToStr(ord(part2[i]));<br/> end ; <br/>







最初の5文字がコピーされます。

Copy Source | Copy HTML<br/> some := copy(tmp,1,5); <br/>





計算の結果、52664になります(この値に戻ります)。



さらに興味深い電話:

CALL 00467A44





ここで私たちにとって興味深いことは何もありません(これはパスワードを回復しようとするプログラムの主な機能です)。

さらに進んで、ここを見てください:

00470919 |. E8 FA4DFFFF CALL <QuickPas._Unit32.sub_00465718>







非常に興味深いブロック:

Copy Source | Copy HTML<br/>0046577F |. BB 01000000 MOV EBX,1<br/>00465784 | > 8D45 FC /LEA EAX,DWORD PTR SS:[EBP-4]<br/>00465787 |. E8 C4FCF9FF |CALL < QuickPas.system. @UniqueStringA > <br/>0046578C |. 8D4418 FF |LEA EAX,DWORD PTR DS:[EAX+EBX-1]<br/>00465790 |. 50 |PUSH EAX<br/>00465791 |. 8BC3 |MOV EAX,EBX<br/>00465793 |. 99 |CDQ<br/>00465794 |. F7FF |IDIV EDI<br/>00465796 |. 8B45 F8 |MOV EAX,DWORD PTR SS:[EBP-8]<br/>00465799 |. 0FB60410 |MOVZX EAX,BYTE PTR DS:[EAX+EDX]<br/>0046579D |. 0FBE55 F3 |MOVSX EDX,BYTE PTR SS:[EBP-D]<br/>004657A1 |. F7EA |IMUL EDX<br/>004657A3 |. 8B55 FC |MOV EDX,DWORD PTR SS:[EBP-4]<br/>004657A6 |. 0FB6541A FF |MOVZX EDX,BYTE PTR DS:[EDX+EBX-1]<br/>004657AB |. 03C2 |ADD EAX,EDX<br/>004657AD |. 5A |POP EDX<br/>004657AE |. 8802 |MOV BYTE PTR DS:[EDX],AL<br/>004657B0 |. 43 |INC EBX<br/>004657B1 |. 4E |DEC ESI<br/>004657B2 |.^ 75 D0 \JNZ SHORT QuickPas.00465784 <br/>





ダンプウィンドウを見ると、プリミティブ暗号化が出力の前にあることがわかります(これは大きすぎて完全な暗号化には達しませんが、共通点があります-これはメッセージと暗号化キーなので、これを呼び出し続けます)ブロック暗号化)。

画像

このコードを詳細に調べることができますが、最終的な目標(keygenの作成)には必要ありません。



どうぞ、ご覧ください:



Copy Source | Copy HTML<br/>0047093B |. E8 144DFFFF CALL < QuickPas._Unit32.sub_00465654 > <br/>







この関数に渡されるパラメーターは非常によく知られています-これらは6日から10日までのシリアルの文字です。

直感は私を失敗させませんでした、私たちはそのようなコードを見る:



Copy Source | Copy HTML<br/>004656AC |. BB 01000000 MOV EBX,1<br/>004656B1 | > 8D45 FC /LEA EAX,DWORD PTR SS:[EBP-4]<br/>004656B4 |. E8 97FDF9FF |CALL < QuickPas.system. @UniqueStringA > <br/>004656B9 |. 8D4418 FF |LEA EAX,DWORD PTR DS:[EAX+EBX-1]<br/>004656BD |. 50 |PUSH EAX<br/>004656BE |. 8BC3 |MOV EAX,EBX<br/>004656C0 |. 99 |CDQ<br/>004656C1 |. F7FF |IDIV EDI<br/>004656C3 |. 8B45 F8 |MOV EAX,DWORD PTR SS:[EBP-8]<br/>004656C6 |. 0FB60410 |MOVZX EAX,BYTE PTR DS:[EAX+EDX]<br/>004656CA |. 0FBE55 F3 |MOVSX EDX,BYTE PTR SS:[EBP-D]<br/>004656CE |. F7EA |IMUL EDX<br/>004656D0 |. 8B55 FC |MOV EDX,DWORD PTR SS:[EBP-4]<br/>004656D3 |. 0FB6541A FF |MOVZX EDX,BYTE PTR DS:[EDX+EBX-1]<br/>004656D8 |. 03C2 |ADD EAX,EDX<br/>004656DA |. 5A |POP EDX<br/>004656DB |. 8802 |MOV BYTE PTR DS:[EDX],AL<br/>004656DD |. 43 |INC EBX<br/>004656DE |. 4E |DEC ESI<br/>004656DF |.^ 75 D0 \JNZ SHORT QuickPas.004656B1 <br/>





同じコードですが、この場合、出力の直前にメッセージを「復号化」します。

通常の出力データを取得するには、暗号化されたキーと同じキーで復号化する必要があることは明らかです。



true_key = 52664

our_key = abcde



メッセージ=復号化(crypt(message、true_key)、our_key)

message = messageの場合、true_keyはour_keyと等しくなければならないことは簡単に推測できます。



一般に、keygenのすべてのデータがありますが、すべてを1つの一般的な手順に減らすことが残っています。

一部の計算を個別の関数に分解しやすくするには:



1. レジストリから値を取得する機能:

Copy Source | Copy HTML<br/> function RegQueryStr(RootKey: HKEY; Key, Name: string ;<br/> Success: PBoolean = nil): string ;<br/> var <br/> Handle: HKEY;<br/> Res: LongInt;<br/> DataType, DataSize: DWORD;<br/>begin<br/> if Assigned(Success) then<br/> Success^ := False;<br/> Res := RegOpenKeyEx(RootKey, PChar(Key), 0 , KEY_QUERY_VALUE, Handle);<br/> if Res <> ERROR_SUCCESS then<br/> Exit;<br/> Res := RegQueryValueEx(Handle, PChar(Name), nil, @DataType, nil, @DataSize);<br/> if (Res <> ERROR_SUCCESS) or (DataType <> REG_SZ) then<br/> begin<br/> RegCloseKey(Handle);<br/> Exit;<br/> end;<br/> SetString(Result, nil, DataSize - 1 );<br/> Res := RegQueryValueEx(Handle, PChar(Name), nil, @DataType,<br/> PByte(@Result[ 1 ]), @DataSize);<br/> if Assigned(Success) then<br/> Success^ := Res = ERROR_SUCCESS;<br/> RegCloseKey(Handle);<br/>end; <br/>







2. HWID計算機能:

Copy Source | Copy HTML<br/> function GetHWID: string;<br/> var <br/>SystemBiosDate,md5_sbd:String;<br/>i: integer ;<br/>digest:pointer;<br/>dwSize:Cardinal;<br/> begin <br/> SystemBiosDate := RegQueryStr(HKEY_LOCAL_MACHINE, 'HARDWARE\DESCRIPTION\System' , 'SystemBiosDate' );<br/> md5_sbd:=SystemBiosDate;<br/> dwSize:=length(md5_sbd);<br/> digest:= GetHash(@md5_sbd[1],dwSize,ALG_MD5);<br/> if digest <> nil then <br/> try<br/> md5_sbd:=BinToHexStr(digest,dwSize);<br/> finally<br/> FreeMem(digest);<br/> end ;<br/> md5_sbd:=Uppercase(md5_sbd);<br/> result := '' ;<br/> for i:=1 to 3 do<br/> begin <br/> result := result + IntToStr(ord(md5_sbd[i]) + i*2);<br/> end ;<br/> result :=copy( Result ,1,5); <br/> End ; <br/>







3. リバース(リバースライン)を取得する機能:

Copy Source | Copy HTML<br/> function ReverseString( const s: string ): string ;<br/> var <br/> i, len: Integer;<br/>begin<br/> len := Length(s);<br/> SetLength(Result, len);<br/> for i := len downto 1 do <br/> begin<br/> Result[len - i + 1 ] := s[i];<br/> end;<br/>end; <br/>







4. 文字列が数字であるかどうかを確認する機能(ユーザー入力の正確さのため):

Copy Source | Copy HTML<br/> function IsStrANumber( const S: string ): Boolean;<br/> var <br/> P: PChar;<br/>begin<br/> P := PChar(S);<br/> Result := False;<br/> while P^ <> # 0 do <br/> begin<br/> if not (P^ in [ '0' .. '9' ]) then Exit;<br/> Inc(P);<br/> end;<br/> Result := True;<br/>end; <br/>







基本的な生成手順:

Copy Source | Copy HTML<br/>procedure generate;<br/> var Serial,NameBuf: String ;<br/>len,len1: integer ;<br/>Textname,HWID: PChar ;<br/>hw: String ;<br/> i ,sum_name,sum_part2: integer ;<br/> part1,tmp,part2,part3: string ;<br/> digest: pointer ;<br/>dw Size : Cardinal ;<br/>begin<br/> len := GetWindowTextLengthA( TxtNameHwnd );<br/> len1 := GetWindowTextLengthA( txtHWID );<br/> if len > 1 then<br/> begin<br/> { Get text from name input }<br/> GetMem( Textname, len );<br/> GetWindowTextA( TxtNameHwnd,PAnsiChar(Textname ),len + 1 );<br/> GetMem( HWID, len1 );<br/> GetWindowTextA( TxtHWID,PAnsiChar(HWID ),len1 + 1 );<br/> HW:=String( HWID );<br/>if ( IsStrANumber(hw )) and ( length(hw )= 5 ) then<br/> begin<br/> { Generate Serial }<br/> NameBuf:=String( Textname );<br/> hw:=string( HWID );<br/>hw:=copy( hw,1,5 );<br/>part1:=ReverseString( hw );<br/>tmp:= '' ;<br/>for i := 1 to 5 do<br/> begin<br/> tmp:=tmp + copy( IntToStr(StrToInt(part1[i] ) xor i ), 1 , 1 );<br/> end;<br/>part1:=copy( tmp,1,5 );<br/> //--------------------- <br/>tmp:= '' ;<br/>part2 :=part1+hw;<br/> dw Size :=length( part2 );<br/> digest:= GetHash( @part2[1],dwSize,ALG_MD5 );<br/> if digest <> nil then<br/> try<br/> part2:=BinToHexStr( digest,dwSize );<br/> finally<br/> FreeMem( digest );<br/> end;<br/>part2 := UpperCase ( part2 );<br/>for i := 4 to 7 do<br/> begin<br/> tmp := tmp + IntToStr( ord(part2[i] ));<br/> end;<br/>part2 := copy( tmp,1,5 );;<br/> //--------------------- <br/>sum_name:= 0 ;<br/>sum_part2:= 0 ;<br/>for i := 1 to length( NameBuf )- 1 do<br/> begin<br/> sum_name:=sum_name + ord( NameBuf[i] );<br/> end;<br/>for i := 1 to length( part2 ) do<br/> begin<br/> sum_part2:=sum_part2 + ord( part2[i] );<br/> end;<br/>part3:=IntToStr( sum_name+sum_part2 );<br/>Serial:=part1 + part2 + part3;<br/> end<br/> else // if hwid not numeric or <> 5 <br/> Serial:= 'Invalid HWID' ;<br/> { Display The Results }<br/> SetWindowTextA( TxtSerialHwnd,PChar(Serial ));<br/> FreeMem ( HWID, len1 + 1 );<br/> FreeMem ( Textname, len+1 );<br/> end<br/> Else<br/> { Display Error }<br/> SetWindowText( TxtSerialHwnd,'Not Enough Characters..' );<br/>end; <br/>







ここでは、すべての主要なポイントが分析されているので、すべてが非常に明確だと思います。

コードのこれらのセクションは質問を引き起こすかもしれません:

Copy Source | Copy HTML<br/>dwSize:=length(part2);<br/>digest:= GetHash(@part2[1],dwSize,ALG_MD5);<br/> if digest <> nil then <br/> try<br/> part2:=BinToHexStr(digest,dwSize);<br/> finally<br/> FreeMem(digest);<br/> end ; <br/>





要するに、これはHashCryptoAPILibを使用したmd5ハッシュ計算であり、HashCryptoAPILibは標準のWindowsツール(CryptoApi)を使用します。 keygenを自分でコンパイルするか、「ダウンロード」セクションでtport.org用に準備することができます。



任意の名前のシリアルを生成してテストします。 プログラムが登録され、正常に動作し、その機能を実行しようとし、メッセージを通常の形式で表示します。 以上です。



上記の資料がおもしろくて、誰かが自分に役立つものを見つけることを願っています。



All Articles