3.5 예제 3 Hancitor with Ficker Stealer and Cobalt Strike 트래픽 분석

 

[그림 40] 기본 필터링 진행

예제 1과 예제 2의 앞 부분에서 진행한 것과 같이 표준 웹 필터를 사용해 pcap을 살펴보았다.

 

[그림 41] php 문자열이 포함된 트래픽 확인

기본적으로 필터링한 트래픽들 중에서 www.nuicala.inspia.net/mars.php 도메인을 찾을 수 있다.

 

[그림 42] .php로 끝나는 악성 Word 문서 제공 URL을 찾기 위한 필터링 진행

http.request.method == GET and http.request.URI contains .php

 

 .php로 끝나는 악성 word 문서를 제공하는 URL을 찾기 위해 위 필터 표현식을 사용하였다. [그림 40]에 나온 트래픽들은 HTTP GET 요청을 보내며 .php로 끝나는 도메인에 대한 것들이다.

 

 

[그림 43] 두 번째 트래픽의 HTTP Stream 확인

 SaveAs() 스크립트 함수 직후, 많은 양의 base64 텍스트가 있는 것을 확인했다.

이는 word 문서에 대한 악의적인 행동이라고 볼 수 있다.

 

 

[그림 44] HTTP Stream의 끝 부분 확인

 많은 양의 base64 텍스트의 끝을 확인해보면‘0125_206410993.doc’라는 파일을 저장하도록 하는 스크립트가 있다. 이는 악성 word 문서의 파일 이름을 표시하고 브라우저를 다른 URL로 새로고침하는 스크립트이다.

 

 

[그림 45] Export Object 기능으로 HTTP 객체 확인

 

[그림 46] mars.php 추출

WiresharkExport Objects 기능을 사용하여 [그림 44]에 해당하는 악성 스크립트를 추출했다.

 

[그림 47] mars.php 웹 브라우저에서 실행

 

 이 스크립트를 웹 브라우저에서 열면 악성 word 문서를 저장하는 옵션이 포함된 팝업 메뉴가 뜨는 것을 확인할 수 있다. 웹 브라우저의 주소 또한 GET 방식으로 ‘0125_206410993.doc’를 다운로드하는 URL로 변경되었다.

 

 예제 3에서 Ficker Stealerdrivewaysnowservice.com에 대한 DNS 쿼리를 생성하고 TCP 포트 80을 통해 HTTP가 아닌 트래픽을 해당 도메인으로 보낸다. 이때 보내는 트래픽에 감염된 Windows 호스트에서 도난당한 데이터가 포함되어 있다. 이를 확인하기 위한 필터링을 진행했다.

 

 

[그림 48] Ficker Stealer 감염 여부를 확인하기 위한 필터링 진행

(http.request.URI contains “/mars.php:) or (http.host.contains api.ipify.org)

 

 Ficker Steal에 대한 exe는 자체적으로 악성이 아니며 api.ipify.org?/format=xml에 대한 IP 주소 검사를 발생시키기 때문에 위와 같은 필터 표현식을 사용하였다. 그리고 hostnameapi.ipify.org인 트래픽을 찾을 수 있다.

 

 

[그림 49] drivewaysnowservice.com을 반환하는 DNS 쿼리를 찾기 위한 필터링 진행

dns.qry.name contains drivewaysnowservice

 

위의 필터 표현식을 사용해 drivewaysnowservice.com으로 변환된 IP 주소를 찾았고, IP 주소는 8.209.78.68이다.

 

 

[그림 50] 해당 IP 주소에 대한 TCP SYN Stream을 찾기 위한 필터링 진행

ip.addr == 8.209.78.68 and tcp.flags == 0x0002

 

 위의 필터 표현식을 통해 8.209.78.68에 대한 TCP SYN 스트림을 찾은 후, TCP 소스포트로 49813번을 사용하는 트래픽의 TCP Stream을 따라가보았다.

 

[그림 51] TCP Stream 내용 확인

 이 Stream 에는 3개의 클라이언트 트래픽과 201개의 서버 트래픽이 존재했다. 서버 트래픽의 대부분은 인코딩되거나 난독화된 것처럼 보였다. 그 중에서 특이한 점은 Hostdrivewaysnowservice.comClient 트래픽에서 /6gbd5ws.exe 실행 파일에 대해 GET 요청을 한다는 점이었다. 실행 파일은 Hancitor가 후속 멜웨어를 동작시키는데 사용할 가능성이 높으므로 앞서 진행된 결과를 이용하여 분석을 계속 진행했다.

 

 

[그림 52] Hancitor 후속 행위를 찾기 위한 필터링 진행

ip.addr == 8.209.78.68 and http.host.contains drivewaysnowservice

 

 이를 확인하기 위해 ip 주소가 8.209.78.68이면서 hostnamedrivewaysnowservice가 포함된 트래픽을 확인하기 위한 필터링을 진행하였다.

 

 그리고 [그림 52]에 나타난 것처럼 HancitorCobalt Strike Ficker Stealer에 대한 후속 악성 코드를 보내는 것을 확인할 수 있다.

 

drivewaysnowservice.com GET /2021.bin

drivewaysnowservice.com GET /2021s.bin

drivewaysnowservice.com GET /6gfbd5ws.exe

 

 

[그림 53] Cobalt Strike 감염 여부를 확인하기 위한 기본 필터링 진행

 23.106.80.14:8080의 여러 프레임에 표시되는 것이 Cobalt Strike 프레임이다. /submit.php?id=를 포함하는 모든 HTTP POST 요청에는 감염된 각 Windows 호스트에 대해 고유한 식별번호가 있음을 확인할 수 있다.

 

 

[그림 54] /submit.php 문자열을 포함하는 트래픽의 TCP Stream 확인

 Cobalt Strike를 통해 전송된 멜웨어는 피해 호스트에서 디코딩되는 인코딩 바이너리로 나타나고 있다. 인코딩되어 있기 때문에 pcap 파일에서 실제 악성코드 바이너리를 찾을 수 없기 때문에, 전송된 후속 악성코드는 감염 후의 트래픽으로 식별할 수 있다.

 

 

[그림 55] Cobalt Strike를 통해 전송된 네트워크 ping 도구의 ICMP 트래픽

icmp

 

ICMP 필터링을 통해 네트워크 ping 도구가 Cobalt Strike를 통해 전송되었음을 알 수 있다.

 

 감염된 Windows 호스트에서 복구된 네트워크 ping 도구 샘플은 내부의 라우팅이 불가능한 IPv4 주소 공간을 대상으로 1,700만 개 이상의 IP 주소를 ping 하므로 약 1.5GB 크기의 수많은 ICMP의 트래픽을 생성한다.

 

이를 확인해보면, ping 트래픽은 다음 주소에 위치한다.

 

192.168.0.0 ~ 192.168.254.254

172.16.0.0 ~ 172.31.254.254

10.0.0.0 ~ 10.254.254.254

 

위의 다수의 ping 트래픽이 Cobalt Strike를 통해 전송된다는 사실을 알 수 있다.

 


3.6 예제 4 Hancitor with Ficker Stealer and Cobalt Strike 트래픽 분석

 

[그림 56] 기본 필터링 진행

이번 예제에서도 우선 기본 필터링을 실행시켜주었다.

 

[그림 57] php 문자열이 포함된 트래픽 확인

기본적으로 필터링한 트래픽들 중에서 www.premierpt.co.uk/footage.php 도메인을 찾을 수 있다

 

[그림 58] .php로 끝나는 악성 Word 문서 제공 URL을 찾기 위한 필터링 진행

http.request.method == GET and http.request.URI contains .php

 

 .php로 끝나는 악성 word 문서를 제공하는 URL을 찾기 위해 위 필터 표현식을 사용하였다. [그림 58]에 나온 트래픽들은 HTTP GET 요청을 보내며 .php로 끝나는 도메인에 대한 것들이다.

 

 

[그림 59] 두 번째 트래픽의 HTTP Stream 확인

 Length582인 두 번째 트래픽의 HTTP stream을 따라가보았다. saveAs에 대한 스크립트 함수 직후, 많은 양의 base64 텍스트가 있다. 이는 word 문서에 대한 악의적인 행동이라고 볼 수 있다.

 

[그림 60] HTTP Stream의 끝 부분 확인

 많은 양의 base64 텍스트의 끝을 확인해보면‘0202_10846666250132.doc’라는 파일을 저장하도록 하는 스크립트가 있다. 이는 악성 word 문서의 파일 이름을 표시하고 브라우저를 다른 URL(http://www.docusign.com/?download=0202_10846666250132.doc)로 새로고침하는 스크립트이다.

 

[그림 61] Export Object 기능으로 HTTP 객체 확인

 

[그림 62] footage.php 웹 브라우저에서 실행

 이를 웹 브라우저에서 열게되면 악성 word 문서를 저장하는 옵션이 포함된 팝업 메뉴가 뜨는 것을 확인할 수 있다. 웹 브라우저의 URL도 예상대로 변경되었음을 확인할 수 있다.

 

 예제 4에서 Ficker Stealerbobcatofredding.com에 대한 DNS 쿼리를 생성하고 TCP 포트 80을 통해 HTTP가 아닌 트래픽을 해당 도메인으로 보인다. 이때 보내는 트래픽에는 감염된 Windows 호스트에서 도난당한 데이터가 포함되어 있다.

 

[그림 63] Ficker Stealer 감염 여부를 확인하기 위한 필터링 진행

(http.request.URI contains “/footage.php:)or(http.host.contains api.ipify.org)

 

 Ficker Stealer에 대한 exe는 자체적인 악성이 아닌 api.ipify.org?/format=xml에 대한 IP 주소 검사를 발생시키기 때문에 위와 같은 필터링을 해주었고, hostnameapi.ipify.org인 트래픽을 찾을 수 있다.

 

 

[그림 64] bobcatofredding.com을 반환하는 DNS 쿼리를 찾기 위한 필터링 진행

dns.qry.name contains bobcatofredding

 

위의 필터 표현식을 사용해 bobcatofredding.com으로 변환된 IP 주소를 찾았고, IP 주소는 8.209.78.68이다.

 

 

[그림 65] 해당 IP 주소에 대한 TCP SYN Stream을 찾기 위한 필터링 진행

ip.addr == 8.209.78.68 and tcp.flags == 0x0002

 

 위의 필터 표현식을 통해 8.209.78.68에 대한 TCP SYN 스트림을 찾은 후, TCP 소스포트로 49800번을 사용하는 트래픽의 TCP Stream을 따라가보았다.

 

 

[그림 66] TCP Stream 내용 확인

 이 Stream에는 3개의 클라이언트 트래픽과 200개의 서버 트래픽이 존재했다. 서버 트래픽의 대부분은 인코딩되거나 난독화된 것처럼 보였다. 그 중, Hostbobcatofredding.comClient 트래픽에서 /6lavfdk.exe 실행파일에 대한 GET 요청을 하는 것을 발견했다. 실행 파일은 Hancitor가 후속 멜웨어를 동작시키는데 사용할 가능성이 높으므로 앞서 진행된 결과를 이용하여 분석을 계속 진행했다.

 

 

[그림 67] Hancitor 후속 행위를 찾기 위한 필터링 진행

ip.addr == 8.209.78.68 and http.host.contains bobcatofredding

 

 이를 확인하기 위해 ip 주소가 8.209.78.68이면서 hostnamebobcatofredding 이 포함된 트래픽을 확인하기 위한 필터링을 진행하였다.

 

 그리고 [그림 67]에 나타난 것처럼 HancitorCobalt Strike Ficker Stealer에 대한 후속 악성 코드를 보내는 것을 확인할 수 있다.

 

bobcatofredding.com GET /0102.bin

bobcatofredding.com GET /0102s.bin

bobcatofredding.com GET /6lavfdk.exe

 

 

[그림 68] Cobalt Strike 감염 여부를 확인하기 위한 기본 필터링 진행

 192.254.79.71:8080의 여러 프레임에 표시되는 것이 Cobalt Strike 프레임이다.

Cobalt Strike의 경우 /submit.php?id=를 포함하는 모든 HTTP POST 요청에 감염된 각 Windows 호스트에 대해 고유한 식별번호가 있는 것으로 보인다.

 

[그림 69] /submit.php 문자열을 포함하는 트래픽의 TCP Stream 확인

 Cobalt Strike를 통해 전송된 멜웨어는 피해 호스트에서 디코딩되는 인코딩 바이너리로 나타나고 있다. 인코딩되어 있기 때문에 pcap 파일에서 실제 악성코드 바이너리를 찾을 수 없기 때문에, 전송된 후속 악성코드는 감염 후의 트래픽으로 식별할 수 있다.

 

 예제 4 에선, NetSupportManager RAT 멜웨어에 대한 트래픽를 찾아봤다. 상업적으로 사용 가능한 RAT(원격 접속 프로그램)NetSupportManager는 관리자가 클라이언트 컴퓨터에 원격으로 액세스하기 위해 사용된다. 그러나 소유자가 모르게 피해자의 컴퓨터에 설치하는 악의적인 행위자에 의해 악용되어 컴퓨터에 대한 무단 액세스 권한을 얻을 수 있다.

 

 배포를 위해 공격자는 손상된 웹 사이트를 악용하고 RATAdobe Flash, Chrome FireFox를 비롯한 자주 사용되는 응용 프로그램의 가짜 업데이트로 가장한다. 사용자가 업데이트를 진행하게 되면 대부분 Dropbox 링크에서 악성 JavaScript 파일이 다운로드 되는 방식이다.

 

 예제 4 에서 RAT는 먼저 geo.netsupportsoftware.com 도메인에 대한 IP 주소 확인을 수행했고, HTTP 요청 헤더에서 사용자 에이전트 문자열의 일부를 NetSupportManager를 사용하여 트래픽을 생성했다.

 

 

[그림 70] NetSupportManager RAT C2 트래픽 확인을 위한 필터링 진행

http.user_agent contains “NetSupportManager” or http.host contains netsupport

 

따라서 위의 필터 표현식을 사용해 필터링을 진행했다. [그림 68]에 포함된 트래픽은 아래와 같다.

 

62.172.138.55:80 geo.netsupportsoftware.com GET /location/loca.asp

46.17.106.230:4543 46.17.106.230 POST http://46.17.106.230/fakeurl.htm

 

 

[그림 71] NetSupportManager RAT C2 트래픽의 TCP 스트림

 

 4개의 최신 pcap을 본 결과 네트워크 트래픽에서 일관된 패턴을 발견했고, 이러한 패턴을 통해 네트워크 내에서 Hancitor 활동 및 관련 맬웨어를 식별하는 데 사용할 수 있다.

 


 본 프로젝트는 와이어샤크를 통한 다운로더 악성코드가 포함된 악성패킷에 대한 분석 과정을 파악하여 내부 공격이 어떠한 순서로 이뤄지는지 알기 위해 진행되었다.

 Hancitor는 사용자가 아무런 의심없이 스팸 메일에 포함된 링크를 클릭했을 시 악성 word 문서 파일을 다운로드 받도록 하며, word 문서 내 활성화 된 매크로를 클릭하면 그 순간부터 빠르게 유포되기 시작한다. 이는 Ficker StealerCobalt Strike 등을 사용한 추가적인 악성 행위를 유발시켰으며 감염된 Windows 호스트가 Hancitor 이메일을 보내는데 사용되는 스팸봇으로서 동작시키게 하는 것까지 확인할 수 있다.

 

 이와 같은 다운로더 악성코드 피해를 줄이기 위해서는 출처가 불분명한 메일의 첨부파일과 URL 실행을 금지하고, 백신의 최신버전을 유지하며 실시간 감시 기능을 실행해야 한다. 또한 파일 실행 전 최신 버전의 백신으로 검사해야 하고 OS(운영체제) 및 인터넷 브라우저, 오피스 소프트웨어에 최신 보안 패치 적용을 해야한다.

 

'Project > Network' 카테고리의 다른 글

Hancitor Analysis by Wireshark (3)  (0) 2021.08.19
Hancitor Analysis by Wireshark (2)  (0) 2021.08.19
Hancitor Analysis by Wireshark (1)  (0) 2021.08.19
DNS Spoofing 공격 & 차단 방법  (0) 2021.06.15

3.3 예제 2-1 Hancitor with Ficker Stealer and Cobalt Strike 트래픽 분석

 

[그림 24] 기본 필터링 진행

 예제 1과 같이 웹 트래픽을 빠르게 검토하기 위한 기본 필터 표현식을 사용해주었다.

[그림 24]에서 보듯이 필터링한 트래픽을 확인하던 중, docs.google.com 도메인이 포함된 트래픽을 발견했다. 앞서 진행했던 [그림 10]에서부터 [그림 17]까지의 과정을 거쳐 /commemorative.php를 통해 악성 Word 문서를 저장하는 옵션이 포함된 팝업 메뉴가 뜨는 것을 확인할 수 있다.

 

 악성 Word 문서가 다운로드 되면, 매크로가 활성화되어 Hancitor 초기 감염이 일어난다. 감염된 Windows 호스트는 api.ipify.org으로 IP 주소를 확인한다. 그 후, C2 트래픽을 유발한다. 202011월부터 Hancitor C2 트래픽의 URL은 항상 /8/form.php로 종료되었다. 따라서 HTTP POST URL/8/form.php로 끝나는 Hancitor C2 트래픽을 찾기 위한 필터링을 해주었다.

 

 

[그림 25] Hancitor C2 트래픽을 찾기 위한 필터링 진행

http.request.uri contains “/8/forum.php” or http.host contains api.ipify.org

 

 Hancitor C2 트래픽이 유발되었는지 확인하기 위해 /8/forum.php를 포함하는 요청 URI를 확인해준다. 그리고 Ficker Stealer에 대한 exe는 자체적으로 악성이 아닌 api.ipify.org/?format=xml에 대한 IP 주소 검사를 발생시키기 때문에 api.ipify.org를 포함하는 host 이름을 확인해주었다.

 

[그림 25]에 출력된 것들은 Hancitor C2 트래픽과 함께 Hancitor Ficker StealaerIP 및 주소이다.

 

api.ipify.org GET

anumessensan.ru POST /8/forum.php

api.ipify.org GET /?format=xml

 

 

[그림 26] Hancitor 후속 행위를 찾기 위한 필터링 진행

http.request.uri contains .exe or or http.request.uri contains .bin

 

실행 파일(exe, bin)을 요청으로 받는 URI를 필터링해 주었다. 이는 Hancitor에서 보낸 후속 Malware를 필터링한 것이다.

 

 그리고 [그림 26]에 나타난 것처럼 HancitorCobalt Strike Ficker Stealer에 대한 후속 악성코드를 보내는 것을 확인할 수 있다.

 

backupez.com GET /0902.bin

backupez.com GET /0902s.bin

backupez.com GET / 6yudfgh.exe

 

 Cobalt Strike에 대한 GET 요청 중 하나는 URLs가 있지만, 다른 하나에는 없다. 이런 경우, Cobalt Strike로 인한 HTTP HTTPS 트래픽이 모두 표시되어야 한다.

다시 웹 기본 필터로 돌아가서, backupez.com에 대한 트래픽 후 발생하는 HTTP 요청 유형을 확인했다.

 

 

[그림 27] 기본 필터링 진행

 그 결과, Cobalt Strike에 대한 TCP 포트 1080을 통한 HTTP 트래픽과 TCP 포트 4443을 통한 HTTPS 트래픽이 모두 표시된 것을 확인할 수 있다.

 

위의 그림에 표시된 Cobalt Strike로 인한 트래픽은 다음과 같다.

104.160.190.114 : 8080-GET / WWFh

104.160.190.114 : 8080-GET / fwlink

104.160.190.114 port 4443-HTTPS 트래픽

 

 

[그림 28] WWFh로 끝나는 HTTP GET 요청 트래픽 TCP Sream 확인

 WWFh로 끝나는 첫 번째 HTTP GET 요청은 Cobalt Strike에 사용되는 Windows 바이너리이다. HTTP 요청에 대한 TCP Stream을 확인해보니 base 64 방식으로 인코딩된 208,473byte 사이즈만큼의 데이터를 반환해 주는 것을 확인할 수 있다.

 

[그림 28]에서 보듯이, 첫 번째 HTTP 요청의 104.160.190.114:8080 / fwlink48byte의 인코딩된 데이터를 반환한다.

 

 또한, Cobalt Strike C2에 대한 HTTPS 트래픽은 TCP 포트 4443을 통해 104.160.190.114에 대한 트래픽이 약 1초에 한 번 나타나며, 사용량이 많은 것을 확인할 수 있다.

 

 

3.4 예제 2-2 Hancitor with Ficker Stealer and Cobalt Strike 트래픽 분석

 

 이 예제 파일은 예제 2-1과 같은 Hancitor 감염이 일어난 후의 상황을 다룬다. 따라서, 예제 2-1에서 봤던 Cobalt Strike C2 트래픽의 HTTP GET 요청을 볼 수 있다. 그러나 이 예제 pcap 파일은 Hancitor가 다른 Windows 실행 파일을 보냈으며 이 실행 파일이 Send-Safe 기반 Spam Malware라는 점이 다르다.

 

[그림 29] fwlink로 끝나는 HTTP GET 요청 트래픽 TCP Sream 확인

 

[그림 30] 기본 필터링 진행

 

[그림 31] Windows 실행파일을 찾기 위한 필터링 진행

http.request.uri contains .exe

 

backupez.com에 대한 또 다른 HTTP GET 요청을 확인할 수 있다. 이 도메인은 Hancitor가 후속 멜웨어를 동작시키는데 사용하는 도메인이다. URL47.exe로 끝난다.

 

 

[그림 32] Export Object 기능으로 HTTP 객체 확인

 

[그림 33] 47.exe 실행파일 저장

 HTTP object를 살펴본 결과, 47.exe 실행파일이 존재했다. Send-Safe는 지금은 없어졌지만 send-safe.com이라는 이름의 상업적인 프로그램으로 팔고 있다. 이 프로그램은 Windows 컴퓨터를 스팸봇의 호스트로 감염되게 할 수 있는 많은 위협 요소를 가진 맬웨어로 사용된다.

 

Send-Safe 스팸봇에 감염된 Windows 호스트는 다음의 트래픽을 생성한다.

 

UDP 포트 50026을 통한 31.44.184.47로의 트래픽

TCP 포트 50025를 통한 31.44.184.47로의 HTTPS 트래픽

Hancitor를 푸시하는 악성 스팸을 보내는 SMTP 트래픽*

 

SMTP(Simple Mail Transfer Protocol)은 전자 우편을 송수신하는데 사용되는 TCP/IP 프로토콜이다. 일반적으로 POP3 또는 IMAP(Internet Message Access Protocol)와 함께 사용되어 메시지를 서버 메일함에 저장하고 사용자를 위해 서버에 주기적으로 메시지를 다운로드 한다.

 

[그림 34] UDP 트래픽 찾기 위한 필터링 진행

udp.port == 50026

 

Send-Safe UDP 트래픽을 찾기 위해 위와 같은 필터 표현식을 사용해주었다.

 

 

[그림 35] HTTPS, SMTP 트래픽 찾기 위한 필터링 진행

(tcp.port == 50025 and tls.handshake.type == 1) or smtp.data.fragment

 

Send-Safe HTTPS 트래픽 및 SMTP 트래픽을 보기 위해 위와 같은 필터 표현식을 사용해주었다.

 

 

[그림 36] HTTPS 트래픽의 Send-Safe 관련 인증서 발급자 데이터 확인

tls.handshake.type == 11 and ip.addr == 31.44.107.47

 

 TCP 포트 50025를 통한 HTTPS 트래픽에서 [그림 36]와 같이 Send-SafeorganizationName commonName으로 사용하는 인증서 발급자 데이터가 존재함을 확인했다.

 

그리고, 예제 2-2pcap에선 SMTP 트래픽이 암호화되지 않았으므로, 와이어샤크를 사용하여 이메일을 추출할 수 있다.

 

[그림 37] IMF 객체 확인
[그림 38] Export Object 기능으로 IMF 객체 확인

 IMFInternet Message Format 으로, 텍스트가 인터넷을 통해 전송되는 형식이다. SMTP가 메시지 봉투라고 한다면, IMF는 봉투 안에 있는 편지라고 비유할 수 있다.

예제 2-2 pcap에는 167개의 IMF 객체가 있었고, Send-Safe에 감염된 Windows 호스트에서 초당 수십개의 메시지가 전송됨을 확인할 수 있다.

 

[그림 39] 메일 하나를 저장하여 내용을 확인

 [그림 37]의 이메일 객체 중 하나를 저장하면 eml 파일을 얻을 수 있는데, 이를 열어 <a> 태그로 docs.google.com/document/d/e/... 형식의 주소가 링크화 된 것을 확인할 수 있다. 이 링크는 메일의 본문에 포함되어 있고, 이 링크를 클릭하면 취약한 word 파일이 열리며 앞서 진행됐던 것들과 유사한 방식으로 Hancitor 감염이 시작된다.

 

'Project > Network' 카테고리의 다른 글

Hancitor Analysis by Wireshark (4)  (0) 2021.08.19
Hancitor Analysis by Wireshark (2)  (0) 2021.08.19
Hancitor Analysis by Wireshark (1)  (0) 2021.08.19
DNS Spoofing 공격 & 차단 방법  (0) 2021.06.15

3.2 예제 1 Hancitor with Ficker Stealer and Cobalt Strike 트래픽 분석

 

실습 환경: KaliLinux 2021.1

 

[그림 7] 예제 1에 사용되는 pcap 파일  

 

[그림 8] 웹 트래픽을 빠르게 파악하기 위한 기본 필터링 진행

[그림 8]에 사용된 필터 표현식은 아래와 같다.

 

http.request or tls.handshake.type == 1

http.request 값은 http 요청에 대한 URL을 나타내고, tls.handshake.type == 1https 또는 TLS 트래픽에 사용되는 도메인 이름을 나타낸다.

!(ssdp)

트래픽에는 정상적인 활동 중 UDP 포트 1900을 이용한 HTTP 요청이 포함된다. UDP 포트 1900을 통한 이 HTTP 트래픽은 SSDP(Simple Service Discovery Protocal)이다. SSDPPnP(Plug n Play)장치를 검색하는데 사용되는 프로토콜이며 일반 웹 트래픽과 관련이 없다.

!(udp.port == 1900)!(ssdp)는 같은 결과를 나타낸다. Window 호스트에서 감염된 pcap을 검토할 때 SSDP 활동을 필터링하면 트래픽을 훨씬 명확하게 볼 수 있다.

 

 

[그림 9] 필터링한 트래픽 중 docs.google.com 도메인 발견

 [그림 9]에서 보듯이 필터링한 트래픽을 확인하던 중, docs.google.com 도메인이 포함된 트래픽을 발견했다. docs.google.com은 본질적으로 악성 도메인이 아니지만,

<22.2 Hancitor Malware 동작방식>에서 파악했듯이 Hancitor는 주로 Google 드라이브를 통해 악용된다.

대부분의 악성 Word 문서를 제공하는 페이지로 연결될 때의 URL.php로 끝난다. 대부분의 경우, 이러한 URLHTTPS를 사용하지만, 이 예제 pcap에서는 Word 문서의 URL로 암호화되지 않은 HTTP를 사용하기 때문에 쉽게 찾을 수 있다.

 

 

[그림 10] .php로 끝나는 악성 Word 문서 제공 URL을 찾기 위한 필터링 진행

 

http.request.method == GET and http.request.URI contains .php

위의 필터 표현식으로 필터링을 진행하였다. [그림 8]에서 보듯이 Host namesomdeeppalace.com인 트래픽이 slickness.php 문서에 대한 HTTP GET 요청을 보내고 있다.

 

 

[그림 11] 해당 트래픽의 HTTP Stream 확인

GET 요청을 보낸 트래픽에 대한 자세한 정보를 파악하기 위해 HTTP Stream을 따라가 보았다.

 

 

[그림 12] 트래픽의 내용 확인

 

 saveAs() 함수 직후, 많은 양의 base64 기반의 텍스트를 찾을 수 있다. 이는 Word 문서에 대한 악의적인 행동이라고 볼  수 있다.

 

[그림 13] HTTP Stream 끝 부분 확인

 base64 기반으로 생성된 ‘0217_2857682888090.doc’라는 파일을 저장하도록 하는 스크립트를 확인할 수 있다. 이는 악성 Word 문서의 파일 이름을 표시하고 브라우저를 다른 URL로 연결시키는 스크립트이다.

 

 

[그림 14] Export Object 기능으로 HTTP 객체 확인

 

[그림 15] 그림 7에서 발견한 slickness라는 이름의 php 객체 저장

 HTTP object를 살펴본 결과, php 객체가 두 개인 것을 확인할 수 있다. [그림 12] 에서 본 것처럼 이러한 많은 base64 텍스트 정보를 다 담기엔 534KB 크기를 가지는 두 번째 slickness.php 파일이 우리가 찾던 악성 스크립트에 적합하다고 생각하여 두 번째 항목을 저장해주었다.

 

[그림 16] 저장된 slickness.php

 

[그림 17] slickness.php 웹 브라우저에서 실행

 악성 Word 문서를 저장하는 옵션이 포함된 팝업 메뉴를 확인할 수 있다. 웹 브라우저 또한 cashplus 문자열로 끝나는 URL로 변경되었다. 이 악성 Word 문서는 2분 내로 Hancitor C2 트래픽을 유발한다.

 

 

 

1.2 Ficker Stealer Infection 트래픽 분석

 

예제 1 에서 Ficker Stealersweyblidian.com에 대한 DNS 쿼리(IP주소로 domain name을 변환하는 메커니즘)를 생성하고 TCP 포트 80을 통해 HTTP가 아닌 트래픽을 해당 도메인으로 보낸다. 이 트래픽은 감염된 Windows 호스트에서 도난당한 데이터로 구성된다.

 

[그림 18] sweyblidian.com을 반환하는 DNS 쿼리를 찾기 위한 필터링 진행

dns.qry.name contains sweyblidian

위의 필터 표현식을 사용해 sweyblidian.com으로 변환된 IP 주소를 찾았고, IP 주소는 185.100.65.29이다.

 

[그림 19] 해당 IP 주소에 대한 TCP SYN Stream을 찾기 위한 필터링 진행

ip.addr == 185.100.65.29 and tcp.flags == 0x0002

 

위의 필터 표현식을 통해 185.100.65.29에 대한 TCP SYN 스트림을 찾았다.

 

 

[그림 20] 해당 트래픽의 TCP Stream 확인

그 후, TCP 소스포트로 49807번을 사용하는 트래픽의 TCP Stream을 따라가보았다.

 

[그림 21] TCP Stream 내용 확인

 TCP Stream의 대부분은 인코딩되거나 난독화된 것처럼 보인다.

데이터를 추출한 Fickser Stealer는 더 이상 아무런 행동을 하지 않는다. 이 시점에서 감염된 컴퓨터가 독립 실행형 Windows 호스트인 경우, Hancitor C2 트래픽만 표시되게 된다. 그러나 감염된 PCAD 환경의 일부인 경우, Cobalt Strike도 확인해야 한다.

 

 

 

1.3 Cobalt Strike 트래픽

 

Cobalt Strike에 대한 HancitorHTTP GET 요청 시 URL에 문자 ‘s’를 사용한다는 특성을 참고하여 분석을 시작하였다.

 

[그림 22] 기본 필터링 진행

 TCP 포트 443을 통해 192.99.250.2의 여러 트래픽에 표시되는 것이 Cobalt Strike 프레임이다. Cobalit Strike HTTPS로 인한 HTTPS 트래픽에 대한 인증서 발급자 데이터는 국가, 지역 및 조직과 같은 항목에 대한 모든 식별 필드가 비어있기 때문에 비정상적이라고 할 수 있다. 따라서 이러한 Cobalt Strike 트래픽에 담긴 인증서 데이터를 찾아봤다.

 

 

[그림 23] 해당 IP주소에 해당하는 트래픽을 찾기 위한 필터링 진행

 

tls.handshake.type == 11 and ip.addr == 192.99.250.2

 

 tls.handshake.type == 11은 클라이언트가 서버에 보내는 3-way-handshaking의 성공적 완료 메시지이다. 필터링을 통해 얻은 두 개의 프레임에서 프레임 세부 정보를 확인해보면, 인증서 발급자 데이터에 대한 필드가 모두 비어있는 것을 확인할 수 있다.

 

 이 예제 1 pcap의 나머지 부분에는 Hancitor C2에 대한 HTTP GET 요청과 Cobalt Strike에 대한 HTTPS 트래픽이 존재한다. Hancitor 트래픽은 약 2분마다 발생하고, Cobalt Strike에 대한 HTTPS 트래픽은 1초마다 발생한다는 사실 또한 파악할 수 있다.

 

'Project > Network' 카테고리의 다른 글

Hancitor Analysis by Wireshark (4)  (0) 2021.08.19
Hancitor Analysis by Wireshark (3)  (0) 2021.08.19
Hancitor Analysis by Wireshark (1)  (0) 2021.08.19
DNS Spoofing 공격 & 차단 방법  (0) 2021.06.15

 2019년 전 세계를 집어삼킨 ‘코로나 19 바이러스’로 인해 우리의 일상과 사회 전반의 모습이 완전히 바뀌었다. ...대학교는 온라인 강의로 새 학기를 시작했고 과제 제출과 시험 등도 온라인 상에서 진행되었다. 비대면 사회로 변화되면서 일터로 출.퇴근하던 업무방식에도 큰 변화가 일어났다. 업무공간에서도 ‘사회적 거리두기’가 시행되면서 원격 및 재택근무, 화상회의 등 비대면 업무방식이 확산되었다. 2020 8월 코로나 19가 재확산되면서 디지털 인프라가 구축되어 있는 ICT 기업뿐만 아니라 대기업 및 유통업계도 재택근무에 돌입했다.

 

 그 결과, 온라인 상 보안의 위험성이 부각되기 시작했다. 인터넷 사용자를 대상으로 하는 사이버 공격이 증가하고 그 방법이 다양화 되고 있기 때문에 사용자의 주의가 필요하다. ASEC 주간 악성코드 통계에 따르면, 공격의 종류는 크게 InfoStealer, RAT (Remote Administration Tool), Coin Miner, 랜섬웨어, 다운로더 등으로 나눌 수 있으며 이번 분석에 사용된 Hancitor Malware는 이 중 다운로더 공격에 속한다.

 

[그림 1] ASEC 주간 악성코드 통계

 다운로더 악성코드의 대부분은 스팸 메일을 통해 유포되며, 사용자가 첨부파일을 실행시키도록 유도하는 방식으로 공격이 진행된다. 그리고 공격자들은 백신 진단 우회를 목적으로 프로그래밍 언어를 활용해 악성코드 외형을 패커로 패킹한다.

 따라서 이러한 방식으로 공격하는 인터넷 상에 공개된 맬웨어 중 하나를 선택하여 트래픽을 분석해보고, 공격이 내부에서 어떻게 진행되었는지 분석해보고자 한다.

 

 

2.1 Hancitor Malware 

 Chanitor라고 불리는 Hancitor는 피해자의 컴퓨터로의 초기 접속을 위해 사용되는 다운로더 악성코드이다. 주된 목적은 Hancitor 자체에 포함된 여러 개의 암호화된 URL 중 하나로 접속하여 맬웨어 페이로드(payload)를 다운로드하여 실행하는 것이다.

 Hancitor Pony Vawtrak 맬웨어를 공동으로 배포한 2014년에 처음 목격되었다. Hancitor 2014년부터 2021년까지 꾸준하게 활동하고 있으며 최근에는 추가적 활동으로 Cobalt Strike를 설치하는 형태가 유포되고 있어 사용자의 주의가 필요하다.

 

2.2 Hancitor Malware 동작 방식

[그림 2] Hancitor Infection Event Chain

 Hancitor는 스팸 메일의 첨부파일이나 다운로드 링크를 이용해 유포된다. 이 이메일들은 각각 docs.google.com의 구글 드라이브 URL HTTPS 링크로 포함한다. 이 구글 드라이브 페이지는 취약한 워드 문서 다운로드 페이지로 연결된다. 워드 문서가 다운로드 되면, 워드 문서에서 [그림 3]과 같은 매크로가 활성화되어 워드 문서에 내장된 Hancitor 페이로드 동적 링크 라이브러리(DLL)를 추출하고 실행시켜 초기 감염이 일어난다. 감염된 호스트는 Hancitor 명령과 컨트롤(C2) 트래픽을 일으킨다.

 

 

[그림 3] Microsoft Office 문서 파일 매크로 활성화 시

 실행된 DLL은 패킹된 형태이며, 실행 중 실제 Hancitor DLL을 디코딩한 후 메모리 상에서 실행시킨다. 이번에 분석할 Hancitor 25KB 사이즈를 갖는 작은 다운로더 악성코드이며, 사용자 및 컴퓨터 이름, IP 주소, 운영체제 버전과 같은 감염 PC의 기본적인 정보를 획득한 후 C&C 서버에 전달한다(C2 트래픽). C&C 서버는 현재 악성코드를 기준으로 3개를 갖는데, 여기에 차례대로 접속하면서 실패할 경우 다음 C&C 서버에 접속을 시도한다.

 

Hancitor C&C 주소

hxxp://sumbahas[.]com/8/forum.php

hxxp://staciterst[.]ru/8/forum.php

hxxp://semareake[.]ru/8/forum.php

 

 그러면 서버는 Hancitor로 하여금 하나 혹은 그 이상의 추가적인 맬웨어 페이로드를 다운로드하고 실행시키도록 한다. 이 예제에선 멜웨어 후속 조치를 위한 두 개의 추가적 페이로드를 전달하는 것을 확인할 수 있다.

 

 

2.2.1 Hancitor 후속 조치를 위한 추가적 페이로드 “Cobalt Strike”

 

 첫 번째 추가적 페이로드는 악성 행위자들 사이에서 점점 더 인기를 끌고있는 합법적인 침투 테스트 도구인 Cobalt Strike였다. 감염된 호스트가 동적인 디렉터리 환경(AD 환경)일 때, Hancitor Cobalt Strike를 전송한다. Cobalt Strike는 키 로깅과 같은 정보 탈취가 가능하며, 추가적인 공격을 용이하게 하기 위한 시스템 취약성을 이용할 수 있고, 감염된 시스템과 피해자의 네트워크 모두에서 그 활동을 숨기는데 도움이 되는 다양한 방법들을 갖고 있다. Cobalt Strike는 네트워크 ping 도구 또는 NetSupport Manager RAT 기반의 멜웨어와 같은 추가 악성 파일에 대한 또 다른 액세스 접근 채널을 제공한다. 예제 4에선 감염된 Windows 호스트가 Hancitor 이메일을 보내는데 사용되는 스팸봇으로서 동작하는 것까지 확인할 수 있다.

 

2.2.2 Hancitor 후속 조치를 위한 추가적 페이로드 “Ficker Stealer”

 

 두 번째 추가적 페이로드는 2020 8월에 처음 확인된 Malware-as-a-Service(MaaS) Ficker Stiller 였다. Ficker Stealer는 합법적인 서비스를 제공하는 것처럼 위장한 광고를 통해 정보를 탈취하는 악성코드이다. 2020년부터 러시아어를 사용하는 포럼에서 판매되고 있으며, 이를 구매한 공격자들이 다양한 방법으로 해당 악성코드를 유포하고 있다.

 

 Ficker Stealer 공격 단계

[1] 사용자가 다운로드 된 파일을 실행하면 해당 악성코드는 감염환경의 국가 정보를 확인한다. 만약, 러시아, 벨라루스, 우즈베키스탄, 우크라이나, 아르메니아, 카자흐스탄, 아제르바이잔에 포함된 국가에서 실행되었다면, 추가 악성행위를 수행하지 않고 종료된다.

 

[2] 감염환경의 인터넷 연결 여부를 확인한다. 이를 위해 IP 확인 사이트인 ipify로 연결하여 api.ipifiy.org/?format=xml에 대한 IP 주소 검사를 발생시킨다. 그 후, 감염환경의 IP 주소를 C:\ProgramData\kaosdma.txt에 저장한다.

 

[3] 인터넷에 연결되어 있다면, [그림 4]와 같이 감염환경의 정보, 스크린샷, 브라우저, 암호화폐 지갑 및 소프트웨어 정보를 수집한다.

 

[4] 수집한 정보를 XOR로 인코딩하여 공격자의 서버로 전송한다.

 

[그림 4] Ficker Stealer의 탈취 정보

 Ficker Stealer EXE 파일은 몇 주 동안 동일하게 사용된다.  2021-01부터 2021-03까지의 SHA256 해시 값은 다음과 같다.

 

94e60de577c84625da69f785ffe7e24c889bfa6923dc7b017c21e8a313e4e8e1

 

 이번 분석에 사용된 Hancitor pcaps Windows 기반의 맬웨어를 포함하고 있다. 따라서 Windows 기반의 컴퓨터를 사용한다면 감염의 위험이 있어, Windows 기반이 아닌 BSD, Linux, MacOS 등에서 실행하였다.

 


3.1 분석에 필요한 사전지식

 

3.1.1 TCP Flags 16bit Value

 

 TCP(Transmission Control Protocol)의 동작은 3-WAY Handshake 방식을 통해 두 지점 간에 세션을 연결하여 통신을 시작하고 4-WAY Handshake를 통해 세션을 종료하여 통신을 종료한다. 세션 연결과 해제 이외에도 데이터를 전송하거나 거부, 세션 종료 같은 기능이 패킷의 Flag 값에 따라 달라지게 된다. TCP Flag는 기본적으로 6가지로 구성된다.

SYN(연결 시작) : 통신 시작 시 세션을 연결하기 위한 플래그

ACK(확인 응답) : 송신 측으로부터 패킷을 잘 받았음을 알려주는 플래그

RST(연결 재설정) : 비정상적인 세션을 끊기 위해 연결을 재설정하기 위한 플래그

PSH(삽입) :빠른 응답을 위해 OSI 7계층의 응용계층으로 바로 전송하도록 하는 플래그

URG(긴급) :긴급한 데이터 전송을 위해 우선순위를 높여 긴급하게 전달하는 플래그

FIN(연결 종료) : 더 이상 전송할 데이터가 없고 세션 연결을 종료시킨다는 플래그

 

TCP Flags 16진수로 표현했을 때의 값을 나타내면 아래 [그림 5]과 같다.

[그림 5] TCP Flag 16진수 표현값

 

3.1.2 SSL/TLS Handshake

[그림 6] SSL/TLS Handshake 과정

 HTTPS로 접속할 때는 TCP 3-way Handshake와 별도로 SSL/TLS Handshake 과정을 거친다. SSL/TLS Handshake 통신을 암호화하는 데 사용할 암호화 알고리즘과 키를 결정하고 서버를 확인하며 실제 데이터 전송을 시작하기 전에 보안 연결이 이루어졌는지 확인하는 과정이다. 해당 과정을 통하여 세션을 생성할 수 있고 이 후 두 노드의 통신은 세션 상에서 수행된다. SSL/TLS Handshake를 사용하면 SSL 또는 TLS 클라이언트 및 서버가 통신하는 보안 키를 설정할 수 있다.

 

 SSL(Secure Sockets Layer) Certificate Authority(CA)라 불리는 서드파티로부터 서버와 클라이언트 인증을 하는데 사용된다. 주로 전송계층과 응용계층 사이에서 보안조치를 할 때 사용한다. SSL TLS(Transport Layer Security)의 과거 명칭이다.

 

[그림 6]에서 보듯, 이 과정에서 클라이언트와 서버는 Client Hello, Server Hello, Certificate, Server Hello Done 등의 메시지를 주고 받는다. 위의 메시지를 주고받은 후, 클라이언트와 서버는 데이터를 교환하기 시작한다.

 


[1] ASEC 주간 악성코드 통계 《https://asec.ahnlab.com/ko/22926/

[4] Ficker Stealer 탈취 정보 https://isarc.tachyonlab.com/m/3936

[5] TCP Flag 16진수 표현값 https://m.blog.naver.com/PostView.nhn?blogId=stop2y&logNo=221018537228&proxyReferer=https:%2F%2Fwww.google.com%2F

 

'Project > Network' 카테고리의 다른 글

Hancitor Analysis by Wireshark (4)  (0) 2021.08.19
Hancitor Analysis by Wireshark (3)  (0) 2021.08.19
Hancitor Analysis by Wireshark (2)  (0) 2021.08.19
DNS Spoofing 공격 & 차단 방법  (0) 2021.06.15

운좋게도 대상을 받은 작품, 

Teenager + 친 = TeenGü 라는 의미를 가진 Android Application을 소개한다.

배경
목표
기능

이 외에도 많은 기능들이 있으나 게시글에 다 담지 못하였다. 

 

처음 개발한 애플리케이션이라 미흡한 부분들이 많지만 팀원들 모두 만족한 결과물이다.

 

아래 깃허브 주소로 코드를 확인할 수 있고, 아래 유튜브 링크를 통해 시연영상을 확인할 수 있다.

https://github.com/YRim99/TeenGu.git

 

GitHub - YRim99/TeenGu: Android Application

Android Application. Contribute to YRim99/TeenGu development by creating an account on GitHub.

github.com

 

https://www.youtube.com/watch?v=i5qniTcAeQI 

 

'Project > Android App' 카테고리의 다른 글

TimerApp  (0) 2021.07.13
StopwatchApp  (0) 2021.07.12
BmiCalculatorApp  (0) 2021.07.10
리스트 컨트롤

 

1. 리스트 컨트롤

- 리스트 박스 vs 리스트 컨트롤

리스트 박스 : 단일 항목 나열

리스트 컨트롤 : 리스트를 여러개 가짐

- 다양한 뷰 스타일 제공 (Icon, Small Icon, List, Report 스타일)

 

- 다양한 매크로 함수 제공

- 포함 헤더 : Commctrl.h, Windowsx.h

 

Report 스타일로 설정

 

이(Report)는 엑셀과 비슷한 구조로, 행과 열로 구분할 수 있다.

열은 컬럼(Column),  행은 아이템(Item)이라고 부른다. 열의 모든 내용은 서브 아이템(Sub Item)이라고 부른다.

시작 인덱스는 모두 0 이다.

 

 

ListView_InsertColumn() 함수를 통한 컬럼 생성과 문자열 등록 

void ListView_InsertColumn(
   HWND hwnd, //윈도우 핸들
   int iCol, //컬럼 인덱스
   const LPLVCOLUMN pcol //LVCOLUMN의 메모리 주소
);

 

+ LVCOLUMN 구조체

typedef struct tagLVCOLUMNA {
  UINT  mask; //컬럼에 적용할 속성 설정(뭐뭐 조합할지)
  int   fmt; 
  int   cx;
  LPSTR pszText;
  int   cchTextMax;
  int   iSubItem;
  int   iImage;
  int   iOrder;
  int   cxMin;
  int   cxDefault;
  int   cxIdeal;
} LVCOLUMNA, *LPLVCOLUMNA;

 

+ LVCLOUMN mask : 컬럼에 적용할 속성 결정 (OR연산 이용)

LVCF_FMT 정렬(왼쪽, 가운데, 오른쪽)
LVCF_WIDTH 길이 적용
LVCF_TEXT 문자열 적용
LVCF_SUBITEM 서브 아이템 적용

 

+ LVCOLUMN fmt : 컬럼과 서브 아이템의 데이터 정렬 형식 지정

LVCFMT_LEFT Text is left-aligned.
LVCFMT_RIGHT Text is right-aligned.
LVCFMT_CENTER Text is centered.

주의할 점 : 인덱스가 0인 컬럼과 서브아이템은 항상 왼쪽 정렬

 

+ LVCOLUMN cx, pszText, iSubItem 

cx          : 가로 길이

pszText   : 문자열 지정

iSubItem : 서브 아이템 인덱스 

 


#include "framework.h"
#include "WindowsProject9_ListControl.h"
#include "Commctrl.h"
#include "Windowsx.h"

INT_PTR CALLBACK DlgProc(HWND, UINT, WPARAM, LPARAM);

int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
    _In_opt_ HINSTANCE hPrevInstance,
    _In_ LPWSTR    lpCmdLine,
    _In_ int       nCmdShow)
{
    DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), NULL, DlgProc);
    return 0;
}

INT_PTR CALLBACK DlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    UNREFERENCED_PARAMETER(lParam);
    static HWND hList;
    char* strMenu[] = { "항목1","항목2", "항목3", "항목4" };
    LVCOLUMN lvColumn;

    switch (message)
    {
    case WM_INITDIALOG:
        hList = GetDlgItem(hDlg, IDC_LIST1);
        lvColumn.mask = LVCF_FMT | LVCF_WIDTH | LVCF_SUBITEM | LVCF_TEXT;
        lvColumn.fmt = LVCFMT_CENTER;
        
        for (int i = 0; i < 4; i++) {
            lvColumn.cx = strlen(strMenu[i]) * 10;//너무 붙어있지 말라고
            lvColumn.pszText = strMenu[i];
            ListView_InsertColumn(hList, i, &lvColumn);
        }
        return (INT_PTR)TRUE;

    case WM_CLOSE:
        EndDialog(hDlg, 0);
        return (INT_PTR)TRUE;
    }
    return (INT_PTR)FALSE;
}

 

항목1,2,3,4 컬럼이 생성된다.

 


※ 아이템 관련 매크로 함수 ListView_GetItemCount

void ListView_GetItemCount(
   HWND hwnd
);
void ListView_InsertItem(
   HWND hwnd,
   const LPLVITEM pitem
);

 

+ LVITEM 구조체

typedef struct tagLVITEMA {
  UINT   mask; //보통 LVIF_TEXT 만 지정하면 됨
  int    iItem; //ListView_GetItemCount
  int    iSubItem; //0 고정
  UINT   state;
  UINT   stateMask;
  LPSTR  pszText; //문자열 설정
  int    cchTextMax;
  int    iImage;
  LPARAM lParam;
  int    iIndent;
  int    iGroupId;
  UINT   cColumns;
  PUINT  puColumns;
  int    *piColFmt;
  int    iGroup;
} LVITEMA, *LPLVITEMA;

 

변수 추가
버튼 눌림을 위한 WM_COMMAND문 추가
버튼을 누를때 마다 아이템이 자신의 인덱스를 넣으면서 추가된다.

 


※ 아이템 항목 설정 ListView_SetItemText() 이용 예제

#include "framework.h"
#include "WindowsProject9_ListControl.h"
#include "Commctrl.h"
#include "Windowsx.h"
#include <stdio.h>

INT_PTR CALLBACK DlgProc(HWND, UINT, WPARAM, LPARAM);

int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
    _In_opt_ HINSTANCE hPrevInstance,
    _In_ LPWSTR    lpCmdLine,
    _In_ int       nCmdShow)
{
    DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), NULL, DlgProc);
    return 0;
}

INT_PTR CALLBACK DlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    UNREFERENCED_PARAMETER(lParam);
    static HWND hList;
    char* strMenu[] = { "순번","C","C++","WIN32" };
    LVCOLUMN lvColumn;
    LVITEM lvItem;
    char string[100];

    switch (message)
    {
    case WM_INITDIALOG:
        hList = GetDlgItem(hDlg, IDC_LIST1);
        lvColumn.mask = LVCF_FMT | LVCF_WIDTH | LVCF_SUBITEM | LVCF_TEXT;
        lvColumn.fmt = LVCFMT_CENTER;
        
        for (int i = 0; i < 4; i++) {
            lvColumn.cx = 50;
            lvColumn.pszText = strMenu[i];
            ListView_InsertColumn(hList, i, &lvColumn);
        }
        return (INT_PTR)TRUE;

    case WM_CLOSE:
        EndDialog(hDlg, 0);
        return (INT_PTR)TRUE;
    
    case WM_COMMAND:
        if (LOWORD(wParam) == IDC_BUTTON1) {
            lvItem.iItem = ListView_GetItemCount(hList);
            lvItem.iSubItem = 0;
            lvItem.mask = LVIF_TEXT;
            sprintf_s(string, "%d", lvItem.iItem);
            lvItem.pszText = string;
            ListView_InsertItem(hList, &lvItem);

            GetDlgItemText(hDlg, IDC_EDIT1, string, 10);
            ListView_SetItemText(hList, lvItem.iItem, 1, string);
            GetDlgItemText(hDlg, IDC_EDIT2, string, 10);
            ListView_SetItemText(hList, lvItem.iItem, 2, string);
            GetDlgItemText(hDlg, IDC_EDIT3, string, 10);
            ListView_SetItemText(hList, lvItem.iItem, 3, string);

            //에디트 컨트롤 클리어
            SetDlgItemText(hDlg, IDC_EDIT1, NULL);
            SetDlgItemText(hDlg, IDC_EDIT2, NULL);
            SetDlgItemText(hDlg, IDC_EDIT3, NULL);
            
            return (INT_PTR)TRUE;
        }
    }

    return (INT_PTR)FALSE;
}

값을 입력하면 삽입과 동시에 칸이 초기화 되는 것까지 확인 가능하다


※ 서브 아이템 데이터 가져오기 ListView_GetItemText()

void ListView_GetItemText(
   HWND hwndLV,
   int iItem,
   int iSubItem_,
   LPTSTR pszText_, //문자열 저장을 위한 메모리 공간
   int cchTextMax_
);

 

1단계) 아이템 선택에 대한 메세지 알아내기

- 발생 메세지 : WM_NOTIFY, 부가적 정보가 담긴 메모리 주소가 lparam에 담김

(부가적 정보 = 통지 코드, 컨트롤 핸들, 아이디가 저장된 메모리 주소)

 

lParam 다루는 방법 : NMHDR 구조체 사용

typedef struct tagNMHDR {
  HWND     hwndFrom; //윈도우 핸들
  UINT_PTR idFrom; //컨트롤 아이디
  UINT     code; //통지 코드, NM_CLICK
} NMHDR;

 

2단계) 아이템 인덱스 알아내기 ListView_GetNextItem()

void ListView_GetNextItem(
   HWND hwnd,
   int i, //임의의 인덱스, 선택이 없을 때:-1
   UINT flags //LVNI_SELECTED
);

 

+) 리스트 컨트롤 뷰 설정  ListView_SetExtendedListViewStyle()

void ListView_SetExtendedListViewStyle(
   HWND hwndLV,
   DWORD dwExStyle //LVS_EX_FULLROWSELECT : 하나의 행 전체 선택, LVS_EX_GRIDLINES : 아이템(열) 전체 선택
);

 

DlgProc에 인덱스 변수 추가

 

WM_INITDIALOG에 ListView_SetExtendedListViewStyle 속성을 설정한다

 

WM_NOTIFY 문 추가
선택 결과가 아래 Edit Control에서 보인다


※ 데이터 값 수정하기

ListView_GetItemText()

ListView_SetItemText()

nIndex 값 다시 설정
nIndex 가 -1이 아닐때. 즉, 선택되었을 때를 의미
수정

'Project > WIN32 API' 카테고리의 다른 글

컨트롤(2)  (0) 2021.07.22
컨트롤  (0) 2021.07.21
다이얼로그  (0) 2021.07.21
키보드, 마우스  (0) 2021.07.13
그래픽 오브젝트  (0) 2021.07.12
1. 에디트 컨트롤(박스)
2. 콤보 박스
3. 리스트 박스

 

※ 컨트롤도 윈도우이다. 따라서 윈도우 핸들이 존재한다.

GetDlgItem()을 통해 핸들을 얻는다.

 

1. Edit Control

: 사용자로부터 데이터 입력 받음

데이터 입력, 수정, 삭제를 위한 컨트롤

 

사용방법? 함수 사용

 

여러 속성을 제공하고 있음

- Password : 비밀번호(값이 보이지 않음)

- Visible : 화면에 출력

- Read Only : 오로지 읽도록(처리된 결과를 보여줄 때 사용)

- Border : 경계선 있/없

- Number : 값을 숫자로만 받음

...

 

※ 내용 읽기 함수  GetDlgItemText, GetDlgItemInt

UINT GetDlgItemTextA( //문자열 읽기
  HWND  hDlg,
  int   nIDDlgItem,
  LPSTR lpString,
  int   cchMax
);
UINT GetDlgItemInt( //정수값 받기
  HWND hDlg,
  int  nIDDlgItem,
  BOOL *lpTranslated, //메모리 주소(보통 NULL)
  BOOL bSigned //TRUE면 음수도 받을 수 있고, FALSE면 양수만 입력 받음
);

 

<EditControl에 쓰여진 데이터 읽기 코드>

#include "framework.h"
#include "WindowsProject7_control2.h"
#include <stdio.h>

INT_PTR CALLBACK    DlgProc(HWND, UINT, WPARAM, LPARAM);

int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
    _In_opt_ HINSTANCE hPrevInstance,
    _In_ LPWSTR    lpCmdLine,
    _In_ int       nCmdShow)
{
    DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), NULL, DlgProc);
    return 0;
}

INT_PTR CALLBACK DlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    char string[100];
    int nVal;

    switch (message)
    {
    case WM_CLOSE:
        EndDialog(hDlg, 0);
        return (INT_PTR)TRUE;

    case WM_COMMAND:
        switch (LOWORD(wParam))
        {
        case IDC_BUTTON1:
            GetDlgItemText(hDlg, IDC_EDIT1, string, 99);
            MessageBoxA(hDlg, string, "확인", MB_OK);
            return (INT_PTR)TRUE;

        case IDC_BUTTON2:
            nVal = GetDlgItemInt(hDlg, IDC_EDIT1, NULL, FALSE);
            sprintf_s(string, "%d", nVal);
            MessageBoxA(hDlg, string, "확인", MB_OK);
            return (INT_PTR)TRUE;
        }       
    }
    return (INT_PTR)FALSE;
}

GetDlgItemText 버튼으로 문자열을 읽어들이고, GetDlgItemInt 버튼으로 양의 정수만(마지막 매개변수를 False로 설정)을 읽어들이고 있다.

 

문자열 입력 후 Text()버튼
문자열 입력 후 Int() 버튼

 

숫자 입력 후 Text() 버튼과 Int()버튼


※ 데이터 설정 함수 SetDlgItemText, SetDlgItemInt

BOOL SetDlgItemText(
  HWND   hDlg,
  int    nIDDlgItem,
  LPCSTR lpString //설정할 문자열 지정
);
BOOL SetDlgItemInt(
  HWND hDlg,
  int  nIDDlgItem,
  UINT uValue, //지정하고자 하는 값
  BOOL bSigned //부호 설정
);

 

Get -> Set 으로 바꾸면 된다.

 

각각의 버튼을 누르면 Edit Text에 내용이 설정된다.


2. Combo Box

여러 항목을 리스트 형태로 출력 및 선택

※ Simple, Drop Down, Drop List 형태 제공

Dialog 설정

'정렬' 속성은 기본적으로 TRUE로 설정이 되어 있다. 

따라서 우리가 항목을 추가할 때 자동으로 정렬이 되므로 자동 정렬되는 것을 원치 않으면 FALSE로 설정하면 된다.

'형식' 속성을 보면 Simple, Drop Down, Drop List 형태를 제공하고 있음을 알 수 있다.

 

 

※ 항목이 펼쳐지는 영역 설정 가능

항목이 펼쳐지는 영역을 설정할 수도 있다.

 

※ 매크로 함수 사용

- Windowsx.h 포함

실제 MSDN에 combo box를 검색하면 사용가능한 매크로 함수 목록이 뜬다.

 

※ 윈도우 핸들 구하는 함수 GetDlgItem

HWND GetDlgItem(
  HWND hDlg,
  int  nIDDlgItem
);

 

ComboBox_AddString() 사용 실습 

void ComboBox_AddString(
   hwndCtl,
   lpsz
);

 

#include "framework.h"
#include "WindowsProject8_ComboBox.h"
#include <stdio.h>
#include "Windowsx.h"

INT_PTR CALLBACK DlgProc(HWND, UINT, WPARAM, LPARAM);

int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
    _In_opt_ HINSTANCE hPrevInstance,
    _In_ LPWSTR    lpCmdLine,
    _In_ int       nCmdShow)
{
    DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), NULL, DlgProc);
    return 0;
}

INT_PTR CALLBACK DlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    static HWND hComboBox; //정적 변수 선언
    static const char* strMenu[] = { "메뉴1","메뉴2","메뉴3" }; //문자열 각각의 메모리 주소가 저장됨
    switch (message)
    {
    case WM_INITDIALOG:
        hComboBox = GetDlgItem(hDlg, IDC_COMBO1); //핸들 얻음
        for (int i = 0; i < 3; i++) {
            ComboBox_AddString(hComboBox, strMenu[i]);
        }
        return (INT_PTR)TRUE;

    case WM_CLOSE:
        EndDialog(hDlg, 0);
        return (INT_PTR)TRUE;
    }
    return (INT_PTR)FALSE;
}

hComboBox 핸들을 선언하고, 각각의 문자열 주소를 저장할 strMenu를 선언한다.

Dialog를 실행할 때 콤보박스의 핸들을 얻어와서 hComboBox에 저장한 뒤 AddString()을 통해 설정한 문자열을 넣어주고 있다.

 

결과

 

 

※ ComboBox_DeleteString() 사용 실습 - 역순으로 삭제

void ComboBox_DeleteString(
   hwndCtl,
   index
);

DlgProc 함수에서 표시된 부분을 추가한다. DeleteString 함수 사용

 

버튼을 클릭할 때마다 항목이 하나씩 사라지고, 모두 지운 뒤에 버튼을 클릭하면 설정한 메세지 창이 뜬다.

 

 

ComboBox_InsertString() 사용하여 항목 추가 실습

void ComboBox_InsertString(
   hwndCtl,
   index,
   lpsz
);

 

코드 추가
WM_COMMAND 문을 switch-case문으로 바꾼 후 삽입 버튼을 누를 때 삽입이 되도록 한다.

 

해당 인덱스에 알맞게 추가되었다.

 

ComboBox_GetCurSel()을 통해 인덱스 조사

void ComboBox_GetCurSel(
   hwndCtl
);

IDC_BUTTON3 추가 후 인덱스 조사하기

 

정상적으로 처리하는 것을 알 수 있다.

단지, 메뉴를 추가한 뒤 인덱스가 늘어난 경우에 대해선 처리하지 못하므로 추후에 이를 case 0,1,2로 나누지 말고 for문으로 바꿔야할 것 같다.

 


3. List Box

- 리스트 출력 영역 안에서 항목 선택

- 지정된 영역보다 많은 항목은 스크롤 바를 통해 선택

- windowsx.h 포함

 

MSDN에서 list box macro를 검색하면 해당 매크로 함수들의 목록이 나온다

 

IDC_BUTTON4 추가
리스트 박스 메뉴 추가

여기서 10이 넘어가는 순간 자동정렬 되므로 속성창에 가서 '정렬'을 FALSE로 바꿔줘야 한다.

 

 

 

'Project > WIN32 API' 카테고리의 다른 글

컨트롤(3)  (0) 2021.07.22
컨트롤  (0) 2021.07.21
다이얼로그  (0) 2021.07.21
키보드, 마우스  (0) 2021.07.13
그래픽 오브젝트  (0) 2021.07.12
1. 컨트롤 기초
2. 버튼 컨트롤

 

1. 컨트롤 기초

※ 생성 방법

- CreateWindow()

- 폼 + 도구 상자 

다이얼로그 폼
도구 상자에서 드래그 앤 드롭
이런 식으로 설정이 가능하다. 입력칸 아래는 '도구 상자' 속에 있는 Static Text 기능을 가져온 것이다.

- Static Text는 단순 문자열 출력 도구이다.

 

 

 

컨트롤 아이디 : 중복되지 않는 양의 정수값

→ Visual Studio에서 자동으로 할당. IDC_XXXX로 시작한다.

 

 

※ 컨트롤의 윈도우 핸들 GetDlgItem()

HWND GetDlgItem(
  HWND hDlg,
  int  nIDDlgItem
);

 

 

※ 컨트롤 메시지 처리 WM_COMMAND 

- LOWORD(wParam) 아이디 → 여러 컨트롤 구분

- HIWORD(wParam) 컨트롤 상태, 통지 코드 

   :: 통지코드 예시

   BN_CLICKED 버튼이 클릭된 상태

   BN_DBCLICK 버튼이 더블클릭된 상태

   BN_SETFOCUS 버튼이 선택된 상태

   BN_DISABLE 버튼 사용 불가 상태

 

 

※ 컨트롤 속성

기본 속성

속성 역할
Caption 컨트롤에 표시되는 텍스트
ID 컨트롤의 아이디
Visible 컨트롤의 출력 여부를 결정

 

ID, Visible 속성
Caption 속성


2. 버튼 컨트롤

: 푸시, 라디오, 체크 버튼

 

→ 생성 방법 ?

폼 + 도구 상자 + 드래그 앤 드롭 (위에서 한 방식과 같다)

 

사용 시 기본 메세지 WM_COMMAND가 발생하고, 각각의 버튼에 해당되는 LOWORD(wParam) 아이디를 이용하면 된다.

 

① 푸시 버튼

푸시 버튼 추가
폼 기반의 대화 상자라 코드가 아주 간단하다. 저 39줄이 전부
버튼 클릭 시

 


② 라디오 버튼

역할 : 여러 항목 중에서 하나 선택

 

라디오 버튼 2개 생성 후 아이디 확인
WM_COMMAND에서 추가

 

 

※ 상태 조사 함수 IsDlgButtonChecked

UINT IsDlgButtonChecked(
  HWND hDlg,
  int  nIDButton
);

리턴 코드 : BST_CHECKED, BST_UNCHECKED, BST_INDETERMINATE

 

 

기존 코드를 조금 수정한다.

체크된 라디오 버튼 각각을 구분하기 위해서 배열을 선언한 후, 몇 번째 라디오 버튼이 눌렸는지 상태를 확인하여 출력하는 코드이다.

 

각각 선택되었을 때 결과가 다르다

 

※ 상태(선택/비선택) 변경

상태 변경 함수 

 

1) BOOL CheckDlgButton()

: 임의로 개별 선택 및 비선택 설정

BOOL CheckDlgButton(
  HWND hDlg,
  int  nIDButton,
  UINT uCheck //BST_CHECKE, BST_UNCHECKED
);

 

2) BOOL CheckRadioButton()

그룹 안에 있는 라디오 버튼 하나만 임의로 선택

그룹? Group 속성을 TRUE로 설정하면 됨

BOOL CheckRadioButton(
  HWND hDlg,
  int  nIDFirstButton,
  int  nIDLastButton,
  int  nIDCheckButton
);

라디오 버튼 수정. 라디오1의 Group 설정을 TRUE로 하면 1,2,3이 하나의 그룹이 된다. 

만약 라디오 버튼 4,5,6을 추가하고 이 셋을 하나의 그룹으로 만들고자 한다면, 라디오 4의 Group 설정만 TRUE로 하면 된다. 

 

버튼2 수정
1을 선택한 후 '라디오' 버튼을 누르면 저절로 2번이 선택된다. 3번도 마찬가지


③ 체크 버튼

: 다수의 컨트롤 선택, 다수의 선택 사항 파악

 

nCheckID 배열 추가
WM_COMMAND의 switch문에 이를 추가한다.

화면에 On, Off를 표시하기 위해서 InvalidateRect 를 사용한다.

 

WM_PAINT 문을 추가한다.

 

체크된 것만 On으로 바뀜

 

'Project > WIN32 API' 카테고리의 다른 글

컨트롤(3)  (0) 2021.07.22
컨트롤(2)  (0) 2021.07.22
다이얼로그  (0) 2021.07.21
키보드, 마우스  (0) 2021.07.13
그래픽 오브젝트  (0) 2021.07.12
1. 모달형 다이얼로그
2. 모델리스형 다이얼로그
3. 폼 기반 윈도우 프로그램
4. 파일열기 모달형 다이얼로그

 

다이얼로그

① 모달(Modal)형 다이얼로그 : 대화상자를 닫기 전 다른 윈도우로 전환할 수 없는 대화상자

② 모델리스(Modeless)형 다이얼로그 : 대화상자를 열어 놓은 채로 다른 윈도우로 전환할 수 있는 대화상자

 

1. 모달형 다이얼로그

- 다이얼로그가 최우선 순위

- 대표적인 다이얼로그 형태 MessageBox()

 

- MessageBox()를 이용한 모달형 다이얼로그 확인 예제

리소스 - Menu - 모달형 메뉴 추가 후 ID 편집으로 ID 확인

 

WM_command 에서 MessageBox() 띄우는 간단한 예시 코드 작성

 

모달형 메시지 박스

 

※ 모달 대화 상자 생성 함수

DialogBox() 

INT_PTR DialogBox(
   HINSTANCE hInstance,
   LPCTSTR lpTemplate,
   HWND hWndParent,
   DLGPROC lpDialogFunc
);

 

 

※ 폼 생성과 lpTemplate 설정

폼 아이디 → 고유 값

폼 아이디를 변환하는 매크로 함수 MAKEINTRESOURCE() → lpTemplate에 지정

 

폼 생성

 

※ 다이얼로그 프로시저

:: 일반 윈도우 메시지와 동일하다. 콜백 함수의 형식을 가진다(메세지에 따라 반응).

DLGPROC Dlgproc;

INT_PTR CALLBACK Dlgproc(
  HWND hwndDlg,
  UINT uMsg,
  WPARAM wParam,
  LPARAM lParam
);

DialogProc과 WndProc 차이

- WndProc 메세지 처리 → DefWindowProc

- Dialog 메세지 처리 → TRUE, FALSE

 

 

※ EndDialog() 함수 호출

- 다이얼로그 해제되면서 부모 윈도우로 리턴해줄 수 있음. 이때 사용

BOOL EndDialog(
  HWND    hDlg,
  INT_PTR nResult
);

 

nResult :: 부모 윈도우에게 전달되는 값(전역변수). 기본 값 = 0 

 

→ 기본적인 해제 적용 방법? WM_CLOSE에서 EndDialog() 호출

 

DlgProc 선언
위 메시지 박스 코드 바로 아래에 DialogBox 함수 작성
About 함수를 복사해온 후 일부 코드 추가

Close 버튼과 OK 버튼, 취소 버튼 등을 누르면 EndDialog() 함수가 실행이 되면서 다이얼로그 창이 해제된다.

 

DialogBox 메세지


2. 모델리스형 다이얼로그

※ 생성 함수 CreateDialog 

- 모달 다이얼로그를 생성하는 DialogBox 함수와 이름만 다를 뿐 매개변수는 똑같다.

HWND CreateDialog(
   HINSTANCE hInstance,
   LPCTSTR lpTemplate,
   HWND hWndParent,
   DLGPROC lpDialogFunc
);

 

※ 모달형과 폼 생성 방법은 동일하다.

주의할 사항은 폼 속성 중에 Visible을 True로 설정해야 한다는 것! 

 

 

※ 해제 함수 DestroyWindow

BOOL DestroyWindow(HWND hWnd);

→ WM_CLOSE에서 호출한다.

 

리소스 메뉴 추가
visible = TRUE
WM_command case 문 추가
WndProc에 추가
메뉴
결과


3. 폼 기반의 윈도우 프로그램

:: WinMain()과 DialogBox() 호출 필요함 

DialogBox(인스턴스, 다이얼로그 아이디, 0, 콜백함수) → 부모 윈도우 자리에 0

 

※ DialogProc 연결

전체 코드(비교적 짧다)
빈 창

 

※ 타이틀 바꾸기

1. 리소스 속성창에서 변경

타이틀 변경

2. SetWindowText()로 변경

 

다이얼로그 시작시 제목을 "이미지"로 변경하고자 함
변경 확인


4. 파일열기 모달형 다이얼로그

:: 파일경로, 파일명을 알아오는 기능, #include <commdlg.h>

 

※ 생성 함수 GetOpenFileName

BOOL GetOpenFileNameA(
  LPOPENFILENAMEA lpofn
);

GetOpenFileName의 매개변수로는 OPENFILENAME이라는 구조체가 들어가며, 이 구조체 안에는 수많은 정보가 있다.

typedef struct tagOFN_NT4A {
  DWORD         lStructSize; //구조체 크기(몇 바이트 차지하는지)
  HWND          hwndOwner; //부모 윈도우 핸들
  HINSTANCE     hInstance;
  LPCSTR        lpstrFilter; //파일형식 설명 → \*.확장자\0 방식
  LPSTR         lpstrCustomFilter;
  DWORD         nMaxCustFilter;
  DWORD         nFilterIndex; //선택한 파일 인덱스, 1부터 시작
  LPSTR         lpstrFile; //전체 경로를 저장할 배열 설정. static 변수 또는 전역변수로 설정
  DWORD         nMaxFile; //파일경로 최대 길이 설정
  LPSTR         lpstrFileTitle; //파일명을 저장할 배열 설정. static 변수 또는 전역변수로 설정
  DWORD         nMaxFileTitle; //파일명 길이 지정
  LPCSTR        lpstrInitialDir;
  LPCSTR        lpstrTitle; //다이얼로그 타이틀 문자열 출력
  DWORD         Flags;
  WORD          nFileOffset;
  WORD          nFileExtension;
  LPCSTR        lpstrDefExt;
  LPARAM        lCustData;
  LPOFNHOOKPROC lpfnHook;
  LPCSTR        lpTemplateName;
} OPENFILENAME_NT4A, *LPOPENFILENAME_NT4A;

 

리소스 메뉴 추가
WndProc에 변수 선언
WM_COMMAND에 열기(32771)가 선택됐을 때의 동작을 선언

 

화면에 표시

※ 파일 저장 모달형 다이얼로그

:: 파일 열기 모달형과 대부분 같다. #include <commdlg.h>

 

※ 생성 함수 GetSaveFileName

BOOL GetSaveFileName(
  LPOPENFILENAMEA lpofn
);

 

리소스 메뉴 추가
열기와 거의 유사하나 표시한 부분만 조금 달라진다. 확장자의 유무에 따라 확장자 추가를 결정한다.

 

'Project > WIN32 API' 카테고리의 다른 글

컨트롤(2)  (0) 2021.07.22
컨트롤  (0) 2021.07.21
키보드, 마우스  (0) 2021.07.13
그래픽 오브젝트  (0) 2021.07.12
그래픽  (0) 2021.07.08

스톱워치를 응용해서 만든 간단한 타이머이다.

입력은 EditText로 받고, Button을 누를 시 타이머가 시작된다.

 

<MainActivity.kt>

package com.example.timerapp

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
import android.widget.EditText
import android.widget.TextView
import kotlin.concurrent.timer
import java.util.*

class MainActivity : AppCompatActivity() {
    private var timerTask : Timer? = null

    lateinit var secTextView : TextView
    lateinit var milliTextView: TextView
    lateinit var setSec : EditText
    lateinit var startButton : Button

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        secTextView = findViewById<TextView>(R.id.secTextView)
        milliTextView = findViewById<TextView>(R.id.millitextView)
        setSec = findViewById<EditText>(R.id.setSec)
        startButton = findViewById<Button>(R.id.startButton)

        startButton.setOnClickListener {
            start()
        }
    }

    private fun start(){
        var time = setSec.text.toString().toInt() //사용자가 입력한 값
        var tmp = time - 1 // 임시 변수

        timerTask = timer(period = 10) { //10ms, 0.01초 단위
            time++

            var sec = tmp - (time / 100) //초
            var milli = 99 - (time % 100) //밀리초

            runOnUiThread { // UI 조작 (초와 밀리초 텍스트 뷰에 설정)
                secTextView.text = "$sec"
                milliTextView.text = String.format("%02d",milli) //끝난 후에도 두 자리가 처음처럼 "00"으로 끝나도록
            }

            if (sec == 0 && milli == 0){
                timerTask?.cancel()
            }
        }
    }
}

위 코드에선 start 함수에서 time값을 증가시키면서 타이머 기능을 구현할 수도 있고

아래와 같이 time값을 감소시키면서 할 수도 있다. (아래가 더 간단하다)

    private fun start(){
        var time = setSec.text.toString().toInt()*100 //사용자가 입력한 값 (밀리세컨 단위로 맞추기)

        timerTask = timer(period = 10) { //10ms, 0.01초 단위
            time--

            var sec = time / 100 //초
            var milli = time % 100 //밀리초

            runOnUiThread { // UI 조작 (초와 밀리초 텍스트 뷰에 설정)
                secTextView.text = "$sec"
                milliTextView.text = String.format("%02d",milli) //끝난 후에도 두 자리가 처음처럼 "00"으로 끝나도록
            }

            if (sec == 0 && milli == 0){
                timerTask?.cancel()
            }
        }
    }

 

<activity_main.xml>

초, 밀리초 = TextView

입력 창 = EditText

타이머 시작버튼 = Button

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/secTextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="0"
        android:textAppearance="@style/TextAppearance.AppCompat.Large"
        android:textSize="100sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.3" />

    <TextView
        android:id="@+id/millitextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:text="00"
        android:textAppearance="@style/TextAppearance.AppCompat.Large"
        android:textSize="32sp"
        app:layout_constraintBaseline_toBaselineOf="@+id/secTextView"
        app:layout_constraintStart_toEndOf="@+id/secTextView" />

    <Button
        android:id="@+id/startButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="30dp"
        android:layout_marginBottom="31dp"
        android:backgroundTint="#3F51B5"
        android:text="타이머 설정"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toEndOf="@+id/setSec" />

    <EditText
        android:id="@+id/setSec"
        android:layout_width="203dp"
        android:layout_height="42dp"
        android:layout_marginStart="32dp"
        android:layout_marginBottom="31dp"
        android:ems="10"
        android:gravity="right"
        android:hint="초"
        android:inputType="number"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

'Project > Android App' 카테고리의 다른 글

<TeenGü>, 2021 Programming Guru2(Android) 대상 수상작  (0) 2021.08.19
StopwatchApp  (0) 2021.07.12
BmiCalculatorApp  (0) 2021.07.10

+ Recent posts