└── 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 |
--------------------------------------------------------------------------------