태그 보관물: defensive-programming

defensive-programming

C ++에서 민감한 문자열을 가리는 기술 보안 없음, 그리고 다른 쪽 끝에서는

중요한 정보 (비공개로 유지하려는 대칭 암호화 키)를 C ++ 애플리케이션에 저장해야합니다. 간단한 방법은 다음과 같습니다.

std::string myKey = "mysupersupersecretpasswordthatyouwillneverguess";

그러나 strings프로세스 (또는 바이너리 앱에서 문자열을 추출하는 다른 모든 것)를 통해 애플리케이션을 실행 하면 위의 문자열이 표시됩니다.

이러한 민감한 데이터를 숨기려면 어떤 기술을 사용해야합니까?

편집하다:

좋습니다. 거의 모든 사람들이 “실행 파일을 리버스 엔지니어링 할 수 있습니다” 라고 말했습니다 . 물론입니다! 이건 내 반려견 이니까 여기에서 약간 고함을 쳐 보겠습니다.

이 사이트에있는 모든 보안 관련 질문의 99 % (예, 약간 과장)가 “완벽하게 안전한 프로그램을 만들 수있는 방법이 없습니다”라는 급류로 답변되는 이유는 무엇입니까? 대답! 보안은 완벽한 사용 성과 한쪽 끝의 보안 없음, 그리고 다른 쪽 끝에서는 완벽한 보안이지만 사용성은없는 슬라이딩 스케일입니다.

요점은 수행하려는 작업과 소프트웨어가 실행되는 환경에 따라 슬라이딩 스케일에서 위치를 선택한다는 것입니다. 나는 군사 시설용 앱을 작성하는 것이 아니라 가정용 PC 용 앱을 작성하고 있습니다 . 미리 알려진 암호화 키를 사용하여 신뢰할 수없는 네트워크에서 데이터를 암호화해야합니다. 이 경우 “모호함을 통한 보안”으로 충분할 것입니다! 물론, 충분한 시간, 에너지 및 기술을 가진 사람은 바이너리를 리버스 엔지니어링하고 암호를 찾을 수 있지만 무엇을 추측합니까? 상관 없음 :

최고 수준의 보안 시스템을 구현하는 데 걸리는 시간은 금이 간 버전으로 인한 판매 손실보다 더 비쌉니다 (실제로이 제품을 판매하는 것은 아니지만 내 요점을 알 수 있음). 이 푸른 하늘은 새로운 프로그래머들 사이에서 프로그래밍에서 “가능한 절대적인 최선의 방법으로 그렇게한다”는 것은 어리석은 말입니다.

이 질문에 답 해주셔서 감사합니다. 가장 도움이되었습니다. 안타깝게도 한 가지 답변 만받을 수 있지만 유용한 답변을 모두 찬성했습니다.



답변

기본적으로, 프로그램에 대한 접근 및 디버거와 사람 그들이 원하는 경우 응용 프로그램에서 키를 찾을 수 있습니다.

그러나 strings바이너리에서 실행할 때 키가 표시되지 않도록 하려면 예를 들어 키가 인쇄 가능한 범위 내에 있지 않은지 확인할 수 있습니다.

XOR로 키 가리기

예를 들어 XOR을 사용하여 키를 2 바이트 배열로 분할 할 수 있습니다.

key = key1 XOR key2

key(완전히) 임의의 바이트 값을 사용할 수 있는 것과 동일한 바이트 길이로 key1을 만든 다음 다음을 계산합니다 key2.

key1[n] = crypto_grade_random_number(0..255)
key2[n] = key[n] XOR key1[n]

빌드 환경에서이 작업을 수행 한 다음 애플리케이션 에만 저장 key1하고 저장할 key2수 있습니다.

바이너리 보호

또 다른 접근 방식은 도구를 사용하여 바이너리를 보호하는 것입니다. 예를 들어 바이너리가 난독 화되었는지 확인하고 실행되는 가상 머신을 시작할 수있는 몇 가지 보안 도구가 있습니다. 이로 인해 디버그하기가 어렵고 많은 상용 등급 보안 애플리케이션 (아마도 멀웨어)을 보호 할 수있는 중요한 방법이기도합니다.

최고의 도구 중 하나는 Themida입니다 .이 도구는 바이너리를 보호하는 훌륭한 역할을합니다. 리버스 엔지니어링으로부터 보호하기 위해 Spotify와 같은 잘 알려진 프로그램에서 자주 사용됩니다. OllyDbg 및 Ida Pro와 같은 프로그램에서 디버깅을 방지하는 기능이 있습니다.

바이너리를 보호하기 위한 더 큰 목록, 아마도 다소 구식 인 도구 목록도 있습니다 .
그들 중 일부는 무료입니다.

비밀번호 일치

여기 누군가가 해싱 암호 + 소금에 대해 논의했습니다.

사용자가 제출 한 암호와 일치하도록 키를 저장해야하는 경우 사용자 이름, 암호 및 솔트를 결합하여 일방향 해싱 기능을 사용하는 것이 좋습니다. 그러나 이것의 문제는 단방향을 수행하고 결과 해시를 비교할 수 있도록 애플리케이션이 솔트를 알아야한다는 것입니다. 따라서 애플리케이션 어딘가에 소금을 저장해야합니다. 그러나 @Edward가 아래 주석에서 지적했듯이 이것은 레인보우 테이블과 같은 사전 공격으로부터 효과적으로 보호합니다.

마지막으로 위의 모든 기술을 조합하여 사용할 수 있습니다.


답변

우선, 충분히 단호한 해커를 막기 위해 할 수있는 일이 없으며 주변에 해커가 많이 있다는 것을 인식하십시오. 주변의 모든 게임과 콘솔에 대한 보호 기능이 결국 깨져서 일시적인 수정일뿐입니다.

잠시 동안 숨어있을 가능성을 높이기 위해 할 수있는 4 가지 방법이 있습니다.

1) 어떤 식 으로든 문자열의 요소를 숨 깁니다. 즉, 다른 문자열로 문자열을 xoring (^ 연산자)하는 것과 같이 분명한 것은 문자열을 검색 할 수 없게 만드는 데 충분합니다.

2) 문자열을 조각으로 나누십시오-문자열을 나누고 이상한 모듈에서 이상한 이름의 메소드로 비트를 팝하십시오. 문자열이 포함 된 메서드를 쉽게 검색하고 찾을 수 있도록 만들지 마십시오. 물론 일부 메서드는 이러한 모든 비트를 호출해야하지만 여전히 조금 더 어렵게 만듭니다.

3) 메모리에 문자열을 구축하지 마십시오. 대부분의 해커는 인코딩 한 후에 메모리에서 문자열을 볼 수있는 도구를 사용합니다. 가능하면 이것을 피하십시오. 예를 들어 키를 서버로 보내는 경우 문자 단위로 보내면 전체 문자열이 주변에 있지 않습니다. 물론 RSA 인코딩과 같은 방식으로 사용하는 경우 이것은 더 까다 롭습니다.

4) 임시 알고리즘을 수행하십시오.이 모든 것 위에 고유 한 트위스트를 추가하십시오. 생산하는 모든 것에 1을 추가하거나 암호화를 두 번 수행하거나 설탕을 추가 할 수 있습니다. 이것은 바닐라 md5 해싱 또는 RSA 암호화와 같이 누군가가 사용할 때 무엇을 찾아야하는지 이미 알고있는 해커에게 조금 더 어렵게 만듭니다.

무엇보다도 키가 발견 될 때 (애플리케이션이 충분히 인기를 얻게되는 경우) 너무 중요하지 않은지 확인하십시오!


답변

내가 과거에 사용한 전략은 겉보기에 무작위로 보이는 문자 배열을 만드는 것입니다. 처음에 삽입 한 다음 0에서 N까지의 각 단계가 난독 처리 된 문자열의 다음 문자를 포함하는 배열의 숫자 <크기를 생성하는 대수적 프로세스로 특정 문자를 찾습니다. (이 대답은 이제 난독 화 된 느낌입니다!)

예:

문자 배열이 주어짐 (숫자와 대시는 참조 용임)

0123456789
----------
ALFHNFELKD
LKFKFLEHGT
FLKRKLFRFK
FJFJJFJ!JL

그리고 처음 6 개의 결과가 3, 6, 7, 10, 21, 47 인 방정식

“HELLO!”라는 단어가 나올 것입니다. 위의 배열에서.


답변

@Checkers에 동의하며 실행 파일을 리버스 엔지니어링 할 수 있습니다.

좀 더 나은 방법은 동적으로 생성하는 것입니다. 예를 들면 다음과 같습니다.

std::string myKey = part1() + part2() + ... + partN();

답변

물론 사용자에게 제공되는 소프트웨어에 개인 데이터를 저장하는 것은 항상 위험합니다. 충분히 교육받은 (그리고 헌신적 인) 엔지니어라면 데이터를 리버스 엔지니어링 할 수 있습니다.

즉, 사람들이 개인 데이터를 공개하기 위해 극복해야하는 장벽을 높이면 충분한 보안을 유지할 수 있습니다. 그것은 일반적으로 좋은 타협입니다.

귀하의 경우에는 인쇄 할 수없는 데이터로 문자열을 어수선하게 만든 다음 다음과 같은 간단한 도우미 함수를 사용하여 런타임에 디코딩 할 수 있습니다.

void unscramble( char *s )
{
    for ( char *str = s + 1; *str != 0; str += 2 ) {
        *s++ = *str;
    }
    *s = '\0';
}

void f()
{
    char privateStr[] = "\001H\002e\003l\004l\005o";
    unscramble( privateStr ); // privateStr is 'Hello' now.

    string s = privateStr;
    // ...
}

답변

문자열에 대한 간단한 암호화 도구를 만들었습니다. 자동으로 암호화 된 문자열을 생성 할 수 있으며이를 수행 할 수있는 몇 가지 추가 옵션이 있습니다. 몇 가지 예가 있습니다.

전역 변수로서의 문자열 :

// myKey = "mysupersupersecretpasswordthatyouwillneverguess";
unsigned char myKey[48] = { 0xCF, 0x34, 0xF8, 0x5F, 0x5C, 0x3D, 0x22, 0x13, 0xB4, 0xF3, 0x63, 0x7E, 0x6B, 0x34, 0x01, 0xB7, 0xDB, 0x89, 0x9A, 0xB5, 0x1B, 0x22, 0xD4, 0x29, 0xE6, 0x7C, 0x43, 0x0B, 0x27, 0x00, 0x91, 0x5F, 0x14, 0x39, 0xED, 0x74, 0x7D, 0x4B, 0x22, 0x04, 0x48, 0x49, 0xF1, 0x88, 0xBE, 0x29, 0x1F, 0x27 };

myKey[30] -= 0x18;
myKey[39] -= 0x8E;
myKey[3] += 0x16;
myKey[1] += 0x45;
myKey[0] ^= 0xA2;
myKey[24] += 0x8C;
myKey[44] ^= 0xDB;
myKey[15] ^= 0xC5;
myKey[7] += 0x60;
myKey[27] ^= 0x63;
myKey[37] += 0x23;
myKey[2] ^= 0x8B;
myKey[25] ^= 0x18;
myKey[12] ^= 0x18;
myKey[14] ^= 0x62;
myKey[11] ^= 0x0C;
myKey[13] += 0x31;
myKey[6] -= 0xB0;
myKey[22] ^= 0xA3;
myKey[43] += 0xED;
myKey[29] -= 0x8C;
myKey[38] ^= 0x47;
myKey[19] -= 0x54;
myKey[33] -= 0xC2;
myKey[40] += 0x1D;
myKey[20] -= 0xA8;
myKey[34] ^= 0x84;
myKey[8] += 0xC1;
myKey[28] -= 0xC6;
myKey[18] -= 0x2A;
myKey[17] -= 0x15;
myKey[4] ^= 0x2C;
myKey[9] -= 0x83;
myKey[26] += 0x31;
myKey[10] ^= 0x06;
myKey[16] += 0x8A;
myKey[42] += 0x76;
myKey[5] ^= 0x58;
myKey[23] ^= 0x46;
myKey[32] += 0x61;
myKey[41] ^= 0x3B;
myKey[31] ^= 0x30;
myKey[46] ^= 0x6C;
myKey[35] -= 0x08;
myKey[36] ^= 0x11;
myKey[45] -= 0xB6;
myKey[21] += 0x51;
myKey[47] += 0xD9;

복호화 루프가있는 유니 코드 문자열 :

// myKey = "mysupersupersecretpasswordthatyouwillneverguess";
wchar_t myKey[48];

myKey[21] = 0x00A6;
myKey[10] = 0x00B0;
myKey[29] = 0x00A1;
myKey[22] = 0x00A2;
myKey[19] = 0x00B4;
myKey[33] = 0x00A2;
myKey[0] = 0x00B8;
myKey[32] = 0x00A0;
myKey[16] = 0x00B0;
myKey[40] = 0x00B0;
myKey[4] = 0x00A5;
myKey[26] = 0x00A1;
myKey[18] = 0x00A5;
myKey[17] = 0x00A1;
myKey[8] = 0x00A0;
myKey[36] = 0x00B9;
myKey[34] = 0x00BC;
myKey[44] = 0x00B0;
myKey[30] = 0x00AC;
myKey[23] = 0x00BA;
myKey[35] = 0x00B9;
myKey[25] = 0x00B1;
myKey[6] = 0x00A7;
myKey[27] = 0x00BD;
myKey[45] = 0x00A6;
myKey[3] = 0x00A0;
myKey[28] = 0x00B4;
myKey[14] = 0x00B6;
myKey[7] = 0x00A6;
myKey[11] = 0x00A7;
myKey[13] = 0x00B0;
myKey[39] = 0x00A3;
myKey[9] = 0x00A5;
myKey[2] = 0x00A6;
myKey[24] = 0x00A7;
myKey[46] = 0x00A6;
myKey[43] = 0x00A0;
myKey[37] = 0x00BB;
myKey[41] = 0x00A7;
myKey[15] = 0x00A7;
myKey[31] = 0x00BA;
myKey[1] = 0x00AC;
myKey[47] = 0x00D5;
myKey[20] = 0x00A6;
myKey[5] = 0x00B0;
myKey[38] = 0x00B0;
myKey[42] = 0x00B2;
myKey[12] = 0x00A6;

for (unsigned int fngdouk = 0; fngdouk < 48; fngdouk++) myKey[fngdouk] ^= 0x00D5;

전역 변수로서의 문자열 :

// myKey = "mysupersupersecretpasswordthatyouwillneverguess";
unsigned char myKey[48] = { 0xAF, 0xBB, 0xB5, 0xB7, 0xB2, 0xA7, 0xB4, 0xB5, 0xB7, 0xB2, 0xA7, 0xB4, 0xB5, 0xA7, 0xA5, 0xB4, 0xA7, 0xB6, 0xB2, 0xA3, 0xB5, 0xB5, 0xB9, 0xB1, 0xB4, 0xA6, 0xB6, 0xAA, 0xA3, 0xB6, 0xBB, 0xB1, 0xB7, 0xB9, 0xAB, 0xAE, 0xAE, 0xB0, 0xA7, 0xB8, 0xA7, 0xB4, 0xA9, 0xB7, 0xA7, 0xB5, 0xB5, 0x42 };

for (unsigned int dzxykdo = 0; dzxykdo < 48; dzxykdo++) myKey[dzxykdo] -= 0x42;

답변

joshperry가 지적한 것처럼 보호하려는 대상에 다소 의존합니다. 경험상 소프트웨어를 보호하기위한 라이선싱 체계의 일부라면 신경 쓰지 마십시오. 그들은 결국 그것을 역 설계 할 것입니다. ROT-13과 같은 간단한 암호를 사용하여 간단한 공격 (줄이 흐르는 문자열)으로부터 보호하십시오. 사용자의 민감한 데이터를 보호하려면 로컬에 저장된 개인 키로 해당 데이터를 보호하는 것이 현명한 조치인지 의문이들 것입니다. 다시 한 번 당신이 보호하려는 것에 달려 있습니다.

편집 : 당신이 그것을 할 경우 Chris가 지적한 기술 조합이 rot13보다 훨씬 낫습니다.