내가 당신의 시아코인을 훔친 방법
원문은 Michael Lynch님이 에 게재했습니다. 이 블로그 구독하기
씨드가 유출된 수상한 레딧 게시물
그날은 2017년 6월 9일 밤이었다. 나에게는 평범한 금요일 밤이었다. 나는 넷플릭스를 보며 레딧을 확인하고 있었다 멋진 애들과 파티를 즐기고 있었다.
그러다 갑자기 /r/siacoin 서브레딧의 “최신” 탭에서 이런 게시물을 발견했다:

시아코인을 잘 모른다면, 시아코인은 남는 하드디스크 공간을 빌려주거나 다른 사람의 공간을 빌려 쓸 수 있게 해주는 암호화폐라고 보면 된다. 나는 이전에도 이 기술에 대해 몇 번 글을 쓴 적이 있다(채굴 가이드, NAS 가이드).
이 레딧 사용자는 방금 아주 위험한 짓을 저질렀다. 자신의 시아 지갑 시드를 인터넷에 올려버린 것이다. 시아의 “시드”란 그걸 가진 사람에게 지갑 안 암호화폐 전체에 대한 완전한 제어 권한을 주는 일종의 암호 문구다. 이 사용자의 경우 지갑에 2,000유로어치의 시아코인을 넣어뒀기 때문에, 시드를 아는 사람은 그 돈 전부를 가져갈 수 있다는 뜻이다. 스크린샷에서 볼 수 있듯, 사용자는 이 암호 문구가 작동하지 않는다는 것을 확인했기 때문에 안전하다고 생각하고 있었다.
아깝다는 건 소용없다 (편자 던지기와 시아 시드에서는 예외지만)
게시물에서 내 눈을 즉시 사로잡은 것은 사용자가 시드를 손으로 직접 적어 올렸다는 점이었다:
실수 없이 적었다고 거의 확신해요, 저는 항상 두 번씩 확인하거든요.
나는 그들이 분명 실수를 했다고 확신했다. 다만 그 실수가 딱 한 군데이기를 바랐다. 사용자가 글자 하나를 틀리거나 두 글자 순서를 바꾼 정도라면, 올바른 시드를 찾아내 2,000유로를 되찾을 수 있을지도 몰랐다.
서둘러야 했다. 유출된 시드를 알아볼 수 있는 사람이 나뿐이 아닐 테니, 다른 누군가가 가로채기 전에 시드를 풀고 돈을 먼저 확보해야 했다.
수작업 해킹
나는 먼저 잘못된 시드에 들어 있는 단어들을 살펴보기 시작했다:
eluded logic wise ascend tagged acoustic situated stylishly younger aptitude inroads avidly hefty also godfather unrest avatar push because brunt viking gone august public tonic vulture shrugged otter adapt
시아가 시드를 어떻게 생성하는지는 잘 몰랐지만, 시아는 완전한 오픈소스이니 알아내는 건 그리 어렵지 않을 거라고 생각했다.
실제로 어렵지 않았다. 시아의 wallet.go 파일을 살펴보다가 SeedToString 함수를 찾았다. 거기서 entropy-mnemonics GitHub 프로젝트로 이어졌고, 그 안에 가능한 시드 단어들의 사전이 들어 있었다:
englishDictionary = Dictionary{
"abbey",
"abducts",
"ability",
"ablaze",
"abnormal",
"abort",
"abrasive",
"absorb",
"abyss",
"academy",
"aces",
"aching",
"acidic",
"acoustic",
"acquire",
"across",
"actress",
...entropy 사전에는 단어가 약 1,600개밖에 없었다. 내 기대는 사용자가 시드를 적어 내려가다가 사전에 없는 단어를 실수로 적었을지도 모른다는 것이었다. 그렇다면 그들이 올린 29개 단어 중 사전에 없는 단어를 찾으면 그게 바로 틀린 단어일 테고, 그 단어와 비슷한 사전 단어를 찾으면 시드를 금방 알아낼 수 있을 것이다.
하지만 아쉽게도 잘못된 시드의 29개 단어는 모두 entropy 사전에 존재했다. 눈으로 보고 찾아내는 방법은 통하지 않았다.
무차별 대입
이제 중화기를 꺼내들 때였다(여기서 중화기란 코드를 칠 때 쓰는 두 손가락을 말한다). 레딧에 올라온 시드에서 복사 실수 한 번만큼 어긋난 단어들을 사전에서 모두 찾아낼 방법이 필요했다.
나는 레벤슈타인 거리가 도움이 될 수 있다는 걸 깨달았다. 레벤슈타인 거리란 한 단어를 다른 단어로 바꾸기 위해 추가, 삭제, 교체해야 하는 글자 수를 말한다. 예를 들어 “cat”과 “car”는 “t”를 “r”로 교체하면 되므로 레벤슈타인 거리가 1이다. “cat”과 “scar”는 “t”를 교체하고 앞에 “s”를 추가해야 하므로 거리가 2다.
가능한 시드를 찾아내기 위해, 잘못된 시드의 단어들과 레벤슈타인 거리가 1인 단어들을 entropy 사전에서 찾는 스크립트를 작성하면 됐다.
먼저 사전을 로컬에 다운로드한 뒤 a부터 z까지의 문자만 남기고 나머지는 모두 제거했다:
$ wget -qO- https://raw.githubusercontent.com/NebulousLabs/entropy-mnemonics/master/english.go \
| egrep "^\s+\"(.+)\"," \
| egrep -o [a-z]+ \
> dictionary.txt그런 다음 python-Levenshtein 라이브러리를 설치하고, 가능한 시드들을 뽑아내는 허접한 파이썬 스크립트를 작성했다:
import Levenshtein
seed = raw_input('enter your wallet seed: ')
for seed_word in seed.split():
for dict_word in open('dictionary.txt'):
dict_word = dict_word.strip()
distance = Levenshtein.distance(seed_word, dict_word)
if distance != 1:
continue
print '"%s" -> "%s"\n%s\n' % (seed_word, dict_word,
seed.replace(seed_word, dict_word))고백하자면, 실제로는 스크립트가 훨씬 더 허접했고 사전의 1,600줄을 파이썬 스크립트에 직접 복사해 붙여 넣는 방식이었다. 여기 코드는 설명을 위해 다듬은 것이다.
금고를 열다
가능한 경우가 수백 가지나 나와서 각 시드를 시도하는 과정까지 자동화해야 할까 봐 걱정했다. 다행히 스크립트 결과, 잘못된 시드와 레벤슈타인 거리가 1인 시드는 단 12가지뿐이었다:
$ python recover.py
enter your wallet seed: eluded logic wise ascend tagged acoustic situated stylishly younger aptitude inroads avidly hefty also godfather unrest avatar push because brunt viking gone august public tonic vulture shrugged otter adapt
"wise" -> "wife"
eluded logic wife ascend tagged acoustic situated stylishly younger aptitude inroads avidly hefty also godfather unrest avatar push because brunt viking gone august public tonic vulture shrugged otter adapt
"tagged" -> "jagged"
eluded logic wise ascend jagged acoustic situated stylishly younger aptitude inroads avidly hefty also godfather unrest avatar push because brunt viking gone august public tonic vulture shrugged otter adapt
"tagged" -> "nagged"
eluded logic wise ascend nagged acoustic situated stylishly younger aptitude inroads avidly hefty also godfather unrest avatar push because brunt viking gone august public tonic vulture shrugged otter adapt
"aptitude" -> "altitude"
eluded logic wise ascend tagged acoustic situated stylishly younger altitude inroads avidly hefty also godfather unrest avatar push because brunt viking gone august public tonic vulture shrugged otter adapt
"push" -> "lush"
eluded logic wise ascend tagged acoustic situated stylishly younger aptitude inroads avidly hefty also godfather unrest avatar lush because brunt viking gone august public tonic vulture shrugged otter adapt
"brunt" -> "grunt"
eluded logic wise ascend tagged acoustic situated stylishly younger aptitude inroads avidly hefty also godfather unrest avatar push because grunt viking gone august public tonic vulture shrugged otter adapt
"tonic" -> "ionic"
eluded logic wise ascend tagged acoustic situated stylishly younger aptitude inroads avidly hefty also godfather unrest avatar push because brunt viking gone august public ionic vulture shrugged otter adapt
"tonic" -> "sonic"
eluded logic wise ascend tagged acoustic situated stylishly younger aptitude inroads avidly hefty also godfather unrest avatar push because brunt viking gone august public sonic vulture shrugged otter adapt
"tonic" -> "topic"
eluded logic wise ascend tagged acoustic situated stylishly younger aptitude inroads avidly hefty also godfather unrest avatar push because brunt viking gone august public topic vulture shrugged otter adapt
"tonic" -> "toxic"
eluded logic wise ascend tagged acoustic situated stylishly younger aptitude inroads avidly hefty also godfather unrest avatar push because brunt viking gone august public toxic vulture shrugged otter adapt
"adapt" -> "adept"
eluded logic wise ascend tagged acoustic situated stylishly younger aptitude inroads avidly hefty also godfather unrest avatar push because brunt viking gone august public tonic vulture shrugged otter adept
"adapt" -> "adopt"
eluded logic wise ascend tagged acoustic situated stylishly younger aptitude inroads avidly hefty also godfather unrest avatar push because brunt viking gone august public tonic vulture shrugged otter adopt경우의 수가 충분히 적어서 시아에 직접 손으로 입력해 볼 수 있었다. 먼저 잘못된 시드에서 wise를 wife로 바꾼 첫 번째 후보 시드를 시도했다:
> siac wallet init-seed
Seed:
Could not initialize wallet from seed: error when calling /wallet/init/seed: seed failed checksum verification예상했던 결과였다. 후보가 모두 유효한 시드일 리는 없었다. 계속 하나씩 시도하다가 tonic을 ionic으로 바꾼 시드에 이르렀다:
> siac wallet init-seed
Seed:
Wallet initialized and encrypted with seed.잭팟!
안에 뭐가 들었는지 확인해 보자:
> siac wallet unlock
Wallet password:
Wallet unlocked
> siac wallet
Wallet status:
Encrypted, Unlocked
Confirmed Balance: 594.8 SC이상했다. 당시 594.8 SC(시아코인)는 약 10유로 정도였는데, 사용자가 지갑에 들어 있다고 주장한 2,000유로와는 거리가 멀었다.
오히려 내가 속은 건 아닐까? 사용자가 실제로는 10유로밖에 없다는 걸 알면서도 더 큰 금액을 주장해 누군가의 도움을 유도한 걸까? 아니면 더 뛰어난 암호화폐 도둑이 먼저 지갑을 털고 나를 조롱하려고 10유로만 남겨둔 걸까?
전리품 확보하기
이상한 잔액을 앉아서 곰곰이 생각해 보고 싶었지만, 시간이 촉박했다. 그 게시물을 보고 나처럼 지갑을 열려는 사람이 또 있을지 알 수 없었다. 시아코인을 훔칠 시간이었다.
벤 게이츠: 다른 사람도 그 보물을 노리고 있어.
라일리 풀: 당연하지. 보물 찾기의 공리잖아.
-내셔널 트레져: 비밀의 책
나는 재빨리 잔액 전부를 내 시아 지갑으로 보냈다. 이렇게 하면 다른 누군가가 나중에 올바른 시드를 찾아내더라도 돈을 가져갈 수 없게 된다.
다시 미스터리로
이제 코인을 안전하게 옮겨 놨으니, 대체 무슨 일이 있었던 건지 알아볼 차례였다. 지갑의 거래 내역을 확인했다:
> siac wallet transactions
[height] [transaction id] [net siacoins] [net siafunds]
108589 427b72c98e8ea64fba234ca2a00288f7a750003a243e6b3e967f5c6d426c2f9f 594.83 SC 0 SF
109002 32ad2729fe6b487aedc1b70d0dff0843404ff1cef69223d5f03699dcd1dbe568 0.00 SC 0 SF
109002 2304da26d61bd2cb7fcac5c7b38a553d788d8dfc386ae4eb47772e36e4a9269d -594.55 SC 0 SF
목록의 마지막 거래는 출금 내역이다. 그건 내가 돈을 가져간 것이다. 신경 쓸 필요는 없다. 0.00 SC 거래는 그냥 노이즈일 뿐이다. 시아 지갑은 자체 주소 간에 돈을 옮길 때 이런 0.0 거래를 생성하기 때문이다.
내가 주목한 건 목록의 첫 번째 거래였다. 그 기록에 따르면 이 지갑은 블록 높이 108,589에서 594.83 SC를 단 한 번 입금받은 것이 전부였다. 블록 높이는 사실상 시아코인 시간 단위로 본 “시각”이다. 시아 블록 익스플로러에서 해당 거래를 확인해 보니, 이 입금은 2017년 6월 7일, 사용자가 레딧에 글을 올리기 이틀 전에 이루어진 것이었다.
사용자는 왜 10유로밖에 입금하지 않았으면서 지갑에 2,000유로를 넣었다고 주장했을까?
림보에 빠진 거래
내가 대담한 절도를 저지르던 당시, 가장 큰 시아코인 거래소인 Poloniex는 시아코인을 사용자 지갑으로 전송하는 데 문제를 겪고 있었다. 사용자 자금이 사라진 건 아니었지만, 사용자가 거래소 계정에서 개인 시아 지갑으로 돈을 보냈는데 Poloniex 쪽 처리가 밀려 며칠 혹은 몇 주 동안 거래가 림보 상태에 빠지는 일이 흔했다.
어쩌면 이 사용자가 보낸 2,000유로가 Poloniex 림보에 갇혀 있었던 건지도 모른다. 그렇다면 그 2,000유로는 결국 지갑에 도착하게 될 테니 아직 가로챌 여지가 있다는 뜻이었다.
새롭고 흥미로운 문제였다. 돈이 아직 지갑에 도착하지도 않았고 언제 도착할지도 모르는 상황에서 어떻게 돈을 훔칠 수 있을까? 나는 노출된 지갑에서 내 지갑으로 계속 돈을 옮기는 배치 스크립트를 작성하기로 했다. 정확히는 배치 스크립트 작성법을 배우기로 했다. 당장 쓸 수 있는 시아 인스턴스가 Windows 가상 머신에 있었는데, 나는 Windows에서 배치 스크립트를 작성하는 법을 몰랐기 때문이다. 결국 이렇게 훌륭한 배치 스크립트를 만들어냈다:
for /l %%x in (1, 0, 100) do (
siac wallet send siacoins 2000SC fff0228f02a01cf8e037047a5ea0db5a88d614913af5f21de209ebf2e58431c68cfc9c27d0e4
)이 스크립트는 레딧 사용자의 노출된 지갑에서 내 지갑 주소로 2,000 SC를 반복해서 보내려고 시도한다. 1부터 100까지 0씩 증가하도록 되어 있으니 사실상 무한 루프다.
지갑 잔액이 0인 동안에는 이 명령이 실패해도 아무 일도 일어나지 않는다. 하지만 내가 기대한 2,000유로가 지갑에 들어온다면, 2,000 SC씩 내 지갑으로 빨아들이게 된다.
나는 비교적 적은 금액인 2,000 SC를 선택했는데, 그게 더 안전했기 때문이다. 사실상 The Price is Right 규칙으로 플레이하는 셈이었다. 만약 당시 2,000유로에 해당하는 125,000 SC를 선택했는데 실제로 지갑에 124,000 SC만 도착했다면, 잔액 부족 오류로 명령이 실패하고 아무것도 전송되지 않았을 것이다.
너무 낮게 잡는다고 해서 거래 수수료를 조금 더 내는 것 외에는 별다른 불이익이 없었다. 2,000 SC는 약 35유로였으니, 만약 약 125,000 SC(2,000유로) 정도의 입금이 들어온다면 내 배치 스크립트는 몇 분 안에 지갑을 비워버릴 수 있었다.
피해자에게 알리기
고백하자면, 그 10유로를 내가 가진다면 뭘 살 수 있을지 꽤 많은 상상을 하긴 했다. 전용기, 롤렉스, 스크루지 맥덕처럼 돈으로 가득 찬 수영장이 있는 대저택 같은 것들 말이다. 하지만 결국 올바른 일을 해야겠다고 결심했고, 시드를 올린 사용자에게 시아코인을 돌려주기로 했다.

만약 내가 돈을 그대로 가졌다면 나의 모습.
그래도 내가 찾은 금액과 그들이 잃었다고 한 금액 사이의 차이는 상황을 어색하게 만들 수도 있었다. 마치 누군가에게 전화해서 “저기, 현금 2,000유로 들어 있는 지갑 잃어버렸다고 전단 붙인 분 맞나요? 지갑은 찾았는데, 안에… 어… 10유로밖에 없더라고요…” (눈을 굴리며)라고 말하는 것과 같았다.
사용자가 처음 게시물을 올린 지 약 두 시간 뒤, 나는 레딧에서 그들에게 개인 메시지를 보냈다. 어떻게 시드를 복구하고, 비슷한 방법을 쓸 수 있는 덜 양심적인 사람들로부터 돈을 안전하게 지키기 위해 돈을 가져갔는지 설명했다. 그리고 유출된 시드와 관련 없는 시아코인 주소를 알려주면 잔액을 돌려주겠다고 요청했다.
몇 시간이 지나고 며칠이 지났지만 아무 답도 없었다. 그들이 시드를 노출한 레딧 게시물을 삭제했다는 걸 알아챘다. 누가 2,000유로를 잃고 온라인에 도움을 요청하는 글을 올린 뒤, 몇 시간 만에 그 일을 완전히 잊어버린 것처럼 행동할까?
미스터리 해결
마침내 월요일 아침, 내 흉악한 범죄의 피해자가 답장을 보내왔다. 그들은 게시물을 올린 직후 돈이 아직 거래소에 그대로 있고 지갑에는 한 번도 도착한 적이 없다는 걸 깨달았다고 설명했다(내가 그럴 줄 알았다!). 그들은 돈을 시드가 안전한 다른 지갑으로 옮길 수 있었다. 실제로 돈을 잃은 게 아니라는 걸 깨닫자, 레딧을 다시 확인할 생각조차 하지 않았다고 했다.
그들은 내가 시드를 복구했다는 사실에 기뻐했는데, 자신들의 암호 문구에 무슨 문제가 있었는지에 대한 그들만의 미스터리가 풀렸기 때문이었다. 그들은 ionic을 정확히 적어 두었지만, 계속해서 tonic이라는 더 친숙한 단어로 잘못 읽고 있었던 것이다. 사용자는 심지어 전액을 가져도 좋다고 제안했지만, 나는 이 블로그 글에서 더 멋져 보일 것 같아서 그 코인이 원래 잃어버린 사용자에게 정당하게 속한다고 느꼈다. 내가 고집하자 그들은 결국 마음을 바꿔 594.8 SC를 돌려줄 주소를 보내왔다.
교훈
절대 시아 지갑 시드를 인터넷에 올리지 마세요. 이 이야기에서 보듯, 틀렸거나 일부만 나온 시드조차도 지갑 전체를 완전히 노출시킬 수 있습니다.
이는 시아코인에만 해당하는 이야기가 아니라 암호화폐 전반에 해당합니다. 모든 암호화폐가 시아처럼 암호 문구를 쓰는 것은 아니지만, 코인을 잃고 싶지 않다면 반드시 비밀로 지켜야 하는 일종의 개인 키를 모두 사용합니다.
글을 무작위로 읽기
댓글
로그인하고 댓글 남기기