บทนำ (Overview)
“Developer Backdoor” นั้นถือว่าเป็นช่องโหว่ของความปลอดภัยชนิดหนึ่ง เนื่องจากมีความเชื่อว่ารหัสผ่าน หรือประตูลับดังกล่าวถูก “Compile” อยู่ในรูปแบบของ “Byte-code” แล้วไม่สามารถที่จะอ่านออกได้ โดยส่วนมาก “Backdoor” ในมุมมองของ “Developer” นั้นก็ใส่ไว้เพื่อเป็นประตูผ่านสะดวกเมื่อถึงเวลา “Support” โปรแกรมแก้ไขปัญหา แต่ก็มีส่วนมากใส่ไว้เพื่อการณ์ประสงค์ร้าย บทความนี้นำเสนอวิธีการตรวจสอบ “Source-code” เพื่อค้นหา “Backdoor” ดังนี้
ขั้นตอน (Steps)
- ทำการ “Convert Bytecode” ให้เป็น “Source code” สามารถติดตามได้จากบทความ (How to reverse engineer .apk – Insecure Bank)
- จากนั้นค้นหา “Classes”, “Methods”, “Properties” ที่เกี่ยวข้องกับ “Login”
- ผลการค้นหาเราพบว่ามี “Class” ที่เกี่ยวข้องกับการ “Login” คือ “LoginActivity” ให้ค้นหา “Method” ที่ชื่อ “onCreate” เนื่องจากเป็นส่วนเริ่มต้นแรกของ “Activity” โดยส่วนมากคือส่วนที่นำมาตั้งค่า “Content View” ต่าง ๆ เกี่ยวกับ “Layout” ที่ใช้สำหรับเขียนโปรแกรม
- เมื่อตรวจสอบต่อไปพบว่า มีการสร้าง “Listener” สำหรับปุ่มกดเวลาเราจะ “Login” โดยเมื่อกดปุ่มแล้วจะเรียกใช้งาน “method” ที่ชื่อ “performlogin” บรรทัดที่ 5 ในส่วนของ “onClick” ดังนี้
this.login_buttons.setOnClickListener(new View.OnClickListener() { public void onClick(View paramAnonymousView) { LoginActivity.this.performlogin(); } });
- ทีนี้เราไปตามหา “method” ที่ชื่อ “performlogin” จาก “Source code” ข้างล่างเราพบว่าโปรแกรมจะส่ง “username” และ “password” ไปยัง “object” อีกตัวที่สร้างมาจาก “class” ที่ชื่อว่า “DoLogin” บรรทัดที่ 5 – 8
protected void performlogin() { this.Username_Text = ((EditText)findViewById(2131558520)); this.Password_Text = ((EditText)findViewById(2131558521)); Intent localIntent = new Intent(this, DoLogin.class); localIntent.putExtra("passed_username", this.Username_Text.getText().toString()); localIntent.putExtra("passed_password", this.Password_Text.getText().toString()); startActivity(localIntent); }
- เราก็ยังตามต่อไปที่ “Class” ที่ชื่อว่า “DoLogin” พยายามค้นหา “Method” ที่ชื่อ “onCreate” เราจะพบว่า โปรแกรมจะอ่าน “username” และ “password” เก็บไว้ แล้วเรียกใช้งาน “Class” ที่ชื่อ “RequestTask” บรรทัด 12 -14
protected void onCreate(Bundle paramBundle) { super.onCreate(paramBundle); setContentView(2130968602); finish(); this.serverDetails = PreferenceManager.getDefaultSharedPreferences(this); this.serverip = this.serverDetails.getString("serverip", null); this.serverport = this.serverDetails.getString("serverport", null); if ((this.serverip != null) && (this.serverport != null)) { Intent localIntent = getIntent(); this.username = localIntent.getStringExtra("passed_username"); this.password = localIntent.getStringExtra("passed_password"); new RequestTask().execute(new String[] { "username" }); return; } startActivity(new Intent(this, FilePrefActivity.class)); Toast.makeText(this, "Server path/port not set!", 1).show(); }
- ข้อมูลเพิ่มเติม: “AsyncTask” เป็น “Class” สำหรับเปิดใช้งาน “UI thread” ที่อนุญาตให้ใช้งานใน “Background operations” โดยมี “Methods” ที่น่าสนใจดังนี้
- ให้ตามไปที่ “Class” ที่ชื่อ “RequestTask” ให้พยายามหา “Methods” ดังนี้ “onPreExecute”, “doInBackground”, “onProgressUpdate” และ “onPostExecute”
class RequestTask extends AsyncTask<String, String, String> { }
- จากการค้นหาเราพบ “Method” ที่ชื่อ “doInBackground” เพียงอันเดียว และมีการใช้งาน “Method” ที่ชื่อ “postData” บรรทัดที่ 5
protected String doInBackground(String... paramVarArgs) { try { postData(paramVarArgs[0]); return null; } }
- เราตามต่อที่ “Method” ที่ชื่อ “postData” พบว่ามีการตรวจสอบว่าถ้า “username” เท่ากับ “devadmin” บรรทัดที่ 10
public void postData(String paramString) throws ClientProtocolException, IOException, JSONException, InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { DefaultHttpClient localDefaultHttpClient = new DefaultHttpClient(); HttpPost localHttpPost1 = new HttpPost(DoLogin.this.protocol + DoLogin.this.serverip + ":" + DoLogin.this.serverport + "/login"); HttpPost localHttpPost2 = new HttpPost(DoLogin.this.protocol + DoLogin.this.serverip + ":" + DoLogin.this.serverport + "/devlogin"); ArrayList localArrayList = new ArrayList(2); localArrayList.add(new BasicNameValuePair("username", DoLogin.this.username)); localArrayList.add(new BasicNameValuePair("password", DoLogin.this.password)); if (DoLogin.this.username.equals("devadmin")) { localHttpPost2.setEntity(new UrlEncodedFormEntity(localArrayList)); } for (HttpResponse localHttpResponse = localDefaultHttpClient.execute(localHttpPost2);; localHttpResponse = localDefaultHttpClient.execute(localHttpPost1)) { InputStream localInputStream = localHttpResponse.getEntity().getContent(); DoLogin.this.result = convertStreamToString(localInputStream); DoLogin.this.result = DoLogin.this.result.replace("\n", ""); if (DoLogin.this.result != null) { if (DoLogin.this.result.indexOf("Correct Credentials") == -1) { break; } Log.d("Successful Login:", ", account=" + DoLogin.this.username + ":" + DoLogin.this.password); saveCreds(DoLogin.this.username, DoLogin.this.password); trackUserLogins(); Intent localIntent2 = new Intent(DoLogin.this.getApplicationContext(), PostLogin.class); localIntent2.putExtra("uname", DoLogin.this.username); DoLogin.this.startActivity(localIntent2); } return; localHttpPost1.setEntity(new UrlEncodedFormEntity(localArrayList)); } Intent localIntent1 = new Intent(DoLogin.this.getApplicationContext(), WrongLogin.class); DoLogin.this.startActivity(localIntent1); } }
- ที่นี้ลองเอา “username” ที่ค้นพบไป “login” โดยไม่จำเป็นต้องใส่ “password” พบว่าสามารถเข้าถึงหน้าหลัง “login” ได้