JavaScript를 사용하여 인증 코드 해독
최근 인터넷에 인증코드를 해독할 수 있는 JavaScript 스크립트가 등장했습니다 - GreaseMonkey! "Shaun Friedle"이 개발한 이 스크립트는 Megaupload 사이트의 CAPTCHA를 쉽게 해결할 수 있습니다. 믿을 수 없다면 http://herecomethelizards.co.uk/mu_captcha/ 에서 직접 시도해 볼 수 있습니다!
이제 Megaupload 사이트에서 제공하는 CAPTCHA는 위 코드에 의해 패배되었습니다. 여기 코드는 설계되었습니다. 아니요, 별로 좋지 않습니다. 하지만 더 흥미로운 점은 다음과 같습니다.
1. HTML 5의 Canvas 애플리케이션 인터페이스 getImageData를 사용하여 인증 코드 이미지에서 픽셀 데이터를 얻을 수 있습니다. Canvas를 사용하면 이미지를 캔버스에 삽입할 수 있을 뿐만 아니라 나중에 이미지를 다시 추출할 수도 있습니다.
2. 위 스크립트에는 완전히 JavaScript로 구현된 신경망이 포함되어 있습니다.
3. Canvas를 이용해 이미지에서 픽셀 데이터를 추출한 뒤 신경망으로 전송하고, 간단한 광학 문자 인식 기술을 이용해 인증코드에 어떤 문자가 사용됐는지 추론한다.
소스 코드를 읽으면 그것이 어떻게 작동하는지 더 잘 이해할 수 있을 뿐만 아니라 이 확인 코드가 어떻게 구현되는지도 이해할 수 있습니다. 앞에서 본 것처럼 여기에 사용된 CAPTCHA는 그다지 복잡하지 않습니다. 각 CAPTCHA는 3개의 문자로 구성되며, 각 문자는 서로 다른 색상을 사용하고 26자 알파벳의 문자만 사용하지만 모든 문자는 모두 동일한 글꼴을 사용합니다.
첫 번째 단계의 목적은 분명합니다. 인증 코드를 캔버스에 복사하여 회색조 이미지로 변환하는 것입니다.
함수 변환_회색(이미지_데이터){
for (var x = 0; x < image_data.width; x++){
for (var y = 0; y < image_data.height; y++){
var i = x*4+y*4*image_data.width;
var luma = Math.floor(image_data.data[i] * 299/1000 +
image_data.data[i+1] * 587/1000 +
image_data.data[i+2] * 114/1000);
image_data.data[i] = 루마;
image_data.data[i+1] = 루마;
image_data.data[i+2] = 루마;
image_data.data[i+3] = 255;
}
}
}
그런 다음 캔버스를 각각 하나의 문자를 포함하는 세 개의 별도 픽셀 행렬로 분할합니다. 이 단계는 각 캐릭터가 별도의 색상을 사용하므로 색상별로 구분이 가능하기 때문에 매우 쉽게 달성할 수 있습니다.
필터(이미지_데이터[0], 105);
필터(이미지_데이터[1], 120);
필터(이미지_데이터[2], 135);
함수 필터(이미지_데이터, 색상){
for (var x = 0; x < image_data.width; x++){
for (var y = 0; y < image_data.height; y++){
var i = x*4+y*4*image_data.width;
// 특정 색상의 모든 픽셀을 흰색으로 바꿉니다.
if (image_data.data[i] == 색상) {
image_data.data[i] = 255;
image_data.data[i+1] = 255;
image_data.data[i+2] = 255;
// 나머지는 모두 검정색으로
} 또 다른 {
image_data.data[i] = 0;
image_data.data[i+1] = 0;
image_data.data[i+2] = 0;
}
}
}
}
마지막으로 관련 없는 간섭 픽셀이 모두 제거됩니다. 이렇게 하려면 먼저 앞이나 뒤의 검은색(일치하지 않는) 픽셀로 둘러싸인 흰색(일치하는) 픽셀을 찾은 다음 일치하는 픽셀을 삭제하면 됩니다.
var i = x*4+y*4*image_data.width;
위의 var = x*4+(y-1)*4*image_data.width;
아래 var = x*4+(y+1)*4*image_data.width;
if (image_data.data[i] == 255 &&
image_data.data[위] == 0 &&
image_data.data[아래] == 0) {
image_data.data[i] = 0;
image_data.data[i+1] = 0;
image_data.data[i+2] = 0;
}
이제 캐릭터의 대략적인 모양이 파악되었으므로 스크립트는 캐릭터를 신경망에 로드하기 전에 필요한 가장자리 감지를 추가로 수행합니다. 스크립트는 그래픽의 가장 왼쪽, 오른쪽, 위쪽 및 아래쪽 픽셀을 찾아 직사각형으로 변환한 다음 직사각형을 20*25 픽셀 매트릭스로 다시 변환합니다.
Cropped_canvas.getContext("2d").fillRect(0, 0, 20, 25);
var edge = find_edges(이미지_데이터[i]);
Cropped_canvas.getContext("2d").drawImage(캔버스, 가장자리[0], 가장자리[1],
가장자리[2]-가장자리[0], 가장자리[3]-가장자리[1], 0, 0,
가장자리[2]-가장자리[0], 가장자리[3]-가장자리[1]);
image_data[i] = Cropped_canvas.getContext("2d").getImageData(0, 0,
Cropped_canvas.width, Cropped_canvas.height);
위의 처리 후에 우리는 무엇을 얻나요? 흑백으로 채워진 단일 직사각형을 포함하는 20*25 행렬입니다. 좋습니다!
그러면 직사각형을 더욱 단순화하겠습니다. 우리는 신경망에 공급될 "광수용체"로서 매트릭스에서 점을 전략적으로 추출합니다. 예를 들어, 어떤 광수용체는 픽셀이 있거나 없는 9*6에 위치한 픽셀에 해당할 수 있습니다. 스크립트는 이러한 일련의 상태(전체 20x25 행렬 계산보다 훨씬 적음 - 64개 상태만 추출됨)를 추출하고 이러한 상태를 신경망에 공급합니다.
그냥 픽셀을 직접 비교하면 어떨까? 신경망을 사용해야 합니까? 요점은 이러한 모호한 상황을 없애고 싶다는 것입니다. 이전 데모를 시도해 본 적이 있다면 픽셀을 직접 비교하는 것이 신경망을 통해 비교하는 것보다 오류가 더 많이 발생한다는 것을 알 수 있습니다. 그러나 대부분의 사용자에게는 직접적인 픽셀 비교만으로 충분하다는 점을 인정해야 합니다.
다음 단계는 글자를 추측하는 것입니다. 64개의 부울 값(문자 이미지 중 하나에서 얻은)과 사전 계산된 일련의 데이터를 신경망으로 가져옵니다. 신경망의 개념 중 하나는 우리가 얻고자 하는 결과가 미리 알려져 있으므로 결과에 따라 그에 따라 신경망을 훈련할 수 있다는 것입니다. 스크립트 작성자는 스크립트를 여러 번 실행하고 이를 생성한 값에서 거꾸로 작업하여 신경망이 답을 추측하는 데 도움이 될 수 있는 일련의 최고 점수를 수집할 수 있지만 이러한 점수는 특별한 의미가 없습니다.
신경망이 인증코드의 문자에 해당하는 64개의 불리언 값을 계산하면 미리 계산된 알파벳과 비교한 후, 각 문자와 일치하는지에 대한 점수를 부여합니다. (최종 결과는 유사할 수 있습니다. 98%는 A, 36%는 B 등일 수 있습니다.)
인증 코드의 세 글자가 모두 처리되면 최종 결과가 나옵니다. 이 스크립트가 100% 정확하지는 않다는 점에 유의해야 합니다(처음에 문자를 직사각형으로 변환하지 않으면 채점의 정확도가 향상될 수 있는지 궁금합니다). 그러나 적어도 현재 목적으로는 꽤 좋습니다. 그렇게 말해보세요. 그리고 모든 작업은 표준 클라이언트 기술을 기반으로 브라우저에서 완료됩니다!
이 스크립트는 특수한 경우로 간주되어야 하며, 이 기술은 다른 간단한 인증 코드에서는 잘 작동할 수 있습니다. 도달 범위를 넘어서는 것입니다(특히 이러한 종류의 클라이언트 기반 분석). 이 프로젝트를 통해 더 많은 사람들이 영감을 받고 더 멋진 것을 개발할 수 있기를 바랍니다. 잠재력이 너무 크기 때문입니다.