개발하시면서 보안이슈 때문에 여러가지 수단 방법 가리지 않고 다방면으로 애쓰고 계실텐데요
제가 지금 태클걸린게 메모리에 평문을 남기지 말라는 태클을 받았는데....
JAVA단에서 가능한 일인지 실험중입니다 -_-;
간략하게 정리를 죰 해보자면...
1. JAVA의 String객체는 immutable다
Integer라던가 Double라던가 맨 앞이 대문자로 시작하는 기본(?)변수형들은 인스턴스를 생성하면서 상수화 하고
String도 역시 immutable이며 한번 만들면 String pool에 던져놓는다. 랄까요?
2. 가베지 컬렉팅은 도움이 안된다.
메모리 해제는 잡아놓은 특정 주소를 더이상 사용 안한다~ 하고 선언해서
다음에 메모리 잡을 때 그 부분도 수집해서 쓸 수 있게 할 뿐이지
메모리에 있는 값을 지우거나 초기화 하는 짓은 안합니다.
게다가 System.gc( ) ; 였던가요? 콜 해봐야 자바 머신에게 지금이 쓰래기 치우기에 적절한 시간이다! 라고 알릴 뿐이지
실제로 작업을 하는것도 아니죠
3. JAVA는 메모리를 접근 못하게 한다.
일단 C처럼 주소를 안다고 해서 마음대로 접근을 할 수가 없죠...
게다가 1번에 써놨듯 String는 JVM이 내부에서 캐싱하기 때문에 유저레벨에서 접근 불가능이 기본...
일단 String로 만들어지지 않고 처리할 수가 없는 상황인데 String으로 만들어 버리면 지울 수가 없는게 챰 문제죠
채팅방에 계신 존잘러 분들의 도움으로 아래와 같은 코드를 만들어 보았습니다.
public static void main( String[] args ) { try { clearString( "pppop" ) ; } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } public static void clearString( final String str ) throws Exception { System.out.println( str ) ; String str2 = new String( str ) ; Field sv = String.class.getDeclaredField( "value" ) ; sv.setAccessible( true ) ; char value[] = (char[]) sv.get( str2 ) ; for( int i = 0 ; i < value.length ; i++ ) { value[i] = '*' ; } System.out.println( str ) ; String kkk = "Hello World" ; char value2[] = (char[]) sv.get( kkk ) ; value2[0] = 'J' ; value2[1] = 'a' ; value2[2] = 'v' ; value2[3] = 'a' ; value2[4] = ' ' ; System.out.println( kkk ) ; System.out.println("Hello World"); }
위 소스코드실행 결과로는
pppop
*****
Java World
Java World
위와같이 나옵니다.
String pool에 존재하는 텍스트를 바꿔치기 해서 더미 데이터로 바꿔치기가 성공한 것 같습니다.
물론 문제점 있더군요
가장 큰 문제가 이겁니다 -_-;
예를 들자면 포트번호가 3306이라 가정했을때 3306이라는 String이 있는 상태인데...
사용자가 비밀번호를 3306으로 해놨을 경우 사용자가 입력한 3306이라는 텍스트 값을 더미로 교체하게 되면
포트번호 3306이라는 스트링도 ****로 대처됩니다 -_-;
그리고 저 코드를 사용했을때도 우연히 다른 곳에서도 동일한 값을 사용하는건지 조사중인데 폰 메모리 전체를 뒤집어 까보면
문자열이 나오는 경우도 있습니다 -_-;;
역시 JNI를 써서 C단에서 처리하도록 다 뒤집어 까는 방법밖에 선택권이 없는걸까요.;
다른 분들은 어떻게 해결하실란가요??

킈킈
end user 사용 환경에서 루팅이나 adb 연결 없이 메모리 덤프가 가능한가요?
third party 앱이면 불가능 할 것 같은데.. ^^
어떤 식의 해킹을 막는건지 궁금하네요.
암호화 / 복호화를 자유자재로 해야 하는거 아니면 보낼때 비대칭암호화 방식으로 공개키로 암호화 하면 되지 않나요?
공개키야 파일로 되어있어서 inputstream으로 해서 바로 key로 바꾸니 String걱정은 안해도 되고요.
아니면 weakedreference인가? 이거쓰면 안쓸때 거의 바로 없어지지 않나요?
너무 쉽게 생각한거 같긴한데..
1.
==========================================================================
물론 문제점 있더군요
가장 큰 문제가 이겁니다 -_-;
예를 들자면 포트번호가 3306이라 가정했을때 3306이라는 String이 있는 상태인데...
사용자가 비밀번호를 3306으로 해놨을 경우 사용자가 입력한 3306이라는 텍스트 값을 더미로 교체하게 되면
포트번호 3306이라는 스트링도 ****로 대처됩니다 -_-;
==========================================================================
2.
==========================================================================
String pool에 존재하는 텍스트를 바꿔치기 해서 더미 데이터로 바꿔치기가 성공한 것 같습니다.
==========================================================================
너무 오지랖이 넓다고 생각히실 수 있겠지만, 위의 두 문장을 봤을때 지금 사용하는 방법이 어떤 식으로 동작하는지 이해가 부족하신 것 같아서 글을 달아봅니다. 제 생각이 틀렸더라면 사과드리겠습니다. 너무 화내진 말아주세요 ^^
먼저 2에 대해서.
String pool 이라는 표현은 틀렸습니다.
말씀하신 것처럼 Java에서는 어떤 방법으로 직접적인 메모리 참조가 불가능합니다.
하지만 그것을 아심에도 불구하고 String pool 이라는 표현을 사용하신건 마치 현재 사용한 리플렉션 패턴이
메모리를 직접 서칭하는 것 같은 착각을 하신 것 같습니다.
결론은 "절대 그럴일 없습니다."
그럼 왜 system out 에서 두 "Hello World" 가 모두 Java로 출력됐냐면,
두 "Hello World"가 실제로는 하나의 객체이기 때문입니다.
무슨 얘기냐구요? String kkk = "Hello World"; 에서 "Hello World" 랑
System.out.println("Hello World"); 에서 "Hello World"는 같은 객체 입니다.
그리고 String kkk 는 이를 레퍼런스 하고 있을뿐이기 때문에 리플렉션을 통해 스트링을 바꿨을때
마치 다른 두 개의 스트링 객체의 값이 바뀐것 같은 착시를 불러왔을 뿐이죠.
이것은 자바에서는 모든 문자가 String으로 취급되기 때문입니다.
"Hello World" 자체가 하나의 String 개체입니다. "Hello World".trim(); 도 가능하구요.
때문에 clearString이라는 메소드 안에서 "Hello World" 라고 사용하는 것은
"Hello World" 인 로컬 String 변수를 사용하는 것과 다르지 않습니다.
실제로 디버깅을 해보시면 두 "Hello World" 가 모두 같은 id 값을 가지고 있으며
심지어 kkk 도 같은 id를 가지고 있음을 알 수 있죠.
그러므로 생각하시는 1번 처럼 메모리에 문자열이 몽땅 바껴버리는 일은 생기지 않을 것입니다. ^^
이런 오해가 생긴 이유는 사용하신 리플렉션 패턴에 대한 이해가 조금 부족했기 때문이 아닌가 하고 사려됩니다.
리플렉션 패턴은 하나의 Object 에 마치 C처럼 직접 참조를 가능하게 해주는 패턴으로
String.class.getDeclaredField( "value" ); 는
String 개체 내부에 실제 문자열을 다루는 Field인 char[] 을 직접 참조할 수 있게 하는 구문입니다.
고로 "Hello World" String 개체의 내부에 접근해서 char[] 를 직접 참조할 수 있게 되는 것이죠.

제가 자바에 대한 지식이 부족하기 때문에 String pool인지 뭔지 잘 모르겠습니다.
음...
제가 작업하는 도중 문제가 생기는 부분이
서버에서 통신을 해서 여러가지 정보를 받아오고 그중에 '예를 들어서' 포트번호가 3306으로 내려오면
String portNum = (서버에서 받아온 포트번호 3306) ;
이러면 portNum이 가지고 있는 value는 3306이란 문자열값이 있는 주소값이 되고
이때 사용자가 입력한 값을 처리하는데 사용자가 입력한 값이 3306이면
입력받은 값을 가지고 String를 만들게 되면 기존에 저장되있는 3306이라는 문자열이 있는 주소값이
동일하게 value로 들어오고
이걸 리플렉션 패턴으로 참조해서 변경하면 결국 portNum이 가지고 있는 value도 같은 녀석이니 결국
똑같이 변경이 되어버린다는 이야기 입니다.
그러면 결국 포트번호는 잃어버리는 셈이니 당연히 서버접속이 제대로 되지 않고 말이죠
NDK 로 하시는게 제일 적절치 않나 생각합니다.
이런 류의 작업은 아예 내용을 볼 수 없게 힙 영역을 사용해야 메모리상에 올라와도 판독이 불가능할 것 같네요.
q1212님이 말씀하신데로 뭘하든 String객체를 써야한다면, 답이 없을듯 싶은데요?
더군다나 String 객체를 인자값으로 하는 Java함수를 써야 할 경우, NDK도 답이 될 수 없꺼 같은데요?
결국 NDK에서 넘김 자료도 String 객체를 생성할텐데 처리할 수 있는 방법이 없어보입니다.
String 객체를 인자값으로 하는 Java함수 자체를 바꾸지 않는한 힘들겠네요.
메모리에 평문을 남기지 말라.;; 이거 단지 암호화 해서 저장하라... 이런 의미 아닌가요?;;;