Android/Compile | Posted by 덩치 2013. 10. 21. 17:17

apk파일 디컴파일하기

펌 OK (출처 표시), 상업적 이용 NO, 컨텐츠 변경 NO

apk파일을 디컴파일하게되면 원본 소스코드를 100%는 아니더라도 확인이 가능하다.

다만, 소스코드 난독화(proguard등)가 적용되어 있다면 코드의 해석은 상당히 힘들게된다.

그럼에도 불구하고 필요한 소스코드가 있다면 디컴파일하여 쓸만한 건덕지를 찾아봐야하니 디컴파일은 중요하다고

할 수 있다. (개발자의 경우 상당히 기분나쁜부분, 우리는 참고용으로만 사용하자)

디컴파일 전에 몇가지 준비사항이 필요하다.


1. https://code.google.com/p/dex2jar/downloads/list 

여기서 dex2jar 다운로드 > 

C:\Program Files\adt-bundle-windows-x86_64-20130522\sdk\platform-tools\dex2jar-0.0.9.15 경로에 압축해제 

(위 경로는 본인의 경우이고, sdk가 있는 경로상에 집어넣어주면 된다.)

dex2jar.bat 파일 우클릭 후 편집 > echo off 를 echo on 으로 수정



2. http://varaneckas.com/jad/

여기서 최신버전 Jad (자바 디컴파일러) 다운로드 > 압축해제


이제 디컴파일 준비는 끝났다.


시작




- 우선 디컴파일할 apk파일의 확장자를 apk 에서 zip로 바꾸면 zip파일이 된다.

- 압축해제하면 classes.dex 파일이 나온다. 이것만 있으면 된다






그렇게 나온 classes.dex 파일을

아까 받은 dex2jar 폴더로 이동시킨다





그리고 현재 위치의 경로를 복사 한 뒤

시작 > 실행 > cmd > cd적은다음 한칸띄고 마우스 우클릭 > 붙여넣기 > 엔터 하면

위의 경로로 이동





이제 d2j-dex2jar.bat classes.dex를 적고 엔터를 누르면 사진과같이 이상한글이 쫘르륵 나오고

경로에 classes-dex2jar.jar 파일이 생성된다.

버전에 따라 d2j-dex2jar.bat가 아니라 dex2jar.bat 인경우도 있는듯 하니 파일명을 잘보고 알아서 입력한다


그리고 생성된 파일을 압축해제하면 클래스파일들이 나오게 된다.




필자는 proguard를 적용한 apk를 사용했기때문에 보는바와같이 클래스명이 저렇게 지맘대로다.

이제 MainActivity.class를 java파일로 변환해줘야 소스코드를 확인할 수 있다.


MainActivity.class파일을 복사하여 2번에서 설치한 Jad가 설치된 경로에 붙여넣어준다





그리고





다시 시작 > 실행 > cmd > jad 경로로 이동 후


jad -o -sjava MainActivity.class  엔터




그러면 이렇게 MainActivity.java 파일이 생긴다 ! 실행해보자




다시말하지만 필자는 코드난독화(proguard)를 적용했기에 변수명이 이렇게 알아먹기 힘들게 표시된다.

(적용하지 않으면 대부분의 소스코드를 볼 수 있다)

오랜시간 해석한다면 불가능하진 않지만 상당한 곤욕을 치루는것은 어쩔 수 없다.


proguard에 관한 포스팅은 이전글을 참고하길 바라며


-끝-

펌 OK (출처 표시), 상업적 이용 NO, 컨텐츠 변경 NO

프로그램 코드 난독화는 특정 난독화 알고리즘(Layout, Data, Control Obfuscation)을 적용하여 소스코드를 분석하기 어

렵게 변환해주는 시스템이다. 본 시스템의 목적은 상세설계에 준하는 설계구조와 주석문, 프로그램 처리방식, 기능 배치, 

구성 등의 설계아이디어 또는 해당하는 알고리즘이나 성능 및 최적화를 위한 구현 노하우 등이 내포되어 있는 SW 소스코

드의 원천기술 유출을 방지하는 것이다.

여기서는 안드로이드의 Proguard를 이용해 코드난독화를 적용한다.

구버전의 포스팅을 보면 proguard.cfg 파일에 대한 언급이 중요시다뤄지는데,

지금의 버전에서는 해당 파일의 유무는 신경쓰지 않는다.


proguard가 적용되는 시점은 프로젝트를 Export할 때 적용되므로, 배포용 apk를 추출하는 방법을 

알고있다면 더 쉽게 할 수 있다. 

(이전 포스팅글 : http://biig.tistory.com/entry/%EC%96%B4%ED%94%8C-%EB%B0%B0%ED%8F%AC%EB%A5%BC-%EC%9C%84%ED%95%9C-keystore%EC%83%9D%EC%84%B1-%EB%B0%8F-%EB%B0%B0%ED%8F%AC%EC%9A%A9-apk-%EC%83%9D%EC%84%B1)




시작


먼저 - adt를 최신버전으로 업데이트시켜준다 ( Help > Check for Updates)

그다음



해당 프로젝트의 project.properties를 실행시켜 표시된부분의 주석을 해제한다.





그리고 해당 프로젝트 우클릭 > Export... 선택





Android 폴더의 Export Android Application 선택후 넥스트




Project명은 자동으로 입력되니 넥스트




keystore 생성하는법은 이전 글에 나와있으니 참고하시고, 이미 키가 있다면 Use existing keystore 선택 후

Browse... 눌러서 원하는 키 선택후 키의 비밀번호 입력 > 넥스트





키 만들때 Alias와 함께 넣은 비밀번호 입력 후 넥스트





apk가 저장될 위치를 설정하고 난 뒤 Finish를 눌러 마무리



그런데 warning이 뜨면서 프로가드가 적용이 안되는 경우가 있다.


그럴때는 어떤 클래스에서 warning이 뜨는지를 파악한 후




이런식으로 해당 클래스에 예외를 걸어줘야한다. proguard warning 등으로 검색하면


많은 정보들을 찾을 수 있을것이다.


-끝-



이렇게하면 proguard가 적용되어 코드난독화가 적용된 apk파일이 생성된다.


이렇게 해줘야 내가 힘들게 짠 소스코드를 다른사람이 디컴파일해서 재사용하는것이 힘들게 할 수 있다.


proguard가 적용되면 어떤 차이가 있는지는 다음 글에서 apk 디컴파일 방법과 함께 소개하겠다.



'Android > Compile' 카테고리의 다른 글

apk파일 디컴파일하기  (12) 2013.10.21
어플 배포를 위한 keystore생성 및 배포용 apk 생성  (0) 2013.10.21

펌 OK (출처 표시), 상업적 이용 NO, 컨텐츠 변경 NO

어플리케이션을 릴리즈 하기 위해서는 키스토어(keystore)를 생성하고, 사인(Signing)을 해야한다.

기본적으로 *디버그용 키가 적용되어 개발하는데는 문제가 없지만 이를 배포하기 위해서는 자기만의 키를 발급받고,

사인을 해서 본인의 흔적을 남겨야한다. 키는 어플의 업데이트 또는 데이터와 코드의 공유를 가능하게 해 주기때문에

중요하게 관리해야하며, 타인에게 유출되어서도 안된다.


*디버그키 - 일반적으로 C:\Users\사용자\.android\debug.keystore 에서 확인가능

컴퓨터마다 위치가 다를 수 있으니, 해당 위치에 보이지 않는다면 


여기서 확인가능하다.




이 글에서는 새로 키를 생성하고 사인하는법과 , 기존의 키를 가지고 사인하는법을 다뤄본다


1. 새로운 키(keystore) 생성과 함께 배포용 apk 생성



프로젝트 우클릭 > Android Tools > Export Signed Application Package... 클릭




프로젝트 명 생성(자동) > 다음



Create new keystore 선택 후 Browse... 클릭




임의 키 이름 입력 후 저장





비밀번호 , 비밀번호 확인 입력 후 다음




Alias는 키값을 식별할 수 있는 적절한 이름, 비밀번호도 마찬가지  (이 두가지는 이전에 설정한 값과 달라도 됨)

Validity는 keysotre의 유효기간으로, 임의로 적으면 됨

Fist and Last Name에 이름 입력 후 다음 (밑의 나머지는 회사정보나 지역명 등등 적어도 되고 안적어도 됨)




배포용 apk를 생성할 위치를 설정하고 Finish를 눌러주면 배포용 apk 생성 끝.





2. 기존의 키를 가지고 배포용 apk 생성


3번째 첨부 이미지에서 Create 대신 Use existing을 선택해주면 됨


-끝-




'Android > Compile' 카테고리의 다른 글

apk파일 디컴파일하기  (12) 2013.10.21
Proguard를 이용해 코드난독화 적용하기  (7) 2013.10.21

펌 OK (출처 표시), 상업적 이용 NO, 컨텐츠 변경 NO

퍼미션은 INTERNET



public void DataSend() {

try{

url = new URI("http://192.168.0.20:8080/testWebapp/Receive.jsp");

new Thread() {

public void run() {

try {

HttpClient httpclient = new DefaultHttpClient();

HttpPost httpPost = new HttpPost(url);

ArrayList<BasicNameValuePair> nameValuePairs = new ArrayList<BasicNameValuePair>();

nameValuePairs.add(new BasicNameValuePair("mark_id", sendMarkId));

nameValuePairs.add(new BasicNameValuePair("store_name", sendName));

nameValuePairs.add(new BasicNameValuePair("latitude", sendLat));

nameValuePairs.add(new BasicNameValuePair("longitude", sendLng));

nameValuePairs.add(new BasicNameValuePair("comments", sendTip));

nameValuePairs.add(new BasicNameValuePair("kind", sendKind));

httpPost.setEntity(new UrlEncodedFormEntity(nameValuePairs, "utf-8"));

httpclient.execute(httpPost);

} catch (Exception e) {

e.printStackTrace();

}

}

}.start();

}catch(Exception e){

Log.e("fureun",e.toString());

}

}

Android/예제 | Posted by 덩치 2013. 7. 24. 13:05

XmlPullParser를 이용한 파싱 예제

펌 OK (출처 표시), 상업적 이용 NO, 컨텐츠 변경 NO

14년 1월 17일 수정내용 ----- 예제 프로젝트 실행이 안된다고 해서 알아보니 다른 패키지가 섞여있었네요.

weather 패키지 삭제한 상태이며 정상작동합니다.



net2.zip



사용한 기능 - XmlPullParser , AlertDialog 등등


매니패스트 퍼미션은 

<uses-permission android:name="android.permission.INTERNET"/>

추가입니다.


InputStreamReader로 웹사이트 내용을 읽어와 URL로 넘겨주고

XmlPullParser를 이용해 한경닷컴 뉴스 제목과 내용을 파싱



MainActivity.java


package com.example.net2;


import java.io.BufferedReader;

import java.io.InputStream;

import java.io.InputStreamReader;

import java.io.StringReader;

import java.net.HttpURLConnection;

import java.net.URL;

import java.util.ArrayList;


import org.xmlpull.v1.XmlPullParser;

import org.xmlpull.v1.XmlPullParserFactory;


import android.app.Activity;

import android.app.AlertDialog;

import android.app.AlertDialog.Builder;

import android.content.DialogInterface;

import android.os.Bundle;

import android.os.Handler;

import android.os.Message;

import android.util.Log;

import android.view.View;

import android.widget.AdapterView;

import android.widget.AdapterView.OnItemClickListener;

import android.widget.ArrayAdapter;

import android.widget.Button;

import android.widget.EditText;

import android.widget.ListView;

import android.widget.Toast;


public class MainActivity extends Activity {

private Builder listDialog;

EditText urltest;

String urlStr,tv,readLine,item;

String tagName,title,body,link = null;

ListView screen;

StringBuffer sb;

Button bt;

int eventType;

XmlPullParser xpp;

XmlPullParserFactory factory;

AlertDialog.Builder dialogBuilder;

ArrayList<String> arrList,arrList2;

ArrayAdapter<String> adapter,adapter2;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);

bt = (Button)findViewById(R.id.bt);

screen = (ListView)findViewById(R.id.screen);

urltest = (EditText)findViewById(R.id.urltest);


arrList = new ArrayList<String>();

arrList2 = new ArrayList<String>();

adapter = new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1,arrList);

screen.setAdapter(adapter);

dialog();

urltest.setOnLongClickListener(new View.OnLongClickListener() {

@Override

public boolean onLongClick(View arg0) { //주소창을 길게 터치하면 다이얼로그창 뷰

dialog();

return false;

}});

screen.setOnItemClickListener(new OnItemClickListener() {  //리스트 목록을 터치하면 이벤트 실행

public void onItemClick(AdapterView<?> parent, View view, int position, long id) {

dialogBuilder.setTitle(parent.getItemAtPosition(position).toString());

       dialogBuilder.setMessage(arrList2.get(position).toString());

       dialogBuilder.setPositiveButton("확인", null);

       dialogBuilder.show();

}});

bt.setOnClickListener(new View.OnClickListener() {

public void onClick(View v) {

adapter.clear(); //어뎁터,리스트를 초기화시켜 새로운 데이터를 받음

arrList.clear();

adapter.notifyDataSetChanged(); //이까지. 초기화된걸 어뎁터로 리스트와 연결시켜줌

urlStr = urltest.getText().toString();

NetworkThread thread = new NetworkThread(); //스레드 선언과 호출

thread.setDaemon(true); //이거해줘야 종료될때 스레드가 죽는다는데 잘모르겠음

thread.start();

}});}


class NetworkThread extends Thread {

public void run() {

stream();

}}

Handler handler = new Handler(){

public void handleMessage(Message msg){

if(msg.what ==0){

adapter.notifyDataSetChanged();

}}};

public void parsing(){

try{

factory = XmlPullParserFactory.newInstance();

factory.setNamespaceAware(true);

xpp = factory.newPullParser();

xpp.setInput(new StringReader (tv.trim()));

eventType = xpp.getEventType();

while (eventType != XmlPullParser.END_DOCUMENT){ //최초 title테그안에 쓸데없는 내용이 있어서 추가해줬음. 

if(eventType == XmlPullParser.START_TAG){             //ex)RSS 한경닷컴어쩌구저쩌구 제목과상관없는내용

String tagName2 = xpp.getName();

if(tagName2.equals("item")){ //아이템 테그 이후부터 검색시작

while (eventType != XmlPullParser.END_DOCUMENT){

if(eventType == XmlPullParser.START_TAG){ //스타트테그를 만나면 테그값 저장

tagName = xpp.getName();

}

else if(eventType == XmlPullParser.TEXT){ //스타트테그가 아니라 텍스트일경우

if(tagName!=null){ //텍스트가 쓰래기값인경우를 배제

if(tagName.equals("title")){ //필요한건 타이틀과 본문이기때문에 스타트테그는 타이틀일때 실행

title = xpp.getText().trim();

if (title.length() > 0) { //여기도 한경닷컴특성상 제목 \n 공백 \n 제목 이런식으로돼있어서 추가

arrList.add(title);

}

}else if(tagName.equals("description")){  //스타트테그가 타이틀이 아니라 본문이면 마찬가지로 수행

link = xpp.getText().trim();

if(link.length()>0){

arrList2.add(link);

}}}}

eventType = xpp.next();

}}}

eventType = xpp.next();

}

}catch (Exception e){}}

public void stream(){  //홈페이지 정보를 읽어들여 파서로 넘겨주기위한 매서드

HttpURLConnection urlConnection = null;

try {

URL url = new URL(urlStr);

urlConnection = (HttpURLConnection) url.openConnection(); //url 연결

InputStream in = urlConnection.getInputStream(); //url내용을 비트형으로읽어옴

InputStreamReader isr = new InputStreamReader(in); // 뭐더라 다시 리더로 변환해주는과정

BufferedReader buf = new BufferedReader(isr); //줄단위로 읽어주기위해 실행

sb = new StringBuffer(); //buf의 한줄 한줄 값을 입력받아 한꺼번에 출력시키기 위해 사용


while (true) {

readLine = buf.readLine();

if (readLine == null) //읽어올 값이 없으면

break; //멈춤 그렇지않으면

sb.append(readLine); //스트링버퍼에 리드라인 내용 계속 추가

sb.append("\n"); //이게없으면 줄바꿈이없어서 알아보기가힘듦. buf의 줄바꿈단위마다 실행

}

tv = sb.toString();

parsing();


handler.sendEmptyMessage(0); //핸들러호출

}

catch (Exception e) {

}

finally {

if (urlConnection != null) {

urlConnection.disconnect();

}}}

public void dialog(){ //다이얼로그 메시지 출력부

final String[] items = {"증권","경제/금융","부동산","산업","국제","정치","사회","스포츠/문화","사설/칼럼"}; //다이얼로그에 리스트 추가

listDialog = new AlertDialog.Builder(this); //다이얼로그 선언

listDialog.setTitle("목차").setItems(items, new DialogInterface.OnClickListener() { //다이얼로그 내에 items값을 갖는 리스트 추가

@Override

public void onClick(DialogInterface arg0, int val) {

// TODO Auto-generated method stub

Toast.makeText(MainActivity.this, "선택 : "+items[val], Toast.LENGTH_SHORT).show();

if(items[val].equals("증권")){  //리스트 내용을 클릭하면 그에 맞게 에디트뷰의 주소를 설정한대로 입력

urltest.setText("http://rss.hankyung.com/new/news_stock.xml");

}else if(items[val].equals("경제/금융")){  //여기서 equals로 비교하지 않고 인덱스를 받아서 하면 더 편리함

urltest.setText("http://rss.hankyung.com/new/news_economy.xml");

}else if(items[val].equals("부동산")){

urltest.setText("http://rss.hankyung.com/new/news_estate.xml");

}else if(items[val].equals("산업")){

urltest.setText("http://rss.hankyung.com/new/news_industry.xml");

}else if(items[val].equals("국제")){

urltest.setText("http://rss.hankyung.com/new/news_intl.xml");

}else if(items[val].equals("정치")){

urltest.setText("http://rss.hankyung.com/new/news_politics.xml");

}else if(items[val].equals("사회")){

urltest.setText("http://rss.hankyung.com/new/news_society.xml");

}else if(items[val].equals("스포츠/문화")){

urltest.setText("http://rss.hankyung.com/new/news_sports.xml");

}else if(items[val].equals("사설/칼럼")){

urltest.setText("http://rss.hankyung.com/new/news_column.xml");

}}

}).setNegativeButton("직접입력",null).show();

}}




activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
    
    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        >
   <EditText
       android:id="@+id/urltest"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:text="http://rss.hankyung.com/new/news_economy.xml"
       android:layout_weight="4" />
   <Button
       android:id="@+id/bt"
       android:layout_width="150dp"
       android:layout_height="wrap_content"
       android:text="검색"
       android:layout_weight="1"/>
    </LinearLayout>
   
    <ListView
        android:id="@+id/screen"
        android:layout_width="fill_parent"
        android:layout_height="match_parent"
        android:layout_weight="1" />

</LinearLayout>