พิจารณาว่าทำงานบนอุปกรณ์ที่รูทหรือไม่


292

แอพของฉันมีฟังก์ชั่นบางอย่างที่จะใช้งานได้บนอุปกรณ์ที่รูทว่างเท่านั้น แทนที่จะมีคุณสมบัตินี้ล้มเหลวเมื่อมีการใช้งาน (และจากนั้นแสดงข้อความข้อผิดพลาดที่เหมาะสมแก่ผู้ใช้) ฉันต้องการความสามารถในการตรวจสอบว่ารูทนั้นว่างก่อนหรือไม่หากไม่ให้ซ่อนตัวเลือกนั้น ๆ ในตอนแรก .

มีวิธีทำเช่นนี้หรือไม่?


11
ไม่มีวิธีที่เชื่อถือได้ในการทำเช่นนั้น คำตอบด้านล่างตรวจสอบลักษณะทั่วไป แต่อุปกรณ์ที่กำหนดอาจไม่ได้รับการรูทด้วยวิธีทั่วไป หากการตรวจสอบหารูทกลายเป็นที่แพร่หลายโซลูชันรูทอาจเริ่มพยายามซ่อนตัวเอง เนื่องจากพวกเขาสามารถปรับเปลี่ยนพฤติกรรมของระบบปฏิบัติการจึงมีตัวเลือกมากมายให้ทำเช่นนั้น
Chris Stratton

อาจเป็นการดีกว่าที่จะระบุว่าฟังก์ชั่นนี้ไม่สามารถใช้งานได้เนื่องจากการขาดความสามารถในการรูทที่ให้ข้อมูลเพิ่มเติมแก่ผู้ใช้แทนที่จะซ่อนความสามารถของแอพของคุณซึ่งเพิ่มความกำกวมให้กับประสบการณ์โดยรวม
กรงขังสุนัขจิ้งจอก

คำตอบด้านล่างใช้ได้กับSystemless Rootหรือไม่
Piyush Kukadiya

คำตอบ:


260

นี่คือคลาสที่จะตรวจสอบรูทหนึ่งในสามวิธี

/** @author Kevin Kowalewski */
public class RootUtil {
    public static boolean isDeviceRooted() {
        return checkRootMethod1() || checkRootMethod2() || checkRootMethod3();
    }

    private static boolean checkRootMethod1() {
        String buildTags = android.os.Build.TAGS;
        return buildTags != null && buildTags.contains("test-keys");
    }

    private static boolean checkRootMethod2() {
        String[] paths = { "/system/app/Superuser.apk", "/sbin/su", "/system/bin/su", "/system/xbin/su", "/data/local/xbin/su", "/data/local/bin/su", "/system/sd/xbin/su",
                "/system/bin/failsafe/su", "/data/local/su", "/su/bin/su"};
        for (String path : paths) {
            if (new File(path).exists()) return true;
        }
        return false;
    }

    private static boolean checkRootMethod3() {
        Process process = null;
        try {
            process = Runtime.getRuntime().exec(new String[] { "/system/xbin/which", "su" });
            BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream()));
            if (in.readLine() != null) return true;
            return false;
        } catch (Throwable t) {
            return false;
        } finally {
            if (process != null) process.destroy();
        }
    }
}

8
หากคำถามสองข้อรับประกันคำตอบที่เหมือนกันแสดงว่าเป็น 99% ของเวลาที่ซ้ำกันดังนั้นให้ตั้งค่าสถานะเป็นดักจับแทนการโพสต์คำตอบเดียวกันทั้งคู่ ขอบคุณ
Kev

2
อาจเป็นเช่นนั้นอย่างไรก็ตามฉันแค่บอกให้คุณรู้ว่าคำตอบที่ซ้ำกันแน่นอนถูกตั้งค่าสถานะโดยชุมชน คุณควรปรับคำตอบของคุณและจัดการปัญหาเฉพาะของ OP คัดลอกและวางคำตอบมีความเสี่ยงจากการดึงดูด downvotes
Kev

9
-1 วิธีนี้ไม่สามารถใช้งานได้เนื่องจากโทรศัพท์บางรุ่นมีsuไบนารีในขณะที่ไม่รูท
neevek

12
เพิ่งแจ้งให้คุณทราบว่าแอป Fox Digital Copy (Beta) ใช้รหัสของคุณเกือบจะเป็นคำต่อคำทั้งหมดรวมถึงคลาส Root และ ExecShell รวมถึงวิธีการ พบว่ามันน่าขบขันอย่างมาก
แมตต์โจเซฟ

8
ฉันจะฟ้องพวกเขาเหมือนสุนัขจิ้งจอกฟ้องคนอื่น ๆ นับไม่ถ้วน
Kevin Parker

59

หากคุณใช้ Crashlytics ของ Fabric / Firebase อยู่แล้วคุณสามารถโทรหา

CommonUtils.isRooted(context)

นี่คือการใช้งานปัจจุบันของวิธีการนั้น:

public static boolean isRooted(Context context) {
    boolean isEmulator = isEmulator(context);
    String buildTags = Build.TAGS;
    if(!isEmulator && buildTags != null && buildTags.contains("test-keys")) {
        return true;
    } else {
        File file = new File("/system/app/Superuser.apk");
        if(file.exists()) {
            return true;
        } else {
            file = new File("/system/xbin/su");
            return !isEmulator && file.exists();
        }
    }
}

คำตอบที่ดีที่สุด โปรดใช้สิ่งนี้กับห้องสมุดใด ๆ มีการเรียกใช้บวกผิด ๆ บนอุปกรณ์จีนจำนวนมาก
Pedro Paulo Amorim

มีวิธีการใดที่เป็นเท็จในเชิงบวกหรือไม่?
Ehsan Mashhadi

ฉันทดสอบสิ่งนี้ใน Nexus 5 ด้วยdownload.chainfire.eu/363/CF-Root/CF-Auto-Root/…อันนี้ไม่ถูกต้อง
Jeffrey Liu

54

ไลบรารี RootTools เสนอวิธีง่าย ๆ ในการตรวจสอบหารูท:

RootTools.isRootAvailable()

การอ้างอิง


10
isRootAvailable () เพียงตรวจสอบการมีอยู่ของ su ในพา ธ และไดเร็กตอรี่ฮาร์ดโค้ดอื่น ๆ ฉันได้ยินมาว่ามีเครื่องมือที่ไม่ได้ใช้การรูทบางตัวจะออกจากที่นั่นดังนั้นมันจะให้ผลบวกที่ผิด
Bob Whiteman

13
RootTools.isAccessGiven () จะไม่เพียง แต่ตรวจสอบหารูท แต่ยังขออนุญาตรูท; ดังนั้นอุปกรณ์ที่ไม่ได้ทำการรูทจะคืนค่าเท็จด้วยวิธีนี้เสมอ
รวม

2
@ aggregate1166877 คุณพูดถูก แต่ก็ไม่ดีพอถ้าฉันไม่ต้องการการอนุญาตรูทเมื่อฉันถาม ฉันแค่อยากรู้ว่ามันถูกรูทหรือไม่ แต่ฉันไม่ต้องการการอนุญาตรูทในตอนนี้
neevek

4
isAccessGiven () คืนค่า false เมื่อผู้ใช้ปฏิเสธการอนุญาตแม้ว่าอุปกรณ์จะถูกรูท
subair_a

นี่เป็นคำตอบเดียวที่ฉันเห็นว่าคุ้มค่า ตรวจสอบคำตอบของฉันด้านล่างหากคุณต้องการสิ่งที่คล้ายกับเพียงแค่คัดลอกวางในหรือต้องการรายละเอียดเพิ่มเติม
rsimp

52

ในแอปพลิเคชันของฉันฉันกำลังตรวจสอบว่าอุปกรณ์ถูกรูทหรือไม่โดยการรันคำสั่ง "su" แต่วันนี้ฉันได้ลบส่วนนี้ของรหัสของฉัน ทำไม?

เพราะแอปพลิเคชันของฉันกลายเป็นนักฆ่าหน่วยความจำ อย่างไร? ให้ฉันเล่าเรื่องของฉันให้คุณฟัง

มีข้อร้องเรียนว่าแอปพลิเคชันของฉันกำลังทำให้อุปกรณ์ทำงานช้าลง (แน่นอนฉันคิดว่าไม่เป็นความจริง) ฉันพยายามหาสาเหตุ ดังนั้นฉันจึงใช้ MAT เพื่อทำให้กองทิ้งและวิเคราะห์และทุกอย่างดูสมบูรณ์แบบ แต่หลังจากเปิดแอพใหม่หลายครั้งฉันรู้ว่าอุปกรณ์ช้าลงจริง ๆ และการหยุดแอปพลิเคชันของฉันไม่ได้ทำให้มันเร็วขึ้น (เว้นแต่ฉันจะรีสตาร์ทอุปกรณ์) ฉันวิเคราะห์ไฟล์ดัมพ์อีกครั้งขณะที่อุปกรณ์ช้ามาก แต่ทุกอย่างก็ยังสมบูรณ์แบบสำหรับไฟล์ดัมพ์ จากนั้นฉันก็ทำสิ่งที่ต้องทำในตอนแรก ฉันระบุกระบวนการ

$ adb shell ps

Surprize; มีกระบวนการหลายอย่างสำหรับใบสมัครของฉัน (มีแท็กกระบวนการของใบสมัครของฉันที่รายการ) บางคนไม่ได้เป็นซอมบี้

ด้วยแอปพลิเคชันตัวอย่างที่มีกิจกรรมเดียวและดำเนินการเพียงคำสั่ง "su" ฉันรู้ว่ากระบวนการซอมบี้ถูกสร้างขึ้นในทุกการเปิดตัวแอปพลิเคชัน ในตอนแรกซอมบี้เหล่านี้จัดสรร 0KB แต่กว่าสิ่งที่เกิดขึ้นและกระบวนการซอมบี้ถือ KB เกือบเท่ากับกระบวนการหลักของแอปพลิเคชันของฉันและพวกเขากลายเป็นกระบวนการมาตรฐาน

มีรายงานข้อผิดพลาดสำหรับปัญหาเดียวกันบน bugs.sun.com: http://bugs.sun.com/view_bug.do?bug_id=6474073สิ่งนี้อธิบายว่าหากไม่พบคำสั่ง zombies จะถูกสร้างขึ้นด้วยวิธี exec () . แต่ฉันก็ยังไม่เข้าใจว่าทำไมและวิธีการที่พวกเขากลายเป็นกระบวนการมาตรฐานและถือกิโลไบต์ที่สำคัญ (สิ่งนี้ไม่ได้เกิดขึ้นตลอดเวลา)

คุณสามารถลองถ้าคุณต้องการด้วยตัวอย่างรหัสด้านล่าง;

String commandToExecute = "su";
executeShellCommand(commandToExecute);

วิธีการดำเนินการคำสั่งง่าย ๆ ;

private boolean executeShellCommand(String command){
    Process process = null;            
    try{
        process = Runtime.getRuntime().exec(command);
        return true;
    } catch (Exception e) {
        return false;
    } finally{
        if(process != null){
            try{
                process.destroy();
            }catch (Exception e) {
            }
        }
    }
}

เพื่อสรุป ฉันไม่มีคำแนะนำให้คุณตรวจสอบว่าอุปกรณ์ถูกรูทหรือไม่ แต่ถ้าฉันเป็นคุณฉันจะไม่ใช้ Runtime.getRuntime (). exec ()

ยังไงซะ; RootTools.isRootAvailable () ทำให้เกิดปัญหาเดียวกัน


5
นั่นเป็นเรื่องที่น่าเป็นห่วงมาก ฉันมีคลาสการตรวจจับอุปกรณ์ที่รูทซึ่งทำสิ่งเดียวกัน - หลังจากอ่านสิ่งนี้ฉันยืนยันว่าทะเลอีเจียนรายละเอียดด้านบน กระบวนการซอมบี้เป็นครั้งคราวถูกทิ้งไว้ข้างหลังการชะลอตัวของอุปกรณ์ ฯลฯ ...
AWT

1
ฉันยืนยันปัญหากับ RootTools 3.4 บน android GT-S5830i 2.3.6 ซอมบี้ส่วนใหญ่ได้รับการจัดสรรหน่วยความจำและปัญหาเป็นระบบ ฉันต้องรีสตาร์ทอุปกรณ์หลังจากผ่านการทดสอบ 3-4 ครั้ง ฉันแนะนำให้บันทึกผลการทดสอบเป็นค่ากำหนดที่ใช้ร่วมกัน
คริสต์

2
Google แนะนำให้ใช้ ProcessBuilder () และคำสั่ง start ()
EntangledLoops

1
@NickS น่าสนใจ แต่คุณสั่งเปิดอะไร ฉันไม่มีปัญหาเดียวกันที่นี่ในการออกคำสั่งบนโทรศัพท์ Android จำนวนมากที่มีระดับ API ที่แตกต่างกันตั้งแต่ 9 - 23
EntangledLoops

1
@EntangledLoops ขอบคุณ. ฉันเปิดไบนารีของตัวเองและโต้ตอบกับมันผ่าน stdin / stdout ฉันตรวจสอบอีกครั้งว่าฉันหยุดมันและพบว่าฉันพลาด Process.destroy () ในกรณีใดกรณีหนึ่ง ดังนั้นไม่มีซอมบี้
Nick S

37

คำตอบหลายข้อที่แสดงไว้ที่นี่มีปัญหาโดยธรรมชาติ:

  • การตรวจสอบคีย์ทดสอบมีความสัมพันธ์กับการเข้าถึงรูท แต่ไม่จำเป็นว่าจะต้องรับประกัน
  • ไดเร็กทอรี "PATH" ควรมาจากตัวแปรสภาพแวดล้อม "PATH" จริงแทนที่จะเป็นรหัสฮาร์ด
  • การมีอยู่ของไฟล์ปฏิบัติการ "su" นั้นไม่ได้หมายความว่าอุปกรณ์นั้นจะถูกรูท
  • ปฏิบัติการ "ซึ่ง" อาจหรืออาจไม่ได้รับการติดตั้งและคุณควรให้ระบบแก้ไขเส้นทางของมันหากเป็นไปได้
  • เพียงเพราะติดตั้งแอป SuperUser บนอุปกรณ์ไม่ได้หมายความว่าอุปกรณ์มีการเข้าถึงรูท

RootToolsห้องสมุดจาก Stericson ดูเหมือนว่าจะมีการตรวจสอบรากมากขึ้นถูกต้องตามกฎหมาย มันมีเครื่องมือและยูทิลิตี้พิเศษมากมายดังนั้นฉันแนะนำเป็นอย่างยิ่ง อย่างไรก็ตามไม่มีคำอธิบายว่ามันตรวจสอบหารูตโดยเฉพาะอย่างไรและอาจหนักกว่าแอพส่วนใหญ่ที่ต้องการจริงๆ

ฉันได้สร้างวิธีการอรรถประโยชน์สองสามอย่างซึ่งอ้างอิงจากไลบรารี RootTools อย่างหลวม ๆ หากคุณเพียงแค่ต้องการตรวจสอบว่าปฏิบัติการ "su" บนอุปกรณ์คุณสามารถใช้วิธีการต่อไปนี้:

public static boolean isRootAvailable(){
    for(String pathDir : System.getenv("PATH").split(":")){
        if(new File(pathDir, "su").exists()) {
            return true;
        }
    }
    return false;
}

วิธีนี้จะวนซ้ำผ่านไดเรกทอรีที่ระบุไว้ในตัวแปรสภาพแวดล้อม "เส้นทาง" และตรวจสอบว่ามีไฟล์ "su" อยู่ในหนึ่งในนั้นหรือไม่

เพื่อตรวจสอบการเข้าถึงรูทอย่างแท้จริงต้องรันคำสั่ง "su" หากมีการติดตั้งแอปเช่น SuperUser ดังนั้น ณ จุดนี้อาจขอสิทธิ์การเข้าถึงรูทหรือหากได้รับอนุญาต / ปฏิเสธอาจมีการแสดงให้เห็นว่าขนมปังปิ้งแสดงว่าได้รับอนุญาต / ปฏิเสธ คำสั่งที่ดีในการเรียกใช้คือ "id" เพื่อให้คุณสามารถตรวจสอบได้ว่า ID ผู้ใช้นั้นเป็นจริง 0 (รูท)

ต่อไปนี้เป็นตัวอย่างวิธีการตรวจสอบว่าได้รับสิทธิ์การเข้าถึงรูต

public static boolean isRootGiven(){
    if (isRootAvailable()) {
        Process process = null;
        try {
            process = Runtime.getRuntime().exec(new String[]{"su", "-c", "id"});
            BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream()));
            String output = in.readLine();
            if (output != null && output.toLowerCase().contains("uid=0"))
                return true;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (process != null)
                process.destroy();
        }
    }

    return false;
}

เป็นเรื่องสำคัญที่จะต้องทดสอบการใช้คำสั่ง "su" เพราะอีมูเลเตอร์บางตัวมีไฟล์ปฏิบัติการที่ติดตั้งไว้ล่วงหน้า "su" แต่อนุญาตให้ผู้ใช้บางคนเข้าถึงได้เช่นเชลล์ adb

สิ่งสำคัญคือต้องตรวจสอบการมีอยู่ของ "su" ที่สามารถเรียกใช้ได้ก่อนที่จะพยายามเรียกใช้เพราะเป็นที่ทราบกันดีว่า Android ไม่ได้ทำการกำจัดกระบวนการที่พยายามเรียกใช้คำสั่งที่หายไป กระบวนการผีเหล่านี้สามารถเรียกใช้ปริมาณการใช้หน่วยความจำในช่วงเวลา


วิธี isRootAvailable () ใช้งานได้ดีขอบคุณ ฉันไม่แนะนำให้ใช้สิ่งนี้ในเธรดหลักอย่างไรก็ตามเพื่อหลีกเลี่ยงการ ANR เช่นการโทรจาก AsyncTask
Thunderstick

1
ฉันคิดว่ามันเป็นความแตกต่างระหว่างความต้องการเพื่อให้แน่ใจว่าไม่มีรากและไม่แน่ใจว่าเป็น หากคุณต้องการให้แน่ใจว่าอุปกรณ์ไม่ได้ถูกรูทการตรวจสอบที่แนะนำนั้นดี คุณจะได้รับผลบวกปลอม แต่ก็ไม่เป็นไรเมื่อไม่ได้ใช้งานรหัสของคุณบนอุปกรณ์ที่ถูกบุกรุกเป็นข้อกังวลหลักของคุณ
Jeffrey Blattman

1
@ DAC84 ฉันไม่แน่ใจว่าฉันเข้าใจคำถามของคุณ หากคุณรัน isRootGiven และปฏิเสธในแอปพลิเคชันการรูทของคุณก็ควรส่งคืนค่าเท็จ นั่นไม่ใช่สิ่งที่เกิดขึ้น? หากคุณต้องการหลีกเลี่ยงการแจ้งเตือนคุณสามารถใช้ isRootAvailable ซึ่งอาจมีชื่อว่า doesSUExist คุณสามารถลองกำหนดค่าแอปพลิเคชันรูทของคุณเพื่อให้รูทได้อย่างอิสระและไม่สามารถจัดการมันได้
rsimp

1
@BeeingJk ไม่จริง ๆ แต่มันเป็นสิ่งที่ดีที่สุดที่คุณสามารถตรวจสอบได้โดยไม่ต้องใช้ su ซึ่งเป็นการทดสอบจริง คุณต้องตรวจสอบ su ใน PATH ก่อนที่จะพยายามดำเนินการ อย่างไรก็ตามการรัน su มักจะส่งผลให้ข้อความขนมปังปิ้งหรือโต้ตอบกับแอปจัดการรากซึ่งอาจไม่ใช่สิ่งที่คุณต้องการ สำหรับตรรกะของคุณเองคุณอาจเห็นว่าการดำรงอยู่ของ su เพียงพอ สิ่งนี้ยังคงสามารถให้ผลบวกที่ผิดพลาดในตัวเลียนแบบบางตัวซึ่งอาจมีไฟล์ที่ปฏิบัติการได้ แต่ล็อคการเข้าถึงได้
rsimp

1
@BeeingJk isRootAvailable อาจเป็นสิ่งที่คุณต้องการ แต่ประเด็นที่ฉันพยายามทำก็คือชื่อแบบนั้นหรือแม้แต่ SUExist ให้ความหมายที่ดีกว่าชื่อเมธอดอย่าง isDeviceRooted ซึ่งไม่ถูกต้องนัก หากคุณจำเป็นต้องยืนยันการเข้าถึงรูททั้งหมดก่อนที่จะดำเนินการต่อคุณต้องลองเรียกใช้คำสั่ง su เช่นเดียวกับที่เขียนใน isRootGiven
rsimp

35

อัปเดต 2017

คุณสามารถทำมันตอนนี้กับGoogle SafetyNet API SafetyNet API ให้ API การทดสอบซึ่งช่วยให้คุณประเมินความปลอดภัยและความเข้ากันได้ของสภาพแวดล้อม Android ที่แอพของคุณทำงาน

การรับรองนี้สามารถช่วยในการพิจารณาว่าอุปกรณ์เฉพาะได้รับการดัดแปลงหรือดัดแปลง

Attestation API ส่งคืนการตอบกลับ JWS เช่นนี้

{
  "nonce": "R2Rra24fVm5xa2Mg",
  "timestampMs": 9860437986543,
  "apkPackageName": "com.package.name.of.requesting.app",
  "apkCertificateDigestSha256": ["base64 encoded, SHA-256 hash of the
                                  certificate used to sign requesting app"],
  "apkDigestSha256": "base64 encoded, SHA-256 hash of the app's APK",
  "ctsProfileMatch": true,
  "basicIntegrity": true,
}

การแยกการตอบสนองนี้สามารถช่วยคุณตรวจสอบว่าอุปกรณ์ถูกรูทหรือไม่

อุปกรณ์ที่ถูกรูทดูเหมือนจะทำให้ ctsProfileMatch = false

คุณสามารถทำได้บนฝั่งไคลเอ็นต์ แต่แนะนำให้แยกการตอบสนองบนฝั่งเซิร์ฟเวอร์ เซิร์ฟเวอร์ไคลเอนต์พื้นฐานที่มี archtecture กับ safety net API จะมีลักษณะดังนี้: -

ป้อนคำอธิบายรูปภาพที่นี่


3
ข้อมูลที่ยอดเยี่ยมและในบริบทอื่นฉันเชื่อว่านี่จะเป็นคำตอบที่ถูกต้อง น่าเสียดายที่คำถาม OPs ไม่ได้เกี่ยวกับการปกป้องแอปของเขาจากสภาพแวดล้อมที่ไม่ปลอดภัย แต่การตรวจสอบรูทเพื่อเปิดใช้งานฟีเจอร์เฉพาะรูทในแอปของเขา สำหรับจุดประสงค์ของการปฏิบัติการกระบวนการนี้ดูเหมือนจะซับซ้อนเกินไป
rsimp

31

การตรวจสอบรูทที่ระดับ Java ไม่ใช่โซลูชันที่ปลอดภัย หากแอปของคุณมีข้อกังวลด้านความปลอดภัยที่จะเรียกใช้บนอุปกรณ์ที่มีการรูทข้อมูลโปรดใช้โซลูชันนี้

คำตอบของ Kevin ทำงานได้เว้นแต่โทรศัพท์จะมีแอพเช่น RootCloak แอพดังกล่าวมีการจัดการกับ Java API เมื่อโทรศัพท์ถูกรูทและพวกเขาเยาะเย้ย API เหล่านี้เพื่อส่งคืนโทรศัพท์ไม่ได้ถูกรูท

ฉันได้เขียนโค้ดระดับพื้นฐานตามคำตอบของ Kevin มันใช้งานได้ดีกับ RootCloak! นอกจากนี้มันไม่ได้ทำให้เกิดปัญหาการรั่วไหลของหน่วยความจำ

#include <string.h>
#include <jni.h>
#include <time.h>
#include <sys/stat.h>
#include <stdio.h>
#include "android_log.h"
#include <errno.h>
#include <unistd.h>
#include <sys/system_properties.h>

JNIEXPORT int JNICALL Java_com_test_RootUtils_checkRootAccessMethod1(
        JNIEnv* env, jobject thiz) {


    //Access function checks whether a particular file can be accessed
    int result = access("/system/app/Superuser.apk",F_OK);

    ANDROID_LOGV( "File Access Result %d\n", result);

    int len;
    char build_tags[PROP_VALUE_MAX]; // PROP_VALUE_MAX from <sys/system_properties.h>.
    len = __system_property_get(ANDROID_OS_BUILD_TAGS, build_tags); // On return, len will equal (int)strlen(model_id).
    if(strcmp(build_tags,"test-keys") == 0){
        ANDROID_LOGV( "Device has test keys\n", build_tags);
        result = 0;
    }
    ANDROID_LOGV( "File Access Result %s\n", build_tags);
    return result;

}

JNIEXPORT int JNICALL Java_com_test_RootUtils_checkRootAccessMethod2(
        JNIEnv* env, jobject thiz) {
    //which command is enabled only after Busy box is installed on a rooted device
    //Outpput of which command is the path to su file. On a non rooted device , we will get a null/ empty path
    //char* cmd = const_cast<char *>"which su";
    FILE* pipe = popen("which su", "r");
    if (!pipe) return -1;
    char buffer[128];
    std::string resultCmd = "";
    while(!feof(pipe)) {
        if(fgets(buffer, 128, pipe) != NULL)
            resultCmd += buffer;
    }
    pclose(pipe);

    const char *cstr = resultCmd.c_str();
    int result = -1;
    if(cstr == NULL || (strlen(cstr) == 0)){
        ANDROID_LOGV( "Result of Which command is Null");
    }else{
        result = 0;
        ANDROID_LOGV( "Result of Which command %s\n", cstr);
        }
    return result;

}

JNIEXPORT int JNICALL Java_com_test_RootUtils_checkRootAccessMethod3(
        JNIEnv* env, jobject thiz) {


    int len;
    char build_tags[PROP_VALUE_MAX]; // PROP_VALUE_MAX from <sys/system_properties.h>.
    int result = -1;
    len = __system_property_get(ANDROID_OS_BUILD_TAGS, build_tags); // On return, len will equal (int)strlen(model_id).
    if(len >0 && strstr(build_tags,"test-keys") != NULL){
        ANDROID_LOGV( "Device has test keys\n", build_tags);
        result = 0;
    }

    return result;

}

ในโค้ด Java ของคุณคุณต้องสร้างคลาส wrapper RootUtils เพื่อทำการโทรแบบเนทีฟ

    public boolean checkRooted() {

       if( rootUtils.checkRootAccessMethod3()  == 0 || rootUtils.checkRootAccessMethod1()  == 0 || rootUtils.checkRootAccessMethod2()  == 0 )
           return true;
      return false;
     }

1
ฉันคิดว่าการตรวจจับรูทนั้นแบ่งออกเป็นสองประเภทซึ่งเปิดใช้งานคุณลักษณะที่ขึ้นอยู่กับรูทจากนั้นใช้มาตรการรักษาความปลอดภัยเพื่อพยายามลดปัญหาด้านความปลอดภัยด้วยโทรศัพท์ที่รูท สำหรับคุณสมบัติที่ต้องรูทฉันพบว่าคำตอบของเควินนั้นค่อนข้างแย่ ในบริบทของคำตอบนี้วิธีการเหล่านี้สมเหตุสมผลมากขึ้น แม้ว่าฉันจะเขียนวิธีที่ 2 เพื่อไม่ใช้อันไหนและทำซ้ำมากกว่าตัวแปรสภาพแวดล้อม PATH เพื่อค้นหาคำว่า "su" "ซึ่ง" ไม่รับประกันว่าจะอยู่ในโทรศัพท์
rsimp

คุณช่วยยกตัวอย่างวิธีการใช้รหัส c ในจาวานี้ได้ไหม
mrid

@mrid โปรดตรวจสอบวิธีการโทร JNI จาก Java บน Android
Alok Kulkarni

วิธีนี้ป้องกันการตรวจหารูทโดยใช้แอพ RootCloak มีเทคนิคการรูทพาสที่รู้จักซึ่งล้มเหลวในวิธีการเหล่านี้หรือไม่?
Nidhin

20

http://code.google.com/p/roottools/

หากคุณไม่ต้องการใช้ไฟล์ jarเพียงใช้รหัส:

public static boolean findBinary(String binaryName) {
        boolean found = false;
        if (!found) {
            String[] places = { "/sbin/", "/system/bin/", "/system/xbin/",
                    "/data/local/xbin/", "/data/local/bin/",
                    "/system/sd/xbin/", "/system/bin/failsafe/", "/data/local/" };
            for (String where : places) {
                if (new File(where + binaryName).exists()) {
                    found = true;

                    break;
                }
            }
        }
        return found;
    }

โปรแกรมจะพยายามค้นหาโฟลเดอร์ su:

private static boolean isRooted() {
        return findBinary("su");
    }

ตัวอย่าง:

if (isRooted()) {
   textView.setText("Device Rooted");

} else {
   textView.setText("Device Unrooted");
}

ขอบคุณ! ฉันใช้นี้เป็นcheckRootMethod4()กับคำตอบของเควิน
Sheharyar

1
ไม่ต้องเพิ่ม== trueบูลีนมันจะเพิ่มอะไรและดูไม่ดี
minipif

2
@smoothblue ทำไมล่ะ มันไม่ได้วางไข่กระบวนการใด ๆ เนื่องจากวิธีการแก้ปัญหาของ DevrimTuncer คือ
FD_

1
ความคิดที่ดีกว่าจะย้ำกว่าเส้นทางแทนฮาร์ดเข้ารหัสไดเรกทอรีเส้นทางปกติ
rsimp

1
ใช้if (isRooted())ตรวจสอบแทนที่จะเขียนจริงอย่างชัดเจน การติดตามรูปแบบการเขียนโค้ดดีกว่า
blueware

13

แทนที่จะใช้ isRootAvailable () คุณสามารถใช้ isAccessGiven () โดยตรงจากวิกิ RootTools :

if (RootTools.isAccessGiven()) {
    // your app has been granted root access
}

RootTools.isAccessGiven () ไม่เพียง แต่ตรวจสอบว่าอุปกรณ์ถูกรูทแล้วยังเรียกใช้ su สำหรับแอปของคุณร้องขอการอนุญาตและส่งคืนค่าจริงหากแอปของคุณได้รับสิทธิ์การใช้งานรูทสำเร็จ สิ่งนี้สามารถใช้เป็นการตรวจสอบครั้งแรกในแอปของคุณเพื่อให้แน่ใจว่าคุณจะได้รับสิทธิ์การเข้าถึงเมื่อคุณต้องการ

การอ้างอิง


แต่ผู้ใช้จะต้องให้สิทธิ์การเข้าถึงรูตใช่มั้ย ดังนั้นหากเป้าหมายของฉันคือหยุดแอปของฉันไม่ให้ทำงานหากอุปกรณ์ถูกรูทตัวเลือกของฉันจะถูก จำกัด จริงๆ
Nasz Njoka Sr.

11

บิวด์ที่ปรับเปลี่ยนบางตัวที่ใช้เพื่อตั้งค่าคุณสมบัติระบบ ro.modversionสำหรับวัตถุประสงค์นี้ สิ่งต่าง ๆ ดูเหมือนจะเคลื่อนไป; งานสร้างของฉันจาก TheDude ไม่กี่เดือนที่ผ่านมามีสิ่งนี้:

cmb@apollo:~$ adb -d shell getprop |grep build
[ro.build.id]: [CUPCAKE]
[ro.build.display.id]: [htc_dream-eng 1.5 CUPCAKE eng.TheDudeAbides.20090427.235325 test-keys]
[ro.build.version.incremental]: [eng.TheDude.2009027.235325]
[ro.build.version.sdk]: [3]
[ro.build.version.release]: [1.5]
[ro.build.date]: [Mon Apr 20 01:42:32 CDT 2009]
[ro.build.date.utc]: [1240209752]
[ro.build.type]: [eng]
[ro.build.user]: [TheDude]
[ro.build.host]: [ender]
[ro.build.tags]: [test-keys]
[ro.build.product]: [dream]
[ro.build.description]: [kila-user 1.1 PLAT-RC33 126986 ota-rel-keys,release-keys]
[ro.build.fingerprint]: [tmobile/kila/dream/trout:1.1/PLAT-RC33/126986:user/ota-rel-keys,release-keys]
[ro.build.changelist]: [17615# end build properties]

อีมูเลเตอร์จาก 1.5 SDK ในอีกทางหนึ่งเรียกใช้อิมเมจ 1.5 มีรูตน่าจะคล้ายกับAndroid Dev Phone 1 (ซึ่งคุณอาจต้องการอนุญาต) และมี:

cmb@apollo:~$ adb -e shell getprop |grep build
[ro.build.id]: [CUPCAKE]
[ro.build.display.id]: [sdk-eng 1.5 CUPCAKE 148875 test-keys]
[ro.build.version.incremental]: [148875]
[ro.build.version.sdk]: [3]
[ro.build.version.release]: [1.5]
[ro.build.date]: [Thu May 14 18:09:10 PDT 2009]
[ro.build.date.utc]: [1242349750]
[ro.build.type]: [eng]
[ro.build.user]: [android-build]
[ro.build.host]: [undroid16.mtv.corp.google.com]
[ro.build.tags]: [test-keys]
[ro.build.product]: [generic]
[ro.build.description]: [sdk-eng 1.5 CUPCAKE 148875 test-keys]
[ro.build.fingerprint]: [generic/sdk/generic/:1.5/CUPCAKE/148875:eng/test-keys]

สำหรับธุรกิจค้าปลีกฉันไม่มีมือหนึ่ง แต่การค้นหาที่หลากหลายภายใต้site:xda-developers.comนั้นเป็นข้อมูล นี่คือG1 ในเนเธอร์แลนด์คุณจะเห็นว่าro.build.tagsไม่มีtest-keysและฉันคิดว่านั่นน่าจะเป็นคุณสมบัติที่น่าเชื่อถือที่สุดที่จะใช้


ที่ดูน่าสนใจ แต่: ในขณะที่ emulator (และ ADP) อนุญาตให้รูทต่อ se พวกเขาไม่อนุญาตให้แอปพลิเคชันใช้งานเช่น: $ su app_29 $ su su: uid 10029 ไม่อนุญาตให้ su
miracle2k

อ่าฉันคิดว่าพวกเขาจะไม่ ... คุณสามารถรวมเข้ากับเช็ค ro.build.host (ไม่ใช่) ลงท้ายด้วย google.com ถ้าพวกเขาเป็นคนเดียวที่มีปุ่มทดสอบ แต่บล็อก su โดยไม่ต้อง ถามผู้ใช้ ขึ้นอยู่กับสิ่งที่โฮสต์สร้างสำหรับอุปกรณ์ใหม่สิ่งที่ไม่ใช่โทรศัพท์ ... ไม่ใช่เรื่องง่าย
คริสบอยล์

11

RootBeerเป็นรูทการตรวจสอบห้องสมุด Android โดย Scott และ Matthew มันใช้การตรวจสอบต่าง ๆ เพื่อระบุว่าอุปกรณ์ถูกรูทหรือไม่

ตรวจสอบ Java

  • CheckRootManagementApps

  • CheckPotentiallyDangerousAppss

  • CheckRootCloakingApps

  • CheckTestKeys

  • checkForDangerousProps

  • checkForBusyBoxBinary

  • checkForSuBinary

  • checkSuExists

  • checkForRWSystem

ตรวจสอบพื้นเมือง

เราโทรหาผู้ตรวจสอบรูทของเราเพื่อเรียกใช้การตรวจสอบของตัวเอง โดยทั่วไปการตรวจสอบแบบดั้งเดิมนั้นยากกว่าการปิดบังดังนั้นแอปพลิเคชันแบบปิดบังบางส่วนจะบล็อกการโหลดไลบรารีดั้งเดิมที่มีคำสำคัญบางคำ

  • checkForSuBinary

8

ฉันขอแนะนำให้ใช้โค้ดเนทีฟสำหรับการตรวจหารูท นี่คือตัวอย่างการทำงานที่สมบูรณ์

ป้อนคำอธิบายรูปภาพที่นี่

เครื่องห่อ JAVA :

package com.kozhevin.rootchecks.util;


import android.support.annotation.NonNull;

import com.kozhevin.rootchecks.BuildConfig;

public class MeatGrinder {
    private final static String LIB_NAME = "native-lib";
    private static boolean isLoaded;
    private static boolean isUnderTest = false;

    private MeatGrinder() {

    }

    public boolean isLibraryLoaded() {
        if (isLoaded) {
            return true;
        }
        try {
            if(isUnderTest) {
                throw new UnsatisfiedLinkError("under test");
            }
            System.loadLibrary(LIB_NAME);
            isLoaded = true;
        } catch (UnsatisfiedLinkError e) {
            if (BuildConfig.DEBUG) {
                e.printStackTrace();
            }
        }
        return isLoaded;
    }

    public native boolean isDetectedDevKeys();

    public native boolean isDetectedTestKeys();

    public native boolean isNotFoundReleaseKeys();

    public native boolean isFoundDangerousProps();

    public native boolean isPermissiveSelinux();

    public native boolean isSuExists();

    public native boolean isAccessedSuperuserApk();

    public native boolean isFoundSuBinary();

    public native boolean isFoundBusyboxBinary();

    public native boolean isFoundXposed();

    public native boolean isFoundResetprop();

    public native boolean isFoundWrongPathPermission();

    public native boolean isFoundHooks();

    @NonNull
    public static MeatGrinder getInstance() {
        return InstanceHolder.INSTANCE;
    }

    private static class InstanceHolder {
        private static final MeatGrinder INSTANCE = new MeatGrinder();
    }
}

เครื่องห่อหุ้ม JNI (native-lib.c) :

JNIEXPORT jboolean JNICALL
Java_com_kozhevin_rootchecks_util_MeatGrinder_isDetectedTestKeys(
        JNIEnv *env,
        jobject  this ) {

    return (jboolean) isDetectedTestKeys();
}

JNIEXPORT jboolean JNICALL
Java_com_kozhevin_rootchecks_util_MeatGrinder_isDetectedDevKeys(
        JNIEnv *env,
        jobject  this ) {

    return (jboolean) isDetectedDevKeys();
}

JNIEXPORT jboolean JNICALL
Java_com_kozhevin_rootchecks_util_MeatGrinder_isNotFoundReleaseKeys(
        JNIEnv *env,
        jobject  this ) {

    return (jboolean) isNotFoundReleaseKeys();
}

JNIEXPORT jboolean JNICALL
Java_com_kozhevin_rootchecks_util_MeatGrinder_isFoundDangerousProps(
        JNIEnv *env,
        jobject  this ) {

    return (jboolean) isFoundDangerousProps();
}

JNIEXPORT jboolean JNICALL
Java_com_kozhevin_rootchecks_util_MeatGrinder_isPermissiveSelinux(
        JNIEnv *env,
        jobject  this ) {

    return (jboolean) isPermissiveSelinux();
}

JNIEXPORT jboolean JNICALL
Java_com_kozhevin_rootchecks_util_MeatGrinder_isSuExists(
        JNIEnv *env,
        jobject  this ) {

    return (jboolean) isSuExists();
}

JNIEXPORT jboolean JNICALL
Java_com_kozhevin_rootchecks_util_MeatGrinder_isAccessedSuperuserApk(
        JNIEnv *env,
        jobject  this ) {

    return (jboolean) isAccessedSuperuserApk();
}

JNIEXPORT jboolean JNICALL
Java_com_kozhevin_rootchecks_util_MeatGrinder_isFoundSuBinary(
        JNIEnv *env,
        jobject  this ) {

    return (jboolean) isFoundSuBinary();
}

JNIEXPORT jboolean JNICALL
Java_com_kozhevin_rootchecks_util_MeatGrinder_isFoundBusyboxBinary(
        JNIEnv *env,
        jobject  this ) {

    return (jboolean) isFoundBusyboxBinary();
}


JNIEXPORT jboolean JNICALL
Java_com_kozhevin_rootchecks_util_MeatGrinder_isFoundXposed(
        JNIEnv *env,
        jobject  this ) {

    return (jboolean) isFoundXposed();
}

JNIEXPORT jboolean JNICALL
Java_com_kozhevin_rootchecks_util_MeatGrinder_isFoundResetprop(
        JNIEnv *env,
        jobject  this ) {

    return (jboolean) isFoundResetprop();
}

JNIEXPORT jboolean JNICALL
Java_com_kozhevin_rootchecks_util_MeatGrinder_isFoundWrongPathPermission(
        JNIEnv *env,
        jobject  this ) {

    return (jboolean) isFoundWrongPathPermission();
}

JNIEXPORT jboolean JNICALL
Java_com_kozhevin_rootchecks_util_MeatGrinder_isFoundHooks(
        JNIEnv *env,
        jobject  this ) {

    return (jboolean) isFoundHooks();
}

ค่าคงที่:

// Comma-separated tags describing the build, like= "unsigned,debug".
const char *const ANDROID_OS_BUILD_TAGS = "ro.build.tags";

// A string that uniquely identifies this build. 'BRAND/PRODUCT/DEVICE:RELEASE/ID/VERSION.INCREMENTAL:TYPE/TAGS'.
const char *const ANDROID_OS_BUILD_FINGERPRINT = "ro.build.fingerprint";

const char *const ANDROID_OS_SECURE = "ro.secure";

const char *const ANDROID_OS_DEBUGGABLE = "ro.debuggable";
const char *const ANDROID_OS_SYS_INITD = "sys.initd";
const char *const ANDROID_OS_BUILD_SELINUX = "ro.build.selinux";
//see https://android.googlesource.com/platform/system/core/+/master/adb/services.cpp#86
const char *const SERVICE_ADB_ROOT = "service.adb.root";

const char * const MG_SU_PATH[] = {
        "/data/local/",
        "/data/local/bin/",
        "/data/local/xbin/",
        "/sbin/",
        "/system/bin/",
        "/system/bin/.ext/",
        "/system/bin/failsafe/",
        "/system/sd/xbin/",
        "/su/xbin/",
        "/su/bin/",
        "/magisk/.core/bin/",
        "/system/usr/we-need-root/",
        "/system/xbin/",
        0
};

const char * const MG_EXPOSED_FILES[] = {
        "/system/lib/libxposed_art.so",
        "/system/lib64/libxposed_art.so",
        "/system/xposed.prop",
        "/cache/recovery/xposed.zip",
        "/system/framework/XposedBridge.jar",
        "/system/bin/app_process64_xposed",
        "/system/bin/app_process32_xposed",
        "/magisk/xposed/system/lib/libsigchain.so",
        "/magisk/xposed/system/lib/libart.so",
        "/magisk/xposed/system/lib/libart-disassembler.so",
        "/magisk/xposed/system/lib/libart-compiler.so",
        "/system/bin/app_process32_orig",
        "/system/bin/app_process64_orig",
        0
};

const char * const MG_READ_ONLY_PATH[] = {
        "/system",
        "/system/bin",
        "/system/sbin",
        "/system/xbin",
        "/vendor/bin",
        "/sbin",
        "/etc",
        0
};

การตรวจหารูทจากรหัสเนทีฟ:

struct mntent *getMntent(FILE *fp, struct mntent *e, char *buf, int buf_len) {

    while (fgets(buf, buf_len, fp) != NULL) {
        // Entries look like "/dev/block/vda /system ext4 ro,seclabel,relatime,data=ordered 0 0".
        // That is: mnt_fsname mnt_dir mnt_type mnt_opts mnt_freq mnt_passno.
        int fsname0, fsname1, dir0, dir1, type0, type1, opts0, opts1;
        if (sscanf(buf, " %n%*s%n %n%*s%n %n%*s%n %n%*s%n %d %d",
                   &fsname0, &fsname1, &dir0, &dir1, &type0, &type1, &opts0, &opts1,
                   &e->mnt_freq, &e->mnt_passno) == 2) {
            e->mnt_fsname = &buf[fsname0];
            buf[fsname1] = '\0';
            e->mnt_dir = &buf[dir0];
            buf[dir1] = '\0';
            e->mnt_type = &buf[type0];
            buf[type1] = '\0';
            e->mnt_opts = &buf[opts0];
            buf[opts1] = '\0';
            return e;
        }
    }
    return NULL;
}


bool isPresentMntOpt(const struct mntent *pMnt, const char *pOpt) {
    char *token = pMnt->mnt_opts;
    const char *end = pMnt->mnt_opts + strlen(pMnt->mnt_opts);
    const size_t optLen = strlen(pOpt);
    while (token != NULL) {
        const char *tokenEnd = token + optLen;
        if (tokenEnd > end) break;
        if (memcmp(token, pOpt, optLen) == 0 &&
            (*tokenEnd == '\0' || *tokenEnd == ',' || *tokenEnd == '=')) {
            return true;
        }
        token = strchr(token, ',');
        if (token != NULL) {
            token++;
        }
    }
    return false;
}

static char *concat2str(const char *pString1, const char *pString2) {
    char *result;
    size_t lengthBuffer = 0;

    lengthBuffer = strlen(pString1) +
                   strlen(pString2) + 1;
    result = malloc(lengthBuffer);
    if (result == NULL) {
        GR_LOGW("malloc failed\n");
        return NULL;
    }
    memset(result, 0, lengthBuffer);
    strcpy(result, pString1);
    strcat(result, pString2);
    return result;
}

static bool
isBadPropertyState(const char *key, const char *badValue, bool isObligatoryProperty, bool isExact) {
    if (badValue == NULL) {
        GR_LOGE("badValue may not be NULL");
        return false;
    }
    if (key == NULL) {
        GR_LOGE("key may not be NULL");
        return false;
    }
    char value[PROP_VALUE_MAX + 1];
    int length = __system_property_get(key, value);
    bool result = false;
    /* A length 0 value indicates that the property is not defined */
    if (length > 0) {
        GR_LOGI("property:[%s]==[%s]", key, value);
        if (isExact) {
            if (strcmp(value, badValue) == 0) {
                GR_LOGW("bad value[%s] equals to [%s] in the property [%s]", value, badValue, key);
                result = true;
            }
        } else {
            if (strlen(value) >= strlen(badValue) && strstr(value, badValue) != NULL) {
                GR_LOGW("bad value[%s] found in [%s] in the property [%s]", value, badValue, key);
                result = true;
            }
        }
    } else {
        GR_LOGI("[%s] property not found", key);
        if (isObligatoryProperty) {
            result = true;
        }
    }
    return result;
}

bool isDetectedTestKeys() {
    const char *TEST_KEYS_VALUE = "test-keys";
    return isBadPropertyState(ANDROID_OS_BUILD_TAGS, TEST_KEYS_VALUE, true, false);
}

bool isDetectedDevKeys() {
    const char *DEV_KEYS_VALUE = "dev-keys";
    return isBadPropertyState(ANDROID_OS_BUILD_TAGS, DEV_KEYS_VALUE, true, false);
}

bool isNotFoundReleaseKeys() {
    const char *RELEASE_KEYS_VALUE = "release-keys";
    return !isBadPropertyState(ANDROID_OS_BUILD_TAGS, RELEASE_KEYS_VALUE, false, true);
}

bool isFoundWrongPathPermission() {

    bool result = false;
    FILE *file = fopen("/proc/mounts", "r");
    char mntent_strings[BUFSIZ];
    if (file == NULL) {
        GR_LOGE("setmntent");
        return result;
    }

    struct mntent ent = {0};
    while (NULL != getMntent(file, &ent, mntent_strings, sizeof(mntent_strings))) {
        for (size_t i = 0; MG_READ_ONLY_PATH[i]; i++) {
            if (strcmp((&ent)->mnt_dir, MG_READ_ONLY_PATH[i]) == 0 &&
                isPresentMntOpt(&ent, "rw")) {
                GR_LOGI("%s %s %s %s\n", (&ent)->mnt_fsname, (&ent)->mnt_dir, (&ent)->mnt_opts,
                        (&ent)->mnt_type);
                result = true;
                break;
            }
        }
        memset(&ent, 0, sizeof(ent));
    }
    fclose(file);
    return result;
}


bool isFoundDangerousProps() {
    const char *BAD_DEBUGGABLE_VALUE = "1";
    const char *BAD_SECURE_VALUE = "0";
    const char *BAD_SYS_INITD_VALUE = "1";
    const char *BAD_SERVICE_ADB_ROOT_VALUE = "1";

    bool result = isBadPropertyState(ANDROID_OS_DEBUGGABLE, BAD_DEBUGGABLE_VALUE, true, true) ||
                  isBadPropertyState(SERVICE_ADB_ROOT, BAD_SERVICE_ADB_ROOT_VALUE, false, true) ||
                  isBadPropertyState(ANDROID_OS_SECURE, BAD_SECURE_VALUE, true, true) ||
                  isBadPropertyState(ANDROID_OS_SYS_INITD, BAD_SYS_INITD_VALUE, false, true);

    return result;
}

bool isPermissiveSelinux() {
    const char *BAD_VALUE = "0";
    return isBadPropertyState(ANDROID_OS_BUILD_SELINUX, BAD_VALUE, false, false);
}

bool isSuExists() {
    char buf[BUFSIZ];
    char *str = NULL;
    char *temp = NULL;
    size_t size = 1;  // start with size of 1 to make room for null terminator
    size_t strlength;

    FILE *pipe = popen("which su", "r");
    if (pipe == NULL) {
        GR_LOGI("pipe is null");
        return false;
    }

    while (fgets(buf, sizeof(buf), pipe) != NULL) {
        strlength = strlen(buf);
        temp = realloc(str, size + strlength);  // allocate room for the buf that gets appended
        if (temp == NULL) {
            // allocation error
            GR_LOGE("Error (re)allocating memory");
            pclose(pipe);
            if (str != NULL) {
                free(str);
            }
            return false;
        } else {
            str = temp;
        }
        strcpy(str + size - 1, buf);
        size += strlength;
    }
    pclose(pipe);
    GR_LOGW("A size of the result from pipe is [%zu], result:\n [%s] ", size, str);
    if (str != NULL) {
        free(str);
    }
    return size > 1 ? true : false;
}

static bool isAccessedFile(const char *path) {
    int result = access(path, F_OK);
    GR_LOGV("[%s] has been accessed with result: [%d]", path, result);
    return result == 0 ? true : false;
}

static bool isFoundBinaryFromArray(const char *const *array, const char *binary) {
    for (size_t i = 0; array[i]; ++i) {
        char *checkedPath = concat2str(array[i], binary);
        if (checkedPath == NULL) { // malloc failed
            return false;
        }
        bool result = isAccessedFile(checkedPath);
        free(checkedPath);
        if (result) {
            return result;
        }
    }
    return false;
}

bool isAccessedSuperuserApk() {
    return isAccessedFile("/system/app/Superuser.apk");
}

bool isFoundResetprop() {
    return isAccessedFile("/data/magisk/resetprop");
}

bool isFoundSuBinary() {
    return isFoundBinaryFromArray(MG_SU_PATH, "su");
}

bool isFoundBusyboxBinary() {
    return isFoundBinaryFromArray(MG_SU_PATH, "busybox");
}

bool isFoundXposed() {
    for (size_t i = 0; MG_EXPOSED_FILES[i]; ++i) {
        bool result = isAccessedFile(MG_EXPOSED_FILES[i]);
        if (result) {
            return result;
        }
    }
    return false;
}

bool isFoundHooks() {
    bool result = false;
    pid_t pid = getpid();
    char maps_file_name[512];
    sprintf(maps_file_name, "/proc/%d/maps", pid);
    GR_LOGI("try to open [%s]", maps_file_name);
    const size_t line_size = BUFSIZ;
    char *line = malloc(line_size);
    if (line == NULL) {
        return result;
    }
    FILE *fp = fopen(maps_file_name, "r");
    if (fp == NULL) {
        free(line);
        return result;
    }
    memset(line, 0, line_size);
    const char *substrate = "com.saurik.substrate";
    const char *xposed = "XposedBridge.jar";
    while (fgets(line, line_size, fp) != NULL) {
        const size_t real_line_size = strlen(line);
        if ((real_line_size >= strlen(substrate) && strstr(line, substrate) != NULL) ||
            (real_line_size >= strlen(xposed) && strstr(line, xposed) != NULL)) {
            GR_LOGI("found in [%s]: [%s]", maps_file_name, line);
            result = true;
            break;
        }
    }
    free(line);
    fclose(fp);
    return result;
}

4
เครื่องมือที่ยอดเยี่ยม Dima ขอบคุณมาก. มันยังจับ magisk
ผู้เชี่ยวชาญ

นี่คือเรื่องจริง
Vahid Amiri

@klutch มีลิงก์ไปยังตัวอย่างการทำงาน (github) ที่บรรทัดแรกของโพสต์ของฉัน
Dima Kozhevin

7

นี่คือรหัสของฉันตามคำตอบบางส่วนที่นี่:

 /**
   * Checks if the phone is rooted.
   * 
   * @return <code>true</code> if the phone is rooted, <code>false</code>
   * otherwise.
   */
  public static boolean isPhoneRooted() {

    // get from build info
    String buildTags = android.os.Build.TAGS;
    if (buildTags != null && buildTags.contains("test-keys")) {
      return true;
    }

    // check if /system/app/Superuser.apk is present
    try {
      File file = new File("/system/app/Superuser.apk");
      if (file.exists()) {
        return true;
      }
    } catch (Throwable e1) {
      // ignore
    }

    return false;
  }

7

ถัดจากคำตอบของ @Kevins ฉันเพิ่งค้นพบเมื่อใช้ระบบของเขาว่า Nexus 7.1 กำลังกลับมาfalseสำหรับทั้งสามวิธี - ไม่มีwhichคำสั่งไม่test-keysและSuperSUไม่ได้ติดตั้งใน/system/appไม่ได้รับการติดตั้งใน

ฉันเพิ่มสิ่งนี้:

public static boolean checkRootMethod4(Context context) {
    return isPackageInstalled("eu.chainfire.supersu", context);     
}

private static boolean isPackageInstalled(String packagename, Context context) {
    PackageManager pm = context.getPackageManager();
    try {
        pm.getPackageInfo(packagename, PackageManager.GET_ACTIVITIES);
        return true;
    } catch (NameNotFoundException e) {
        return false;
    }
}

สิ่งนี้มีประโยชน์น้อยกว่าเล็กน้อยในบางสถานการณ์ (ถ้าคุณต้องการการเข้าถึงรูทที่รับประกัน) เนื่องจาก SuperSU สามารถติดตั้งบนอุปกรณ์ที่ไม่มีการเข้าถึง SU อย่างสมบูรณ์

อย่างไรก็ตามเนื่องจากเป็นไปได้ที่จะมี SuperSU ติดตั้งและใช้งานได้แต่ไม่อยู่ใน/system/appไดเรกทอรีกรณีพิเศษนี้จะทำการรูท (haha) จากกรณีดังกล่าว


นี่ไม่ใช่คำตอบที่ดีเนื่องจากคุณมีแพ็คเกจรากอื่น ๆ ที่สามารถติดตั้งในอุปกรณ์ของคุณได้ การเข้ารหัสฮาร์ดแพคเกจแอปอื่น ๆ จะรุนแรงเท่าที่คุณคาดไม่ถึงและแสดงรายการทั้งหมด
blueware

5
    public static boolean isRootAvailable(){
            Process p = null;
            try{
               p = Runtime.getRuntime().exec(new String[] {"su"});
               writeCommandToConsole(p,"exit 0");
               int result = p.waitFor();
               if(result != 0)
                   throw new Exception("Root check result with exit command " + result);
               return true;
            } catch (IOException e) {
                Log.e(LOG_TAG, "Su executable is not available ", e);
            } catch (Exception e) {
                Log.e(LOG_TAG, "Root is unavailable ", e);
            }finally {
                if(p != null)
                    p.destroy();
            }
            return false;
        }
 private static String writeCommandToConsole(Process proc, String command, boolean ignoreError) throws Exception{
            byte[] tmpArray = new byte[1024];
            proc.getOutputStream().write((command + "\n").getBytes());
            proc.getOutputStream().flush();
            int bytesRead = 0;
            if(proc.getErrorStream().available() > 0){
                if((bytesRead = proc.getErrorStream().read(tmpArray)) > 1){
                    Log.e(LOG_TAG,new String(tmpArray,0,bytesRead));
                    if(!ignoreError)
                        throw new Exception(new String(tmpArray,0,bytesRead));
                }
            }
            if(proc.getInputStream().available() > 0){
                bytesRead = proc.getInputStream().read(tmpArray);
                Log.i(LOG_TAG, new String(tmpArray,0,bytesRead));
            }
            return new String(tmpArray);
        }

4

แนวคิดเพิ่มเติมสองประการหากคุณต้องการตรวจสอบว่าอุปกรณ์สามารถรูทได้จากแอปของคุณหรือไม่:

  1. ตรวจสอบไบนารีที่มีอยู่ของ 'su': run "which su" Runtime.getRuntime().exec()
  2. มองหา SuperUser.apk ใน/system/app/Superuser.apkตำแหน่ง

3

การใช้ C ++ กับ ndk เป็นวิธีที่ดีที่สุดในการตรวจสอบรูทแม้ว่าผู้ใช้จะใช้แอปพลิเคชันที่ซ่อนรูทของเขาเช่น RootCloak ฉันทดสอบโค้ดนี้ด้วย RootCloak และฉันสามารถตรวจจับรูทได้แม้ว่าผู้ใช้จะพยายามซ่อนมัน ดังนั้นไฟล์ cpp ของคุณต้องการ:

#include <jni.h>
#include <string>


/**
 *
 * function that checks for the su binary files and operates even if 
 * root cloak is installed
 * @return integer 1: device is rooted, 0: device is not 
 *rooted
*/
extern "C"
JNIEXPORT int JNICALL


Java_com_example_user_root_1native_rootFunction(JNIEnv *env,jobject thiz){
const char *paths[] ={"/system/app/Superuser.apk", "/sbin/su", "/system/bin/su",
                      "/system/xbin/su", "/data/local/xbin/su", "/data/local/bin/su", "/system/sd/xbin/su",
                      "/system/bin/failsafe/su", "/data/local/su", "/su/bin/su"};

int counter =0;
while (counter<9){
    if(FILE *file = fopen(paths[counter],"r")){
        fclose(file);
        return 1;
    }
    counter++;
}
return 0;
}

และคุณจะเรียกใช้ฟังก์ชันจากโค้ด java ของคุณดังต่อไปนี้

public class Root_detect {



   /**
    *
    * function that calls a native function to check if the device is 
    *rooted or not
    * @return boolean: true if the device is rooted, false if the 
    *device is not rooted
   */
   public boolean check_rooted(){

        int checker = rootFunction();

        if(checker==1){
           return true;
        }else {
           return false;
        }
   }
   static {
    System.loadLibrary("cpp-root-lib");//name of your cpp file
   }

   public native int rootFunction();
}

1
if [[ "`adb shell which su | grep -io "permission denied"`" != "permission denied" ]]; then
   echo "Yes. Rooted device."
 else
   echo "No. Device not rooted. Only limited tasks can be performed. Done."
    zenity --warning --title="Device Not Rooted" --text="The connected Android Device is <b>NOT ROOTED</b>. Only limited tasks can be performed." --no-wrap
fi

1

มีSafety Net Attestation APIของบริการ Google playซึ่งเราสามารถประเมินอุปกรณ์และตรวจสอบว่ามีการรูท / เปลี่ยนแปลงหรือไม่

โปรดทำตามคำตอบเพื่อจัดการกับอุปกรณ์ที่รูท:
https://stackoverflow.com/a/58304556/3908895


1

ลืมทุกสิ่งที่ตรวจจับแอพรูทและ su ไบนารี ตรวจสอบกระบวนการรูตดีมอน สิ่งนี้สามารถทำได้จากเทอร์มินัลและคุณสามารถเรียกใช้คำสั่งเทอร์มินัลภายในแอพ ลองซับเดี่ยวนี้

if [ ! -z "$(/system/bin/ps -A | grep -v grep | grep -c daemonsu)" ]; then echo "device is rooted"; else echo "device is not rooted"; fi

คุณไม่จำเป็นต้องได้รับอนุญาตรูทเพื่อที่จะทำสิ่งนี้ให้สำเร็จ


0

แน่นอนว่ามันเป็นคำถามที่น่าสนใจและยังไม่มีใครได้รับรางวัล ฉันใช้รหัสต่อไปนี้:

  boolean isRooted() {
      try {
                ServerSocket ss = new ServerSocket(81);
                ss.close();
                                    return true;
            } catch (Exception e) {
                // not sure
            }
    return false;
  }

รหัสไม่สามารถกันกระสุนได้เนื่องจากเครือข่ายไม่สามารถใช้งานได้เพื่อให้คุณได้รับการยกเว้น หากวิธีนี้ให้ผลเป็นจริงคุณสามารถมั่นใจได้ 99% มิฉะนั้นจะเหลือเพียง 50% เท่านั้น การอนุญาตเครือข่ายอาจทำให้วิธีการแก้ปัญหาเสีย


ฉันทดสอบสิ่งนี้และมันไม่ได้กลับมาเป็นจริงกับอุปกรณ์ที่รูทของฉัน
เคล็ดลับ

เป็นที่น่าสนใจที่จะดูว่าคุณได้รับข้อยกเว้นประเภทใด คุณอาจได้รับข้อยกเว้นของพอร์ตที่มีอยู่แล้วอย่างไรก็ตามหากคุณไม่สามารถสร้างเซิร์ฟเวอร์พอร์ตในระดับต่ำกว่า 1024 ได้ก็จะลดค่าของการรูทเนื่องจากยังมีข้อ จำกัด บางประการ
Singagirl

-1

ใช้ห้องสมุดของฉันที่รูทบ็อกซ์มันค่อนข้างง่าย ตรวจสอบรหัสที่ต้องการด้านล่าง:

    //Pass true to <Shell>.start(...) call to run as superuser
    Shell shell = null;
    try {
            shell = Shell.start(true);
    } catch (IOException exception) {
            exception.printStackTrace();
    }
    if (shell == null)
            // We failed to execute su binary
            return;
    if (shell.isRoot()) {
            // Verified running as uid 0 (root), can continue with commands
            ...
    } else
            throw Exception("Unable to gain root access. Make sure you pressed Allow/Grant in superuser prompt.");
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.