└── README.md /README.md: -------------------------------------------------------------------------------- 1 | © Lutwidse All rights reserved. 2 | 3 | ## 0. Introduction to analyze LINE 4 | Hi, I'm Lutwidse who is learning about reverse engineering. 5 | I think each country has it's own popular messaging application. 6 | In my country Japan, most users used LINE. 7 | I was very interested in LINE's communication protocol but however, LINE is not an OSS application. 8 | So I decided to reverse engineer it. 9 | 10 | ## 1. But what exactly LINE is 11 | 12 | *Referenced from "Line (software) 13 | from Wikipedia, the free encyclopedia"* 14 | 15 | > Line (styled in all caps as LINE) is a freeware app for instant communications on electronic devices such as smartphones, tablet computers, and personal computers. Line users exchange texts, images, video and audio, and conduct free VoIP conversations and video conferences. 16 | 17 | - > As its competitor Kakao dominated the South Korean messaging market, Naver Corporation launched a messenger application NAVER Talk in February 2011 in South Korea. However, because the South Korean messaging market was dominated by Kakao, the business of NAVER Talk was suppressed. Naver Corporation was expanding their messaging application and targeted to other countries' messaging markets which have not been developed yet. Naver Corporation released their messaging application, which changed its name to 'LINE', to the Japanese messaging market in 2011. As LINE became a huge success, finally NAVER combined NAVER Talk and LINE in March 2012. 18 | 19 | Really interesting that LINE got huge success in Japan. 20 | Currently, there are no domestic messaging applications in Japan, and people tend to avoid switching to new services, so even if they are created, they are unlikely to become widespread. 21 | In the category I know, crowdfunding of domestic secure applications using blockchain technology is carried out. However, there are only 56 donors (2020-11-16-0350), and when calculating the proportion of the population between the ages of 15 and 64 who regularly use smartphones, only about 0.0000007% of the population has donated. 22 | As a fact. The "mottainai" spirit of using conventional products is at the root. 23 | 24 | ## 2. Communication protocol overview 25 | 26 | *Referenced from LINE Encryption Overview 27 | Technical Whitepaper* 28 | 29 | 3.1 Protocol Overview 30 | - > The main transport protocol used in LINE mobile clients is based on SPDY 2.0 [1]. While the 31 | SPDY protocol typically relies on TLS to establish an encrypted channel, LINE’s 32 | implementation uses a lightweight handshake protocol to establish the transport keys used 33 | for application data encryption. 34 | Our handshake protocol is loosely based on the 0-RTT handshake in TLS v1.3 [2]. LINE’s 35 | transport encryption protocol uses elliptic curve cryptography (ECC) with the secp256k1 36 | curve [3] to implement key exchange and server identity verification. We use AES for 37 | symmetric encryption and derive symmetric keys using HKDF [4]. 38 | We describe the protocol in more detail below. 39 | 40 | Okay, so basically LINE Android/iOS is using SPDY 2.0 for communication. 41 | SPDY protocol is developed by Google to purpose supporting the HTTP. 42 | But it's abandoned in 2016 due to prioritizing developing HTTP/2. 43 | 44 | Let's see if we can get something from the packet. 45 | I used HttpCanary for capturing packets. 46 | Also, I used both LINE / LINE Lite and armv7/armv8 for efficient debugging. 47 | 48 | **QrCode - Login Session** 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | In those packets, we can see that LINE is using Apache Thrift compact protocol for communication to connect "/acct/lgn/sq/v1". 57 | It's API and probably "/account/login/". 58 | As we can see LINE is generating a session in the thrift API endpoint with by createSession function. 59 | 60 | Now we need to understand what Thrift is and behavior. 61 | 62 | ## 3. Let's deep into Apache Thrift 63 | 64 | Apache Thrift is the protocol made by Facebook for scalable cross-language services development. 65 | Such as Thrift is called Interface Description Language a.k.a IDL. 66 | The developer defines data types in the .thrift file and compiles the definition file with Thrift compiler to use in any programming language. 67 | 68 | *A simple example of Thrift IDL definition.* 69 | 70 | ```thrift 71 | exception HelloError 72 | { 73 | 1:i32 errcode, 74 | 2:string message 75 | } 76 | 77 | struct HelloResponse 78 | { 79 | 1:string message; 80 | } 81 | 82 | struct HelloRequest{} 83 | 84 | service HelloService 85 | { 86 | HelloResponse HelloWorld( 87 | 1:HelloRequest request) 88 | throws (1:HelloError err); 89 | } 90 | ``` 91 | 92 | Thrift IDL has six types. 93 | - Base Types 94 | - Special Types 95 | - Structs 96 | - Containers 97 | - Exceptions 98 | - Services 99 | 100 | And the formula is FieldID: Types name. 101 | FieldId is used to make sure that both communications data are correct. 102 | 103 | ## 4. It's time to deserialize LINE's communication data 104 | 105 | Well, now we know LINE is using Thrift for communication. 106 | However, the packets are serialized. 107 | So let's hook TServiceClient in LINE lite by using a decompiler/debugger. 108 | TServiceClient is the core of communication protocol. 109 | 110 | *Referenced from Java Apache Thrift javadoc* 111 | 112 | ```Java 113 | public abstract class TServiceClient 114 | extends java.lang.Object 115 | A TServiceClient is used to communicate with a TService implementation across protocols and transports. 116 | ``` 117 | 118 | ```Java 119 | protected void sendBase(java.lang.String methodName, 120 | TBase args) 121 | throws TException 122 | Throws: 123 | TException 124 | ``` 125 | 126 | ```Java 127 | protected void receiveBase(TBase result, 128 | java.lang.String methodName) 129 | throws TException 130 | Throws: 131 | TException 132 | ``` 133 | 134 | Okay, we might able to hook packets with these functions! 135 | 136 | 137 | 138 | 139 | 140 | Yes, we reached TServiceClient in the decompiler. 141 | It's probably obfuscated by llvm-obfuscator which is called "ORK" that made by LINE Corporation. 142 | But it's easily to understand. 143 | function a is receiveBase, function b is sendBase. 144 | So I coded an Xposed application for hooking that. 145 | 146 | ```Java 147 | public class ThriftHooker implements IXposedHookLoadPackage { 148 | public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lparam) throws Throwable { 149 | if (lparam.packageName.equals("com.linecorp.linelite")) { 150 | Class TServiceClient = lparam.classLoader.loadClass("w.a.a.TServiceClient"); 151 | 152 | XposedHelpers.findAndHookMethod(TServiceClient, "b", String.class, "w.a.a.TProtocol", new XC_MethodHook() { 153 | @RequiresApi(api = Build.VERSION_CODES.O) 154 | 155 | @Override 156 | protected void beforeHookedMethod(MethodHookParam param) throws Throwable { 157 | XposedBridge.log("[TServiceClient sendBase]: " + " [ " + param.args[1] + " ] " + param.args[1].toString()); 158 | } 159 | 160 | @Override 161 | protected void afterHookedMethod(MethodHookParam param) throws Throwable { 162 | } 163 | }); 164 | 165 | XposedHelpers.findAndHookMethod(TServiceClient, "a", "w.a.a.TProtocol", String.class, new XC_MethodHook() { 166 | @RequiresApi(api = Build.VERSION_CODES.O) 167 | 168 | @Override 169 | protected void beforeHookedMethod(MethodHookParam param) throws Throwable { 170 | ArrayList args = new ArrayList(); 171 | for (Object arg: param.args) { 172 | args.add(arg.toString()); 173 | } 174 | XposedBridge.log("[TServiceClient receiveBase]: " + " [ " + param.args[1] + " ] " + param.args[0].toString()); 175 | } 176 | 177 | @Override 178 | protected void afterHookedMethod(MethodHookParam param) throws Throwable { 179 | } 180 | }); 181 | } 182 | } 183 | } 184 | ``` 185 | 186 | And as the result... 187 | 188 | 189 | 190 | **Boom!** 191 | A bunch of packets is readable now. 192 | 193 | ``` 194 | [TServiceClient sendBase]: [ getServerTime_args() ] getServerTime_args() 195 | [TServiceClient sendBase]: [ createSession_args(request:CreateQrSessionRequest()) ] createSession_args(request:CreateQrSessionRequest()) 196 | [TServiceClient receiveBase]: [ getServerTime ] getServerTime_result(success:0, e:null) 197 | [TServiceClient receiveBase]: [ createSession ] createSession_result(success:null, e:null) 198 | [TServiceClient sendBase]: [ createQrCode_args(request:CreateQrCodeRequest(authSessionId:**********************************************************39587a69)) ] createQrCode_args(request:CreateQrCodeRequest(authSessionId:**********************************************************39587a69)) 199 | [TServiceClient receiveBase]: [ createQrCode ] createQrCode_result(success:null, e:null) 200 | [TServiceClient sendBase]: [ verifyCertificate_args(request:VerifyCertificateRequest(authSessionId:**********************************************************39587a69, certificate:********************************************************ec433014)) ] verifyCertificate_args(request:VerifyCertificateRequest(authSessionId:**********************************************************39587a69, certificate:********************************************************ec433014)) 201 | [TServiceClient receiveBase]: [ verifyCertificate ] verifyCertificate_result(success:null, e:null) 202 | [TServiceClient sendBase]: [ qrCodeLogin_args(request:QrCodeLoginRequest(authSessionId:**********************************************************39587a69, systemName:G011A, autoLoginIsRequired:true)) ] qrCodeLogin_args(request:QrCodeLoginRequest(authSessionId:**********************************************************39587a69, systemName:G011A, autoLoginIsRequired:true)) 203 | [TServiceClient receiveBase]: [ qrCodeLogin ] qrCodeLogin_result(success:null, e:null) 204 | ``` 205 | 206 | ## 5. More deep into QrCode login method 207 | 208 | 209 | 210 | In this bytecode, LINE is generating a URL for QrCode login. 211 | When we open the URL in the LINE application, PinCode confirmation will be displayed. 212 | As code shows the key pairs called ecdh are calculated with Curve25519. 213 | 214 | *Referenced from cr.yp.to* 215 | 216 | > Given a user's 32-byte secret key, Curve25519 computes the user's 32-byte public key. Given the user's 32-byte secret key and another user's 32-byte public key, Curve25519 computes a 32-byte secret shared by the two users. This secret can then be used to authenticate and encrypt messages between the two users. 217 | 218 | So the final URL is something like that 219 | ``` 220 | https://line.me/R/au/g/authSessionId?secret=ecdh&e2eeVersion=version 221 | ``` 222 | 223 | After some research, I noticed that the unhookable functions is existing. 224 | 225 | 226 | 227 | The function seems to await for the QrCode login. 228 | Others existed as well, but I will omit them here. 229 | 230 | 231 | 232 | Here is the dump of endpoints that used in QrCode login and messaging. 233 | Of particular importance is 234 | - SECONDARY_LOGIN /ACCT/lgn/sq/v1 235 | - QrCode Login 236 | - SECONDARY_LOGIN_PERMIT /ACCT/lp/lgn/sq/v1 237 | - QrCode Login Validate 238 | - TalkService /S4 239 | - Messaging API 240 | - PollingService /P4 241 | - Receive Operations Cycle 242 | 243 | ## 6. Rebuild Thrift IDL from bytecode 244 | 245 | Alright, now we understand tons of behavior of LINE. 246 | What to do next is rebuild the Thrift IDL. 247 | As one method, I chose Smali. 248 | If you are Java application developer, you might know, when you build the apk, it contains a .dex file which contains Dalvik bytecode. 249 | 250 | I won't go into too much detail, but Java is an intermediate language and easy to decompile. However, reading bytecode is not easy. 251 | So we will use baksmali to disassembly/assembly, apktool is the best choice I think. 252 | After disassembled that with apktool, search codes with Linux command. 253 | 254 | ```shell 255 | $find . -name "*.smali" | xargs grep -E "_result|_args" 256 | ``` 257 | 258 | 259 | 260 | Nice! 261 | Smali's syntax is similar to x86-32 assembly and I was familiar with assembly so I could understand it in about 20 minutes. 262 | And after understanding, I wrote a program in Golang and Python that automatically rebuilds Thrift IDL from Smali. 263 | The algorithm is very simple. 264 | I think the lexical analysis is also useful, but I made the processing for the pattern only with the if statement. 265 | It's so easy that I don't think it's necessary to write it but anyway I will explain the algorithm in bullet points. 266 | **For convenience, we refer to args as program_args.** 267 | 268 | - _result 269 | - Struct 270 | - Exception 271 | 272 | - _args 273 | - Struct 274 | 275 | > Rules 276 | - service name and its own functions are can be known from Impl 277 | - jp\naver\line\android\thrift\client\impl 278 | - Response can be known from _result 279 | - When _result has no Struct, it's a void function. 280 | - Request can be known from _args 281 | - FieldID, Types, the name is can be known from invoke-direct. 282 | - instance fields are linked to program_args of direct methods 283 | - The Type is definitely Types or Struct or Enum 284 | - If instance fields are not enough to link program_args it is optional and can be known from invoke-direct. 285 | - A struct that contains "Exception" in the name is definitely an Exception function. 286 | - Enums are rebuildable from reading invoke-direct 287 | - typedef is can be known from # direct methods 288 | 289 | *Eaxmple of rebuild createQrCode* 290 | 291 | ```smali 292 | # instance fields 293 | .field public d:Lb/a/d/a/a/b/a/j; 294 | 295 | // b/a/d/a/a/b/a/j = Response 296 | 297 | .field public e:Lb/a/d/a/a/b/a/r; 298 | 299 | // b/a/d/a/a/b/a/r = Exception 300 | 301 | 302 | # direct methods 303 | .method public static constructor ()V 304 | .locals 8 305 | 306 | .line 1 307 | new-instance v0, Lw/a/a/j/l; 308 | 309 | const-string v1, "createQrCode_result" 310 | 311 | invoke-direct {v0, v1}, Lw/a/a/j/l;->(Ljava/lang/String;)V 312 | 313 | sput-object v0, Lb/a/d/a/a/b/a/m0;->f:Lw/a/a/j/l; 314 | 315 | .line 2 316 | new-instance v0, Lw/a/a/j/c; 317 | 318 | const-string v1, "success" 319 | 320 | const/16 v2, 0xc 321 | 322 | const/4 v3, 0x0 323 | 324 | invoke-direct {v0, v1, v2, v3}, Lw/a/a/j/c;->(Ljava/lang/String;BS)V 325 | 326 | /* 327 | v1 = name 328 | v2 = Type 329 | v3 = FieldID 330 | */ 331 | 332 | sput-object v0, Lb/a/d/a/a/b/a/m0;->g:Lw/a/a/j/c; 333 | 334 | ... 335 | ``` 336 | 337 | ```smali 338 | # instance fields 339 | .field public d:Lb/a/d/a/a/b/a/i; 340 | 341 | // b/a/d/a/a/b/a/i = request 342 | 343 | # direct methods 344 | .method public static constructor ()V 345 | .locals 7 346 | 347 | .line 1 348 | new-instance v0, Lw/a/a/j/l; 349 | 350 | const-string v1, "createQrCode_args" 351 | 352 | invoke-direct {v0, v1}, Lw/a/a/j/l;->(Ljava/lang/String;)V 353 | 354 | sput-object v0, Lb/a/d/a/a/b/a/l0;->e:Lw/a/a/j/l; 355 | 356 | .line 2 357 | new-instance v0, Lw/a/a/j/c; 358 | 359 | const-string v1, "request" 360 | 361 | const/16 v2, 0xc 362 | 363 | const/4 v3, 0x1 364 | 365 | invoke-direct {v0, v1, v2, v3}, Lw/a/a/j/c;->(Ljava/lang/String;BS)V 366 | 367 | /* 368 | v1 = name 369 | v2 = Type 370 | v3 = FieldID 371 | */ 372 | 373 | sput-object v0, Lb/a/d/a/a/b/a/l0;->f:Lw/a/a/j/c; 374 | 375 | ... 376 | ``` 377 | 378 | ```thrift 379 | 380 | enum g_a_c_u0_a_c_b_c 381 | { 382 | INTERNAL_ERROR = 0; 383 | ILLEGAL_ARGUMENT = 1; 384 | VERIFICATION_FAILED = 2; 385 | NOT_ALLOWED_QR_CODE_LOGIN = 3; 386 | VERIFICATION_NOTICE_FAILED = 4; 387 | RETRY_LATER = 5; 388 | INVALID_CONTEXT = 100; 389 | APP_UPGRADE_REQUIRED = 101; 390 | } 391 | 392 | exception SecondaryQrCodeException 393 | { 394 | 1:g_a_c_u0_a_c_b_c code; 395 | 2:string alertMessage; 396 | } 397 | 398 | struct CreateQrCodeResponse 399 | { 400 | 1:string callbackUrl; 401 | } 402 | 403 | struct CreateQrCodeRequest 404 | { 405 | 1:string authSessionId; 406 | } 407 | 408 | service SecondaryQrcodeLoginService 409 | { 410 | CreateQrCodeResponse createQrCode( 411 | 1:CreateQrCodeRequest request) throws (1:SecondaryQrCodeException e); 412 | } 413 | ``` 414 | 415 | ## 7. Login LINE with CLI and use functions 416 | 417 | 418 | 419 | Seems successfully generated a Python library from the rebuilt Thrift IDL. 420 | All you have to do now is implement the API in Python! 421 | *The longer you wait for something, the more you appreciate it when you get it. Because anything worth having is definitely worth waiting for. - Susan Gale* 422 | 423 | ```python 424 | from LutwidseAPI.TalkService import TalkService 425 | 426 | # Login algorithm is omitted due to code as hell as spaghetti. 427 | msg = Message 428 | msg = Message(to=groupId, text="Hello World") 429 | # groupId can be known with packet analysis or you can use Thrift functions that get group information. 430 | client.sendMessage(reqSeq, msg) 431 | ``` 432 | 433 | **FIRE IN THE HOLE!** 434 | 435 | 436 | 437 | 438 | 439 | ## 8. Fetching operations 440 | 441 | If you hooking LINE long time, you would notice that function called fetchOps is rotating every few minutes. 442 | 443 | *Referenced from wikipedia, the free encyclopedia* 444 | 445 | > long polling (uncountable) 446 | >> (computing) A technology where the client requests information from the server without expecting an immediate response. 447 | 448 | So the function is used to wait and catch the user's actions, such as "Your friend sent a message to you/group" "somebody changed the group's icon". 449 | 450 | ``` 451 | [TServiceClient sendBase]: [ fetchOps_args(localRev:393831, count:50, globalRev:295818, individualRev:1286) ] fetchOps_args(localRev:393831, count:50, globalRev:295818, individualRev:1286) 452 | ``` 453 | 454 | But how do globalRev and individualRev is deciding? 455 | So let's debug the operations with the API your own made. 456 | Now, you should be wondering about the strange sequence returned. 457 | 458 | ``` 459 | createdTime 0 460 | param1 128658291 461 | param2 295818notice23moretab304stickershop234channel205denykeyword244connectioninfo148buddy256timelineinfo8themeshop41callrate43configuration348sticon52suggestdictionary144suggestsettings281usersettings0analyticsinfo289searchpopularkeyword224searchnotice169timeline99searchpopularcategory287extendedprofile254seasonalmarketing34newstab84suggestdictionaryv2106chatappsync337agreements323instantnews147emojimapping96searchbarkeywords38shopping256chateffectbg223chateffectkw27searchindex276hubtab109payruleupdated144smartch244homeservicelist296timelinestory289wallettab261podtab183 462 | reqSeq -1 463 | revision -1 464 | type 0 465 | ``` 466 | 467 | Assuming you like puzzles, it's very easy to answer. 468 | 469 | - fetchOps 470 | - localRev = your account's fetched operations count 471 | - count = how many operations you will fetch 472 | - individualRev = first numbers of param1 473 | - globalRev = first numbers of param2 474 | 475 | I have no idea know why LINE deciding the key with that. 476 | Anyway, you can confirm it works properly. 477 | 478 | **Thank you for reading - Lutwidse** 479 | 480 | ### References 481 | > https://en.wikipedia.org/wiki/Line_(software) 482 | 483 | > https://www.stat.go.jp/data/jinsui/2019np/index.html 484 | 485 | > https://camp-fire.jp/projects/view/315308 486 | 487 | > https://scdn.line-apps.com/stf/linecorp/en/csr/line-encryption-whitepaper-ver1.0.pdf 488 | 489 | > https://thrift.apache.org/docs/idl 490 | 491 | > https://people.apache.org/~thejas/thrift-0.9/javadoc/org/apache/thrift/TServiceClient.html 492 | 493 | > https://engineering.linecorp.com/ja/blog/ork-vol-1/ 494 | 495 | > https://cr.yp.to/ecdh.html 496 | 497 | > https://github.com/JesusFreke/smali 498 | 499 | > https://en.wiktionary.org/wiki/long_polling 500 | 501 | > https://en.wikipedia.org/wiki/Push_technology 502 | --------------------------------------------------------------------------------