Angler – Hack The Box – @lautarovculic

Angle hackthebox

Difficult: Medium

Category: Mobile

OS: Android

Description: The skilled fisherman used his full strength and expertise to hook the fish. Can you beat him and set the fish free?

First we’ll download the .apk file. The pass is hackthebox

And then decompile with apktool ☝️🤓

				
					apktool d Angler.apk
				
			

The SDK version is 32, then we can use an Android 12 (SDK 31)

Let’s try install the .apk

				
					adb install -r Angler.apk
				
			

Also, let’s check the source code with jadx-gui

This is the MainActivity.java

				
					public class MainActivity extends g {
    public static final /* synthetic */ int A = 0;
    public TextView v;

    /* renamed from: w, reason: collision with root package name */
    public TextView f1753w;

    /* renamed from: x, reason: collision with root package name */
    public ImageView f1754x;

    /* renamed from: y, reason: collision with root package name */
    public String f1755y = "@|uqcu0t\u007f~7d0{y||0}u1\u001aY7||0du||0i\u007fe0gxubu0dxu0v|qw0yc>";

    /* renamed from: z, reason: collision with root package name */
    public final a f1756z = new a();

    /* loaded from: classes.dex */
    public class a extends BroadcastReceiver {
        public a() {
        }

        @Override // android.content.BroadcastReceiver
        public final void onReceive(Context context, Intent intent) {
            PrintStream printStream;
            String str;
            if (intent.getStringExtra("Is_on").equals("yes")) {
                MainActivity mainActivity = MainActivity.this;
                int i3 = MainActivity.A;
                Window window = mainActivity.getWindow();
                window.addFlags(Integer.MIN_VALUE);
                window.clearFlags(67108864);
                window.setStatusBarColor(mainActivity.getResources().getColor(R.color.purple_200));
                d.a r3 = mainActivity.r();
                Objects.requireNonNull(r3);
                r3.b(new ColorDrawable(mainActivity.getResources().getColor(R.color.teal_700)));
                mainActivity.f1754x.setImageResource(R.drawable.please);
                mainActivity.v.setTextColor(mainActivity.getResources().getColor(R.color.purple_200));
                mainActivity.v.setText("1%");
                mainActivity.f1753w.setText(d.d(mainActivity.f1755y));
                Toast.makeText(context, "Look me inside", 1).show();
                printStream = System.out;
                str = MainActivity.this.getInfo(d.d("XDR"));
            } else {
                printStream = System.out;
                str = "I am Strong, no one can defeat me";
            }
            printStream.println(str);
        }
    }

    static {
        System.loadLibrary("angler");
    }

    public native String getInfo(String str);

    @Override // androidx.fragment.app.p, androidx.activity.ComponentActivity, w.g, android.app.Activity
    public final void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        setContentView(R.layout.activity_main);
        this.v = (TextView) findViewById(R.id.textView2);
        this.f1753w = (TextView) findViewById(R.id.textView);
        this.f1754x = (ImageView) findViewById(R.id.imageView);
        registerReceiver(this.f1756z, new IntentFilter("android.intent.action.BATTERY_LOW"));
    }
}
				
			

Here is a Broadcast receiver that expected an extra stringIs_on” “yes” in the android.intent.action.BATTERY_LOW

				
					    public final void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        setContentView(R.layout.activity_main);
        this.v = (TextView) findViewById(R.id.textView2);
        this.f1753w = (TextView) findViewById(R.id.textView);
        this.f1754x = (ImageView) findViewById(R.id.imageView);
        registerReceiver(this.f1756z, new IntentFilter("android.intent.action.BATTERY_LOW"));
    }
				
			

If this string is set as yes, then, proceed with this code

				
					            if (intent.getStringExtra("Is_on").equals("yes")) {
                MainActivity mainActivity = MainActivity.this;
                int i3 = MainActivity.A;
                Window window = mainActivity.getWindow();
                window.addFlags(Integer.MIN_VALUE);
                window.clearFlags(67108864);
                window.setStatusBarColor(mainActivity.getResources().getColor(R.color.purple_200));
                d.a r3 = mainActivity.r();
                Objects.requireNonNull(r3);
                r3.b(new ColorDrawable(mainActivity.getResources().getColor(R.color.teal_700)));
                mainActivity.f1754x.setImageResource(R.drawable.please);
                mainActivity.v.setTextColor(mainActivity.getResources().getColor(R.color.purple_200));
                mainActivity.v.setText("1%");
                mainActivity.f1753w.setText(d.d(mainActivity.f1755y));
                Toast.makeText(context, "Look me inside", 1).show();
                printStream = System.out;
                str = MainActivity.this.getInfo(d.d("XDR"));
				
			

Then, we can use adb for launch this activity manager.

				
					sudo adb shell am broadcast -a android.intent.action.BATTERY_LOW --es Is_on yes
				
			

Output:

				
					Broadcasting: Intent { act=android.intent.action.BATTERY_LOW flg=0x400000 (has extras) }
Broadcast completed: result=0
				
			

Result:

am

Activity manager

broadcast

Send a broadcast message

-a

Intent action

android.intent.action.BATTERY_LOW

The intent action

—es

Extra string

Now, in this point, the app will execute this piece of code:

				
					str = MainActivity.this.getInfo(d.d("XDR"));
				
			

That is

				
					static {
        System.loadLibrary("angler");
}
public native String getInfo(String str);
				
			

Because getInfo(); is an native method in android.

And the info is the content of d.d(“XDR”), where in the static code, I don’t found any interesting.

And the method is compiled in C, then, probably we need play with some debugger for an Dynamic Analysis.

But before, we need know what arch of proc we are using, for that you can run

				
					adb shell getprop ro.product.cpu.abi
				
			

And, in my case is x86_64 then, I’ll use Angler/lib/x86_64/libangler.so

It’s time for use ghidra and analyze

And we can see the function Java_com_example_angler_MainActivity_getInfo

Where if inspect the code we can see that getInfo calls two functions, illusion and ne.

illusion function

				
					void illusion(char *param_1)

{
  int iVar1;
  long in_FS_OFFSET;
  byte local_58 [16];
  void *local_48;
  basic_string<> local_40 [16];
  void *local_30;
  long local_28;
  
  local_28 = *(long *)(in_FS_OFFSET + 0x28);
  iVar1 = 100;
  do {
    std::__ndk1::basic_string<>::basic_string<>(local_40,"HTB{");
                    /* try { // try from 00148970 to 0014897a has its CatchHandler @ 001489cd */
    a((basic_string)local_58);
    if ((local_58[0] & 1) != 0) {
      operator.delete(local_48);
    }
    if (((byte)local_40[0] & 1) != 0) {
      operator.delete(local_30);
    }
    iVar1 = iVar1 + -1;
  } while (iVar1 != 0);
  if (*(long *)(in_FS_OFFSET + 0x28) != local_28) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return;
}
				
			

Here we can see HTB{ like the start of the flag 🥺 but we need keep analyzing.

ne function

				
					a lot of text
				
			

But we can do focus on

				
					  iVar3 = strcmp(in_RSI,(char *)pbVar10);
  if (iVar3 == 0) {
    std::__ndk1::basic_string<>::basic_string<>((basic_string<> *)param_1,"You found the flag");
  }
  else {
                    /* try { // try from 00149d0b to 00149d8e has its CatchHandler @ 00149dbb */
    std::__ndk1::basic_string<>::basic_string<>
              ((basic_string<> *)param_1,"I am not here, I am there");
  }
				
			

This methods have the strcmp (String compare)

That if the strings are equals, then You found the flag 😎

else, I am not here 😰, I am there 🔝

So, let’s keep this code

				
					frida -U Angler
				
			

Then for list exports and imports We can use the Module Enumerate

				
					[Pixel 3 XL::Angler ]->Module.enumerateImports("libangler.so")
[Pixel 3 XL::Angler ]->Module.enumerateImports("libangler.so")
				
			

Copy and save the output in two json file (exp/imp).json

I just copy the export content all that my terminal allowed me, then.

Searching for the illusion function in exp.json

				
					cat exp.json | grep illusion -n
				
			

Output

				
					6420:        "name": "_Z8illusionPKc"
				
			

But, we know the format of the json then

				
					cat exp.json | grep illusion -A 1 -B 1
				
			

-A 1 and -B 1 for grep 1 line above and 1 line below

				
					"address": "0x77888e94a930",
"name": "_Z8illusionPKc",
"type": "function"
				
			

The name is _Z8illusionPKc

And for strcmp

				
					cat imp.json | grep strcmp -A 1 -B 2
				
			
				
					"address": "0x778ba02582d0",
"module": "/apex/com.android.runtime/lib64/bionic/libc.so",
"name": "strcmp",
"type": "function"
				
			

Now we need hook the native library methods

We can use the Interceptor methods of frida for intercept the strcmp calls.

Here’s an script.js

				
					// Find the module containing strcmp
var libangler = Module.findBaseAddress("libangler.so");

// Find the address of strcmp within libangler.so
var strcmpPtr = Module.findExportByName(null, "strcmp");

// Intercept strcmp calls
Interceptor.attach(strcmpPtr, {
    onEnter: function(args) {
        // Get the compared strings
        var str1 = Memory.readUtf8String(args[0]);
        var str2 = Memory.readUtf8String(args[1]);

        // Log the compared strings
        console.log("strcmp called with strings:", str1, str2);
    }
});
				
			

Then

				
					frida -U -l script.js Angler
				
			

And run again the broadcast message of the start.

				
					sudo adb shell am broadcast -a android.intent.action.BATTERY_LOW --es Is_on yes
				
			

And

Just parse the hexcode

And get the flag 🥳

I hope you found it useful (:

Leave a Reply

Your email address will not be published. Required fields are marked *