Word Apps
App 중 하나인 켜자마자 일본어 (강제로 일본어공부-JLPT, JPT)
App의 암호화된 데이터를 복호화하고, 정규 표현식을 사용하여 필요한 정보를 추출합니다.
(📌 켜자마자 일본어 DB 복호화 및 추출.py
전체 작업 과정 일부 편집본)
■ *.db 파일 내용 중 items
테이블의 content_data
열 값을 복호화 하고 각 데이터의 유형별로 정리합니다.
■ 켜자마자 일본어 DB 복호화 및 추출 (Raw).py
Script를 사용하면 복호화 된 원본 코드 그대로 반환 됩니다.
■ 반환 된 결과를 *.txt 파일로 저장할 수 있습니다.
Program | URL | 필수여부 | 비고 |
---|---|---|---|
Python |
Download | 필수 | ◼ Python Script 동작, 파이썬 3.9.0 버전 또는 그 이상 사용 가능 |
켜자마자 일본어 (강제로 일본어공부-JLPT, JPT) |
Download | 필수 | ◼ *.db 파일 추출 대상 Android App |
반디집 (또는 다른 압축 프로그램 사용) |
Download | 필수 | ◼ *.apk 파일 내용 추출 프로그램 (* 다른 압축 프로그램 사용 가능) |
CX 파일 탐색기 (또는 다른 *.apk 파일 추출 방법 사용) |
Download | 필수 | ◼ *.apk 파일 추출 App (* 다른 App을 사용해서 *.apk 파일 추출, Android Emulator에서 *.apk 파일 추출, 다른 곳에서 *.apk 파일 다운 등 등 여러가지 방법 중 가장 마음에 드는 방법 택 1) |
Script | URL | 비고 |
---|---|---|
켜자마자 일본어 DB 복호화 및 추출 |
Download | ◼ 복호화 된 데이터에서 각 유형별로 구분하여 Markdown 표로 반환 |
켜자마자 일본어 DB 복호화 및 추출 (Raw) |
Download | ◼ 복호화 된 데이터 원본을 그대로 반환 |
(* 만약, AES Key 값과 Iv 값 변경으로 정상적으로 복호화가 안 되면 *.apk 파일을 직접 Decompile (또는 Reverse Engineering)해서 알아내야 함.)
-
Python과 반디집(또는 다른 압축 프로그램)을 설치합니다.
[ ※ 주의 ] Python 설치 시 Add python.exe to PATH 에 반드시 체크 후 Install Now 클릭
(📌 미처 누르지 못했다면 설치 파일 다시 실행 또는 제거 후 재 설치)
[ ※ 주의 ] 설치 후 Disable path length limit 기능을 사용할 수 있도록 반드시 클릭
(📌 필수는 아니나, 해당 기능을 사용하는 것을 권장)
-
필수 Package 설치
Python 설치 후 키보드Win + R
또는시작 -> 검색
란에cmd
를 입력하여 cmd를 실행하고 아래 코드를 입력하여 필수 Package를 설치합니다.
(필수) Package 설치
pip install pycryptodome
or
python -m pip install pycryptodome
[ ※ 주의 ] 만약 위 명령어 사용 중 ERROR: Could not install packages due to an EnvironmentError: [WinError 5] 액세스가 거부되었습니다: (생략) Consider using the--user
option or check the permissions. 과 같은 오류가 나왔다면 끝에--user
를 붙여서 입력
(* 권한 오류 발생시 두 코드 중 하나 선택)
pip install pycryptodome --user
or
python -m pip install pycryptodome --user
-
켜자마자 일본어 (강제로 일본어공부-JLPT, JPT)
App의 *.apk 파일을 추출하여 작업 할 PC로 복사 또는 이동 후 *.apk 파일을 압축 프로그램으로 실행합니다.
-
*.db 파일을 찾아 원하는 경로에 끌어다 놓습니다.
(📌 2024-07-21 기준\assets\
에jpkr_20200219.db
파일 위치)
-
Python Script에 *.db 파일 이름을 입력하고 저장합니다.
만약, db 파일 이름이jpkr_20200219.db
이면db_filename = 'jpkr_20200219.db'
과 같이 입력
(📌 [ ※ 주의 ] 반드시 파일 이름을 따옴표(') 안에 입력해야 함.)
-
Python Script 파일과 *.apk 파일에서 추출 한 *.db 파일을 동일한 위치에 두고 Python Script 파일을 실행합니다.
(📌켜자마자 일본어 DB 복호화 및 추출.py
전체 작업 과정 무편집본)
Program | URL | 필수여부 | 비고 |
---|---|---|---|
DB Browser for SQLite |
Download | 필수 | ◼ *.db 내용 보는거 다른 프로그램 써도 됨 |
Bytecode Viewer |
Download | 필수 | ◼ *.apk 내용 분석(* 필자는 2.9.22 버전 사용) 다른 버전 써도 되고 다른 프로그램 써도 됨 |
Java (또는 다른 압축 프로그램 사용) |
Download | 필수 | ◼ Windows 사용자는 가급적 x64 MSI Installer 설치 파일 받아서 설치 |
반디집 (또는 다른 압축 프로그램 사용) |
Download | 필수 | ◼ *.apk 파일에서 *.db 파일 추출 용 다른 압축 프로그램 써도 됨 |
-
*.apk 파일에서 db 파일 빼내기
01-01. 작업 할 PC에 *.apk 파일 옮기고 반디집으로 열기
01-02. 내부 파일들 중 Data가 들어 있을 법 한 *.db 파일 찾아서 압축 해제
(📌 2024-07-21 기준\assets\
에jpkr_20200219.db
파일 위치)
-
*.db 파일 분석
02-01.DB Browser for SQLite
실행 후 조금 전 빼낸 *.db 파일 불러오고 데이터 보기 클릭
02-02. 테이블(T)에서 모든 테이블 데이터 확인 후 단어가 저장되어 있을 법 한 테이블 이름과 해당 테이블의 열 이름 종류 기억
(📌 2024-07-21 기준items
테이블)
-
App 구조 분석
03-01. 화면 좌측 상단에 보이는Drag class/jar/zip/APK/DEX here
에 *.apk 파일 드래그 또는File - Add...
로 불러오기
(📌 *.apk 파일이 정상적으로 불러와진 모습)
03-02.items
테이블과 어떤식으로 연결되어 있는지 확인하기 위해 여러개의 열 값 중content_data
으로 우선 검색
(📌Search
창에 있는Search_String
칸에content_data
입력 후Search
버튼 클릭 후 반환 된 코드 모두 확인)
03-03.alib/word/model/Item.class
라는 파일에서content_data
검색 후 해당 글자가 있는 근처를 우선으로 분석
(📌 57번째 줄et1 var10 = new et1()
코드 내용을 바탕으로et1
Class 확인)
03-04. Files 창에et1
입력 후Enter
키
03-05. 코드 분석
(📌 위 스크린 샷 기준 133번째 줄decrypt
, 149번째 줄encrypt
라는 내용을 봐서 복호화 하거나 암호화하는 코드일 가능성이 높은 것을 확인)
코드 처음 부분부터 각 변수별 값 분석
public String
a 값 : Iv 값(* 블록 암호화에서 사용되는 추가적인 입력 값으로, 암호화 과정에서 사용되는 초기 상태를 설정하는 데 사용)
public String
e 값 : AES 암호화에서 사용할 키
this.d
= Cipher.getInstance("AES/CBC/NoPadding"); 코드 : AES 알고리즘을 CBC(Cipher Block Chaining) 모드로 사용하며, 패딩 없이 데이터를 암호화 및 복호화하기 위해 Cipher 객체를 생성하는 부분
-
테스트
(📌 위 사이트는 여기를 클릭하여 이동)
❔ 파일이 정상적으로 복호화 되지 않는 경우
원인 1 : *.db 내용 중 items
테이블 변경(* Ex. 열 이름, 기록 된 데이터가 다른 테이블로 이동 등)
원인 2 : *.db 내용 중 지정 된 item_type
값을 참고하여 올바르지 않은 규칙으로 처리(* Ex. 데이터 구조 변경 등)
해결 방법
원인 1
*.db 파일 분석 후 78번째 줄 cursor.execute("SELECT id, content_data, item_type FROM items")
코드에서 테이블과 열 이름 수정
(📌 추가로 수정해야 하는 부분이 있다면 Python Script에 기록 된 주석 내용 참고하여 수정)
원인 2
켜자마자 일본어 DB 복호화 및 추출 (Raw).py
파일 실행
(📌 추가로 수정해야 하는 부분이 있다면 해결 방법 - 원인 1
내용과 Python Script에 기록 된 주석 내용 참고하여 수정)
복호화 된 원본 데이터 구조 분석
각 `item_type`별 구조 예시
# item_type 1
{"word":"あいさつ","grammer":"명사,동사","display":"あいさつ","concise":"① 인사","content":null,"voice_usa":"挨拶","example":"あいさつの言葉<ことば>\n인사말\nあいさつを交<か>わす\n인사를 나누다\n","url":null}
# item_type 5
{"word":"油<あぶら>を売<う>る","explanation":"あぶらをうる","pronunciation":"기름을 팔다","korean_display":"수다를 떨거나 딴짓을 하며 해야할 일을 미루는 것. 에도시대에 머릿기름 장사를 하던 사람이 자연스럽게 대화를 하면서 구매를 유도했던 모습에 비유","vowel":"","example":"油<あぶら>を売<う>る"}
# item_type 6
{"korean_display":"아","word":"あ","grammer":"히히라가나에서 가장 먼저 배우게 되는 5글자 あいうえお(아이우에오)는 일본어에서 유일한 모음이다.\n \n ※히라가나란? 일본어의 기본 문자로 기초 공부를 하기 위해 필수로 익혀야 하는 글자들이다.\n \n 청음 : 부가적인 부호없이 글자 하나 만으로 사용할 수 있으며, 가장 기본 문자이다. 탁음,반탁음,요음을 제외한 글자를 전부 청음이라고 보면 된다.","display":"あ","concise":"a","voice":""}
(📌 기본 형식이 언제 변경될지 모르기 떄문에 반드시 수정 작업 할 때는 켜자마자 일본어 DB 복호화 및 추출 (Raw).py
파일로 원본 데이터 분석 후 작업해야 됨.)
(* 아래 코드는 임의로 줄 바꿈하여 알아보기 쉽게 정리한 것)
# item_type 1
"word": "あいさつ",
"grammer": "명사,동사",
"display": "あいさつ",
"concise": "① 인사",
"content": null,
"voice_usa": "挨拶",
"example": "あいさつの言葉<ことば>\n인사말\nあいさつを交<か>わす\n인사를 나누다\n",
"url": null
# item_type 5
"word": "油<あぶら>を売<う>る",
"explanation": "あぶらをうる",
"pronunciation": "기름을 팔다",
"korean_display": "수다를 떨거나 딴짓을 하며 해야할 일을 미루는 것. 에도시대에 머릿기름 장사를 하던 사람이 자연스럽게 대화를 하면서 구매를 유도했던 모습에 비유",
"vowel": "",
"example": "油<あぶら>を売<う>る"
# item_type 6
"korean_display": "아",
"word": "あ",
"grammer": "히라가나에서 가장 먼저 배우게 되는 5글자 あいうえお(아이우에오)는 일본어에서 유일한 모음이다.\n \n ※히라가나란? 일본어의 기본 문자로 기초 공부를 하기 위해 필수로 익혀야 하는 글자들이다.\n \n 청음 : 부가적인 부호없이 글자 하나 만으로 사용할 수 있으며, 가장 기본 문자이다. 탁음,반탁음,요음을 제외한 글자를 전부 청음이라고 보면 된다.",
"display": "あ",
"concise": "a",
"voice": ""
켜자마자 일본어 DB 복호화 및 추출.py
파일 코드 중 정규 표현식 관련 코드 수정
수정해야 하는 부분
01. 49번째 줄 ~ 75번째 줄
patterns = {
# item_type이 1일 때 사용하는 정규 표현식 패턴
'word_1': re.compile(r'"word":"(.*?)","grammer"'), # 'word' 항목 추출
'grammar_1': re.compile(r'"grammer":"(.*?)","display"'), # 'grammer' 항목 추출
(...생략...)
'url_1': re.compile(r'"solve":"(.*?)"'), # 'solve' 항목 추출
# item_type이 5일 때 사용하는 정규 표현식 패턴
'word_5': re.compile(r'"word":"(.*?)","explanation"'), # 'word' 항목 추출
'explanation_5': re.compile(r'"explanation":"(.*?)","pronunciation"'), # 'explanation' 항목 추출
(...생략...)
'example_5': re.compile(r'"example":"(.*?)"'), # 'example' 항목 추출
# item_type이 6일 때 사용하는 정규 표현식 패턴
'korean_display_6': re.compile(r'"korean_display":"(.*?)","word"'), # 'korean_display' 항목 추출
'word_6': re.compile(r'"word":"(.*?)","grammar"'), # 'word' 항목 추출
(...생략...)
'voice_6': re.compile(r'"voice":"(.*?)"') # 'voice' 항목 추출
}
위 정규 표현식 설명
예시
복호화 된 원본
{"word":"あいさつ","grammer":"명사,동사","display":"あいさつ","concise":"① 인사","content":null,"voice_usa":"挨拶","example":"あいさつの言葉<ことば>\n인사말\nあいさつを交<か>わす\n인사를 나누다\n","url":null}
Python Script
re.compile(r'"word":"(.*?)","grammer"')
re.compile(...)
: 정규 표현식을 컴파일하여 패턴 객체를 생성하는 Python의 re 모듈 함수
(* 정규 표현식을 컴파일하여 패턴 객체를 만들면, 나중에 이 패턴을 사용해 문자열에서 원하는 부분을 추출하거나 검색할 수 있음.)
r'"word":"(.*?)","grammer"'
: 컴파일할 정규 표현식 패턴
"
: 큰따옴표 문자 그 자체를 의미. 문자열에서 "word"와 "grammer"이라는 문자열을 직접 매칭하기 위해 사용
"word":"
: "word":" 문자열을 정확히 매칭.
(* 이 부분은 탐색하려는 패턴의 시작 부분)
(.*?)
: 이 부분은 실제로 추출하려는 값을 포함하는 그룹
.
: 어떤 문자 하나를 의미
*?
: 0번 이상의 반복을 의미하며, 가능한 한 적게 일치하도록 함.
(* 비탐욕적(non-greedy) 방식(가능한 가장 짧은 문자열을 일치)으로 문자열 일치 시도 )
","grammer"
: ","grammer" 문자열을 정확히 매칭하기 위해 사용
(* 이 부분은 추출하려는 값 뒤에 오는 부분을 매칭.)
이 정규 표현식은 "word":"와 ","grammer" 사이의 문자열을 추출하는 것을 목적으로 함.
(* Ex. "word":"あいさつ","grammer"에서 "あいさつ" 값 추출)
(📌 반드시 *.db 파일에서 item_id
분석 후 분리하여 변수 명을 지정해야 됨.)
(📌 새로 추가 된 형식이 있다면 반드시 위 코드 설명을 참고하여 추가로 작성해야 됨.)
02. 107번째 줄 ~ 119번째 줄
word_match = None
grammar_match = None
display_match = None
concise_match = None
content_match = None
voice_usa_match = None
example_match = None
url_match = None
explanation_match = None
pronunciation_match = None
korean_display_match = None
vowel_match = None
voice_match = None
(📌 새로 추가 된 형식이 있다면 반드시 추가로 작성해야 됨.)
03. 122번째 줄 ~ 144번째 줄
if item_type == 1:
word_match = patterns['word_1'].search(decrypted_content)
grammar_match = patterns['grammar_1'].search(decrypted_content)
(...생략...)
url_match = patterns['url_1'].search(decrypted_content)
elif item_type == 5:
word_match = patterns['word_5'].search(decrypted_content)
explanation_match = patterns['explanation_5'].search(decrypted_content)
(...생략...)
example_match = patterns['example_5'].search(decrypted_content)
elif item_type == 6:
korean_display_match = patterns['korean_display_6'].search(decrypted_content)
word_match = patterns['word_6'].search(decrypted_content)
(...생략...)
voice_match = patterns['voice_6'].search(decrypted_content)
(📌 정규 표현식 패턴에 추가 한 변수 명을 item_type
에 맞게 새로 추가 또는 수정)
04. 147번째 줄 ~ 164번째 줄
result = {
'id': item_id,
'item_type': item_type,
'category_id': category_id if category_id is not None else 'N/A', # category_id가 None이면 'N/A'로 설정
'category_title': category_title if category_title is not None else 'N/A', # category_title이 None이면 'N/A'로 설정
(...생략...)
'voice': voice_match.group(1) if voice_match else 'N/A' # voice_match가 존재하면 첫 번째 그룹의 값을 사용하고, 없으면 'N/A'로 설정
}
(📌 정규 표현식 패턴에 추가 한 유형 추가 또는 수정)
(📌 새로운 유형을 추가 할 경우 코드 복사 후 맨 앞 ''
사이 글자는 새로 추가된 유형으로, if
와 else
사이 값은 정규 표현식 패턴에 추가 한 변수 명으로 지정하면 됨)
05. 171번째 줄 ~ 179번째 줄
markdown_output = "| ID | Item Type | Category ID | Category Title | Word | Grammar | Display | Concise | Voice USA | Example | URL | Explanation | Pronunciation | Korean Display | Vowel | Voice |\n"
markdown_output += "|----|-----------|-------------|----------------|------|---------|---------|---------|-----------|---------|-----|-------------|---------------|----------------|-------|-------|\n"
# results 리스트의 각 결과를 반복하여 Markdown 표의 각 행을 생성
for result in results:
markdown_output += (f"| {result['id']} | {result['item_type']} | {result['category_id']} | {result['category_title']} | "
f"{result['word']} | {result['grammar']} | {result['display']} | {result['concise']} | "
f"{result['voice_usa']} | {result['example']} | {result['url']} | {result['explanation']} | "
f"{result['pronunciation']} | {result['korean_display']} | {result['vowel']} | {result['voice']} |\n")
(📌 result
(* 147번째 줄 ~ 164번째 줄)에 추가 한 변수 명 추가)