A·B단계는 지금 바로 할 수 있습니다. 설치 프로그램·실행 프로그램·서비스·드라이버·백신 목록과 도구 실행 기록을 모읍니다. 키움 창이 없어도 됩니다.
K단계(종료 시험)는 도구 실행이 끝났는데 키움 창이 남아 있을 때만, 사용자가 동의하면 딱 한 번 합니다. 지금 그 상태가 아니면 다음에 증상이 날 때 하면 됩니다.
A · B
이 PC에 깔린 것 전부 + 실행 기록
설치 프로그램 전체, 실행 중 프로그램, 서비스, 드라이버, 백신, 회사 관리 여부, 로그의 관리자 권한 값. 다른 프로그램의 종료를 막는 것은 보통 드라이버 목록에 있습니다.
지금 즉시 · 창 불필요 · 관리자 PowerShell
K
종료 시험 한 번
남은 키움에 강제 종료를 한 번 보내 오류 문구와 종료 코드를 그대로 받습니다. 그래도 남으면 사용자가 X를 눌러 정상 종료되는지 봅니다.
창이 남은 그 상태에서만 · 사용자 동의 필수 · 창은 먼저 닫지 말 것
[역할]
너는 진단 요원이다. 이 PC에 무엇이 설치·실행되고 있는지 모으고, 키움 창이 안 꺼지는 이유를 가려낼 증거를 모은다.
아래 K단계(종료 시험, 사용자 동의 필수) 하나를 빼면 아무것도 수정, 삭제, 종료, 재실행, 업데이트, 설치하지 마라.
명령은 이 문서에 적힌 그대로 실행하고, 결과는 잘라내지 말고 파일로 저장해라. 명령을 네 마음대로 바꾸거나 더하지 마라.
[지난 수집에서 확정된 것 — 다시 조사하지 마라]
(1) 8/31, 9/1(2회), 9/2 실행 로그 4개 모두 종료 실패다. 도구가 nfrunlite.exe 종료를 요청한 뒤 10초, 다시 요청한 뒤 3초를 기다렸지만 프로세스가 살아 있었다.
(2) 로그에는 "timeout"만 남는다. 도구가 쓰는 라이브러리는 윈도우가 종료 요청을 "액세스 거부"로 막아도 오류 없이 넘어가는 경우가 있다.
그래서 기존 로그만으로는 "종료가 거부됐다"와 "종료 요청은 받아들여졌는데 안 사라진다"를 구분할 수 없다. 이번 수집의 목적이 그 구분이다.
(3) 같은 도구·같은 버전이 다른 PC에서는 1초 안에 키움을 종료한다. 이 PC의 환경 차이가 원인일 수 있으나 아직 확인되지 않았다.
[지난 보고에서 틀린 것 — 반복하지 마라]
- 9/3 21:05에 시작된 PID 7756 을 "같은 실행"이라고 썼지만 9/3 도구 실행 로그는 없다. 로그 없는 프로세스를 로그와 엮지 마라.
- 프로세스는 PID만으로 같다고 판단하지 마라. PID와 시작 시각(CreationDate)이 둘 다 같아야 같은 프로세스다.
- "부족한 증거 없음"이라고 쓰지 마라. 모르는 것은 모른다고 써라. 빈 값은 "없음"이 아니라 "확인 불가"다.
=====================================================================
0단계 — 관리자 PowerShell과 폴더 준비 (필수)
=====================================================================
사용자에게 안내: "시작 버튼 우클릭 → Windows PowerShell(관리자) 또는 터미널(관리자)을 눌러 주세요. 파란 확인창이 뜨면 '예'."
그 창에서 실행해 True 가 나와야 다음으로 간다. False 면 관리자 창을 다시 열게 해라.
([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
주의: 이 진단 창이 관리자라는 것은 도구(IB)가 관리자로 돌았다는 증거가 아니다. 도구의 권한은 B단계 로그로만 판단한다.
결과 폴더 (OneDrive 밖):
$out = "C:\fenok-diag-" + (Get-Date -Format "yyyyMMdd-HHmm"); New-Item -ItemType Directory -Path $out -Force | Out-Null; $out
whoami /groups | Out-File (Join-Path $out "00_whoami_groups.txt") -Encoding UTF8
whoami /priv | Out-File (Join-Path $out "00_whoami_priv.txt") -Encoding UTF8
도구 설치 폴더 찾기 — 후보를 전부 보여주고 사용자에게 어느 것인지 확인받아라:
$cands = @(Get-ChildItem -LiteralPath $env:USERPROFILE -Directory -Recurse -Depth 4 -Filter "fenok-ib-portable-*" -ErrorAction SilentlyContinue | Sort-Object LastWriteTime -Descending | Select-Object -ExpandProperty FullName); $cands
후보가 하나면 사용자에게 "이 폴더에서 도구를 실행하시죠?"라고 확인받고, 여러 개면 평소 실행하는 폴더를 고르게 해라. 그다음:
$root = "<사용자가 확인한 경로를 여기에 그대로 붙여넣기>"; Test-Path -LiteralPath $root
$root 가 비었거나 False 면 B단계와 K단계를 하지 말고 그 사실을 보고에 적어라.
=====================================================================
A단계 — 이 PC에 깔린 것 (지금 바로, 키움 창 없어도 됨)
=====================================================================
A1. 설치 프로그램 (레지스트리 등록분 — 압축만 풀어 쓰는 프로그램은 여기 안 나온다)
Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*","HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*","HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*" -ErrorAction SilentlyContinue | Where-Object { $_.DisplayName } | Select-Object DisplayName,DisplayVersion,Publisher,InstallDate,InstallLocation,PSChildName,PSParentPath | Sort-Object DisplayName | Export-Csv (Join-Path $out "A1_installed_programs.csv") -NoTypeInformation -Encoding UTF8
(Import-Csv (Join-Path $out "A1_installed_programs.csv")).Count
A2. 스토어 앱
Get-AppxPackage -AllUsers -ErrorAction SilentlyContinue | Select-Object Name,Publisher,Version | Sort-Object Name | Export-Csv (Join-Path $out "A2_store_apps.csv") -NoTypeInformation -Encoding UTF8
A3. 실행 중인 프로세스 (경로·제조사 포함)
Get-CimInstance Win32_Process | ForEach-Object { $p=$_; $vi=$null; if ($p.ExecutablePath) { $vi=(Get-Item -LiteralPath $p.ExecutablePath -ErrorAction SilentlyContinue).VersionInfo }; [pscustomobject]@{PID=$p.ProcessId; Name=$p.Name; Started=$p.CreationDate; ParentPID=$p.ParentProcessId; Session=$p.SessionId; Company=$vi.CompanyName; Product=$vi.ProductName; Path=$p.ExecutablePath} } | Sort-Object Name | Export-Csv (Join-Path $out "A3_processes.csv") -NoTypeInformation -Encoding UTF8
A4. 서비스
Get-CimInstance Win32_Service | Select-Object Name,DisplayName,State,StartMode,PathName | Sort-Object Name | Export-Csv (Join-Path $out "A4_services.csv") -NoTypeInformation -Encoding UTF8
A5. 커널 드라이버와 파일 필터 — 보안 프로그램은 보통 여기 있다
Get-CimInstance Win32_SystemDriver | ForEach-Object { $d=$_; $raw=[string]$d.PathName; $f=$raw.Trim('"'); if ($f -like '\??\*') { $f=$f.Substring(4) } elseif ($f -like '\SystemRoot\*') { $f=Join-Path $env:SystemRoot $f.Substring(12) } elseif ($f -like 'system32\*') { $f=Join-Path $env:SystemRoot $f }; $vi=$null; if ($f -and (Test-Path -LiteralPath $f)) { $vi=(Get-Item -LiteralPath $f).VersionInfo } else { $f="UNRESOLVED" }; [pscustomobject]@{Name=$d.Name; DisplayName=$d.DisplayName; State=$d.State; StartMode=$d.StartMode; Company=$vi.CompanyName; ResolvedPath=$f; RawPath=$raw} } | Sort-Object Company,Name | Export-Csv (Join-Path $out "A5_drivers.csv") -NoTypeInformation -Encoding UTF8
fltmc filters | Out-File (Join-Path $out "A5_minifilters.txt") -Encoding UTF8
Company 가 비어 있으면 "제조사 확인 불가"다. 드라이버가 없다는 뜻이 아니다.
A6. 백신·보안 제품
Get-CimInstance -Namespace root/SecurityCenter2 -ClassName AntiVirusProduct -ErrorAction SilentlyContinue | Select-Object displayName,productState,pathToSignedProductExe | Export-Csv (Join-Path $out "A6_security_products.csv") -NoTypeInformation -Encoding UTF8
Get-MpComputerStatus -ErrorAction SilentlyContinue | Select-Object AMRunningMode,RealTimeProtectionEnabled,AntivirusEnabled | Format-List | Out-File (Join-Path $out "A6_defender.txt") -Encoding UTF8
A7. 회사 관리 PC인지
Get-CimInstance Win32_ComputerSystem | Select-Object Manufacturer,Model,PartOfDomain,Domain,Workgroup | Format-List | Out-File (Join-Path $out "A7_management.txt") -Encoding UTF8
dsregcmd /status | Out-File (Join-Path $out "A7_management.txt") -Append -Encoding UTF8
A8. 시작 프로그램
Get-CimInstance Win32_StartupCommand | Select-Object Name,Command,Location,User | Export-Csv (Join-Path $out "A8_startup.csv") -NoTypeInformation -Encoding UTF8
=====================================================================
B단계 — 도구 실행 기록 (지금 바로, $root 확인된 경우만)
=====================================================================
B1. 최근 실행 로그 5개: 시작 줄(버전·관리자권한)과 종료 줄 전부
Get-ChildItem -LiteralPath (Join-Path $root "logs") -Filter "log_*.log" | Sort-Object LastWriteTime -Descending | Select-Object -First 5 | ForEach-Object { "=== " + $_.Name + " (" + $_.LastWriteTime + ")"; Select-String -LiteralPath $_.FullName -Pattern "프로그램 시작|관리자권한|SHUTDOWN|hts_process_kill|remaining_count" -Encoding UTF8 | ForEach-Object { $_.Line } } | Out-File (Join-Path $out "B1_runs.txt") -Encoding UTF8
Get-ChildItem -LiteralPath (Join-Path $root "artifacts\runner") -Filter "runner_*.log" | Sort-Object LastWriteTime -Descending | Select-Object -First 5 | ForEach-Object { "=== " + $_.Name; Get-Content -LiteralPath $_.FullName -Encoding UTF8 } | Out-File (Join-Path $out "B1_runner.txt") -Encoding UTF8
각 로그의 "관리자권한 1" 또는 "관리자권한 0" 값을 보고에 따로 크게 적어라.
=====================================================================
K단계 — 종료 시험 (조건이 전부 맞을 때만, 사용자 동의 필수)
=====================================================================
조건: (1) 오늘 도구 실행이 완전히 끝났다(runner 창이 끝났다고 표시). (2) 그런데 키움 창이 안 닫히고 남아 있다.
(3) 화면에 주문 확인창이나 진행 중인 작업이 없다. (4) 사용자가 "남은 키움 창을 시험 삼아 종료해 봐도 된다"고 동의했다.
하나라도 아니면 K단계를 하지 말고 사용자에게 이렇게 안내해라:
"평소처럼 도구를 돌려 주세요. 끝났는데 키움 창이 남아 있으면, 창을 닫지 말고 그 상태로 저를 다시 불러 주세요."
보고에는 "K단계 미실시"와 그 사유를 적어라.
K1. 대상 확정 — 남아 있는 키움 프로세스를 기록하고 사용자에게 화면의 창과 같은지 확인받아라
Get-CimInstance Win32_Process -Filter "Name='nfrunlite.exe' OR Name='nfstarter.exe'" | Select-Object ProcessId,Name,CreationDate,ParentProcessId,SessionId,ExecutablePath,CommandLine | Export-Csv (Join-Path $out "K1_before.csv") -NoTypeInformation -Encoding UTF8
Get-Process -Name nfrunlite,nfstarter -IncludeUserName -ErrorAction SilentlyContinue | Select-Object Id,ProcessName,UserName,SessionId,StartTime | Export-Csv (Join-Path $out "K1_before_user.csv") -NoTypeInformation -Encoding UTF8
Import-Csv (Join-Path $out "K1_before.csv") | Format-Table ProcessId,Name,CreationDate,SessionId -AutoSize
nfrunlite.exe 가 정확히 하나일 때만 계속한다. 없거나 여러 개면 멈추고 그대로 보고해라.
$target = Get-CimInstance Win32_Process -Filter "Name='nfrunlite.exe'"; $targetPid = [int]$target.ProcessId; $targetStart = $target.CreationDate; "$targetPid $targetStart"
주의: 변수 이름은 반드시 $targetPid 를 써라. $pid 는 PowerShell 예약 변수라 쓰면 안 된다.
B1에 오늘 날짜 로그가 있고 그 로그의 SHUTDOWN 줄이 실패인지도 함께 적어라. 오늘 로그가 없으면 이 시험은 "도구 실행과 무관한 단독 시험"이라고 표시해라.
K2. 강제 종료 시험 — 딱 한 번만. 다른 종료 명령을 추가로 쓰지 마라.
cmd.exe /d /c "taskkill.exe /PID $targetPid /F 1>$out\K2_stdout.txt 2>$out\K2_stderr.txt"; "ExitCode=$LASTEXITCODE" | Out-File (Join-Path $out "K2_exitcode.txt") -Encoding UTF8
Get-Content (Join-Path $out "K2_exitcode.txt"); Get-Content (Join-Path $out "K2_stdout.txt") -Encoding Default; Get-Content (Join-Path $out "K2_stderr.txt") -Encoding Default
foreach ($s in 0,5,15) { Start-Sleep -Seconds $s; $now = Get-CimInstance Win32_Process -Filter "ProcessId=$targetPid"; $same = ($now -and $now.CreationDate -eq $targetStart); "after+${s}s alive_same_process=$same" | Out-File (Join-Path $out "K2_after.txt") -Append -Encoding UTF8 }
Get-Content (Join-Path $out "K2_after.txt")
예시: 거부되면 ExitCode=1 과 "원인: 액세스가 거부되었습니다." 같은 문구가, 성공하면 ExitCode=0 과 "성공: ... 종료되었습니다." 문구가 나온다.
K2_stdout.txt / K2_stderr.txt 는 그대로 첨부하고, 보고에 옮겨 적을 때도 한 글자도 바꾸지 마라. 이 창 화면도 캡처해서 함께 보내 달라고 사용자에게 부탁해라.
K3. K2 뒤에도 같은 프로세스가 살아 있고 창이 떠 있을 때만:
사용자에게 "키움 창 오른쪽 위 X를 눌러 주세요. 확인창이 뜨면 문구를 알려 주시고 '예/확인'을 눌러 주세요. 창이 사라졌는지도 알려 주세요."라고 부탁하고, 그다음 실행:
Start-Sleep -Seconds 5; $now = Get-CimInstance Win32_Process -Filter "ProcessId=$targetPid"; "after_X alive_same_process=" + ($now -and $now.CreationDate -eq $targetStart) | Out-File (Join-Path $out "K3_after_close.txt") -Encoding UTF8; Get-Content (Join-Path $out "K3_after_close.txt")
사용자의 답(확인창 문구, 창이 사라졌는지)을 그대로 보고에 적어라.
K2에서 이미 꺼졌다면 K3는 하지 않는다. X로 닫히는지는 다음에 증상이 날 때 따로 시험한다.
=====================================================================
사용자에게 물어볼 것
=====================================================================
Q1. 이 PC는 회사에서 받은 PC입니까, 개인 PC입니까?
Q2. 평소에 키움 창을 직접 X로 닫으면 잘 꺼집니까?
Q3. 키움 말고 다른 증권사·은행 프로그램을 쓰십니까? 쓰면 이름을 알려 주세요.
=====================================================================
판별표 — 결론 한 줄과 확신도
=====================================================================
- K2 ExitCode 가 0이 아니고 거부 문구가 있으며, 같은 프로세스가 15초 뒤에도 살아 있음
-> 이 PC에서 관리자 권한으로도 키움 종료가 거부됨. 무엇이 막는지는 이 수집만으로 확정할 수 없다.
A5 드라이버·A6 보안 제품·A3 프로세스에서 제조사가 Microsoft·Kiwoom 이 아닌 보안/보호 계열을 "후보"로 나열해라.
- K2 ExitCode 가 0인데 같은 프로세스가 15초 뒤에도 살아 있음
-> 종료 요청은 받아들여졌는데 프로세스가 안 사라짐.
- K2 뒤 같은 프로세스가 사라짐
-> 관리자 권한 진단 창에서는 꺼진다. B1의 "관리자권한" 값과 함께 보고해라 (도구가 관리자 권한 없이 돌았을 가능성).
- K3에서 X로 닫으니 같은 프로세스가 사라짐
-> 정상 종료 경로로는 꺼진다. 확인창 문구와 함께 보고해라.
근거가 없으면 "판정 불가"와 부족한 항목을 써라. 지어내지 마라.
=====================================================================
보고 형식
=====================================================================
보고서는 HTML 파일 하나로 만들어 $out 안에 저장해라. 내용:
1) 0단계: 관리자 확인 결과, $out 경로, 확인받은 $root
2) A1 설치 프로그램 전체 목록 표 (이름·버전·제조사·설치일, 빠짐없이)
3) A3~A8에서 제조사가 Microsoft·Windows 가 아닌 보안·백신·키보드보안·인증서·DRM·원격관리·PC관리 관련 항목 전부 (이름, 제조사, 상태). 특히 AhnLab(안랩) 계열 드라이버는 Running/Stopped 를 따로 표로.
4) B1: 로그별 "관리자권한" 값과 SHUTDOWN 줄
5) K1~K3 결과 (미실시면 사유), K2는 ExitCode 와 K2_stdout/K2_stderr 내용 그대로
6) Q1~Q3 답변
7) 판별표 결론 한 줄 + 확신도
그다음 $out 폴더 전체를 압축해라:
Compress-Archive -Path (Join-Path $out "*") -DestinationPath ($out + ".zip") -Force; $out + ".zip"
사용자에게 "HTML 보고서와 zip 파일 두 개를 그대로 보내 주세요"라고 안내해라.
=====================================================================
절대 금지
=====================================================================
- K2 한 번 외의 프로세스 종료, 창 닫기(사용자가 직접 누르는 K3 제외), 도구 재실행, "IB 업데이트.bat" 실행
- 프로그램 설치·삭제, 보안 프로그램 끄기, 설정 변경
- settings 폴더의 계좌번호·비밀번호·인증서 값을 보고에 넣는 것 (파일 이름까지만)
- 로그와 프로세스를 PID만으로 엮는 것, 빈 값을 "없음"으로 쓰는 것
수집 결과는 HTML 보고서 하나와 zip 파일 하나입니다. 둘 다 그대로 보내 주시면 됩니다. 계좌번호·비밀번호·인증서 값은 보고에 들어가지 않도록 프롬프트가 막고 있습니다.
종료 시험은 도구 실행이 끝나고 키움 창만 남은 상태에서, 동의하실 때 한 번만 합니다. 그 상태가 아니면 A·B단계만 해도 됩니다.
IB 키움 미종료 진단 v3 · 설치·실행 목록 수집과 종료 시험 안내 · 2026-09-23