https://nproject.io/blog/juicessh-give-me-back-my-pro-features/ nproject * nproject * Accueil * NProject * Beasts of wanderia * Articles recents [juice] JuiceSSH - Give me my pro features back il y a un mois [background] C'est l'automne ! il y a 3 ans [header] Fabrication d'une niche il y a 3 ans [livresdere] Belcastel il y a 3 ans Mots cles + blabla + nproject + outil + technique + voyage Basculer en mode sombre [ ] [juice] JuiceSSH - Give me my pro features back 13 dec. 2025 JuiceSSH used to be the best SSH client available on Android until December 2025. Since then, the purchase made in 2019 is not recognized anymore, and the price went up by 20$. Some users complain in review that after buying it again, the application doesn't get activated. Support is unresponsive, this looks like an exit scam. Below is a way to make the application work again. This required jadx to understand smali, and will require you ApkTool and jarsigner, which is part of OpenJDK, and you that can install on Windows using choco install openjdk. You'll also need a JuiceSSH apk, I downloaded one from PureAPK, but feel free to dump your own from your device using adb if you cannot find it. Make sure to verify the hash using virus total/sha256sum if downloading from internet, which should be d1ee811bcd82f25aea0bdc568896d82017ee174d9c4631c123a9d9173c748232 for the last version available, version 3.2.2. Below are powershell version of the command lines, but you get the idea. Decompile The first step is to decompile the dex packed code from the apk. & "C:\Program Files\OpenJDK\jdk-25\bin\java.exe" -jar d juicessh.apk Modify smali You then need to modify the smali of three files, which are detailed below. smali/com/sonelli/juicessh/models/User.smali In this file, we'll patch the purchase validation and signature validation, done by the public boolean H() function. Here is the original version. public boolean H() { try { String str = ""; ArrayList arrayList = new ArrayList(); for (Purchase purchase : this.purchases) { if (!arrayList.contains(purchase.order)) { str = str + purchase.product + purchase.state; arrayList.add(purchase.order); } } return vg0.b(this.signature, this.sessionIdentifier + this.name + this.email + str + this.disabled.toString()); } catch (IllegalStateException e) { e.printStackTrace(); return false; } } Which we'll simply change into public boolean H() { return true; } # virtual methods .method public H()Z .locals 1 const/4 v0, 0x1 return v0 .end method smali/com/sonelli/oi0.smali In this one, we'll patch the public static boolean d(Object obj) function, who calls the H() function we modified above, which now returns true, filters product matching JuiceSSH in purchases list, and check if it the purchase is valid. We'll simply make it return true in any case. Here is the original version: public static boolean d(Object obj) { if (!obj.getClass().getName().equals(User.class.getName())) { return false; } try { if (!((User) obj).H()) { return false; } ArrayList arrayList = new ArrayList(); for (Purchase purchase : ((User) obj).purchases) { if (purchase.product.equals(a())) { arrayList.add(purchase); } } Collections.sort(arrayList, new a()); if (arrayList.size() > 0) { if (((Purchase) arrayList.get(arrayList.size() - 1)).state.intValue() == 0) { return true; } } return false; } catch (NullPointerException e) { e.printStackTrace(); return false; } } Here is the patched one: public static boolean d(Object obj) { return obj.getClass().getName().equals(User.class.getName()); } .method public static d(Ljava/lang/Object;)Z .locals 3 # obj.getClass() invoke-virtual {p0}, Ljava/lang/Object;->getClass()Ljava/lang/Class; move-result-object v0 # obj.getClass().getName() invoke-virtual {v0}, Ljava/lang/Class;->getName()Ljava/lang/String; move-result-object v0 # User.class const-class v1, Lcom/sonelli/juicessh/models/User; # User.class.getName() invoke-virtual {v1}, Ljava/lang/Class;->getName()Ljava/lang/String; move-result-object v1 # compare strings invoke-virtual {v0, v1}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z move-result v2 if-nez v2, :cond_true const/4 v0, 0x0 return v0 :cond_true const/4 v0, 0x1 return v0 .end method smali/com/sonelli/pi0.smali Finally, we'll patch the central part of the authentication, which is called each time a pro-feature is triggered to ensure user has valid license, the public static void j(Context context, p pVar) function. Here is the original version: public static void j(Context context, p pVar) { User user; User user2; String strS = User.s(context); if (strS == null) { pVar.a(context.getString(R$string.authentication_failure)); return; } if (strS.equals("New User")) { pVar.a("New User"); return; } User user3 = b; if (user3 != null && !user3.disabled.booleanValue()) { long jCurrentTimeMillis = System.currentTimeMillis() - b.modified; DateUtils.getRelativeTimeSpanString(System.currentTimeMillis() + (b.w() * 1000), System.currentTimeMillis(), 0L, 0); DateUtils.getRelativeTimeSpanString(System.currentTimeMillis() + (3600000 - jCurrentTimeMillis), System.currentTimeMillis(), 0L, 0); if (b.w() <= 0) { gj0.b("API", "Cached user's API session has expired - refreshing session..."); e(context, null, b.sessionIdentifier, pVar); return; } pVar.b(b); if (jCurrentTimeMillis <= 3600000 || context == null || (user2 = b) == null) { return; } e(context, null, user2.sessionIdentifier, null); return; } User userA = User.A(context); if (userA == null || userA.disabled.booleanValue() || !userA.H()) { e(context, null, null, pVar); return; } b = userA; if (userA.w() <= 0) { e(context, null, b.sessionIdentifier, pVar); return; } pVar.b(b); if (context == null || (user = b) == null) { return; } e(context, null, user.sessionIdentifier, null); } pVar.b() is the success callback we'll call while e() is called in case of error. b is the globally stored user we'll have to set. To patch this, we'll simply craft a User with meaningless data, a session expire always in future, save the user in b, and call the success callback every time. public static void j(Context context, p pVar) { User user = new User(); user.email = "myemail@google.com"; user.name = "hello"; user.given_name = "hello"; user.sessionExpires = System.currentTimeMillis() + (86400000 * 365); user.sessionIdentifier = ""; b = user; pVar.b(user); } .method public static j(Landroid/content/Context;Lcom/sonelli/pi0$p;)V .locals 8 # User u = new User(); new-instance v0, Lcom/sonelli/juicessh/models/User; invoke-direct {v0}, Lcom/sonelli/juicessh/models/User;->()V # u.email = "myemail@google.com"; const-string v1, "myemail@google.com" iput-object v1, v0, Lcom/sonelli/juicessh/models/User;->email:Ljava/lang/String; # u.name = "hello"; const-string v1, "hello" iput-object v1, v0, Lcom/sonelli/juicessh/models/User;->name:Ljava/lang/String; # u.given_name = "hello"; iput-object v1, v0, Lcom/sonelli/juicessh/models/User;->given_name:Ljava/lang/String; # long now = System.currentTimeMillis(); invoke-static {}, Ljava/lang/System;->currentTimeMillis()J move-result-wide v2 # yearMillis = 86400000L * 365L const-wide/32 v4, 0x05265c00 # 86400000 const-wide/16 v6, 0x016d # 365 mul-long/2addr v4, v6 # u.sessionExpires = now + yearMillis; add-long/2addr v2, v4 iput-wide v2, v0, Lcom/sonelli/juicessh/models/User;->sessionExpires:J # u.sessionIdentifier = "" const-string v1, "" iput-object v1, v0, Lcom/sonelli/juicessh/models/User;->sessionIdentifier:Ljava/lang/String; # pi0.b = u; sput-object v0, Lcom/sonelli/pi0;->b:Lcom/sonelli/juicessh/models/User; # pVar.b(b); invoke-virtual {p1, v0}, Lcom/sonelli/pi0$p;->b(Lcom/sonelli/juicessh/models/User;)V return-void Recompile & "C:\Program Files\OpenJDK\jdk-25\bin\java.exe" -jar .\apktool_2.12.1.jar juicessh Sign the apk # Create a keystore if needed to self sign the APK keytool -genkey -v -keystore k.keystore -alias a -keyalg RSA -keysize 2048 -validity 50000 # Sign the APK jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore k.keystore ./juicessh/dist/juicessh.apk a Done You can install this apk, ignore the security warning because it is self signed, and enjoy JuiceSSH with its pro features again. I don't think the cloud sync will work anymore, but that's a minor inconvenience. The plugins don't work anymore too, which is really a joke. Paprika Developpeur. Ecrivez pour rechercher [ ] Pas de resultat pour votre recherche, essayez autre chose. nproject (c) 2026 * Publie avec Ghost Informations sur la licence JavaScript