CINXE.COM
Should We Chat, Too? Security Analysis of WeChat’s MMTLS Encryption Protocol - The Citizen Lab
<!doctype html> <!--[if lt IE 7]><html lang="en-US" prefix="og: https://ogp.me/ns#"><![endif]--> <!--[if (IE 7)&!(IEMobile)]><html lang="en-US" prefix="og: https://ogp.me/ns#"><![endif]--> <!--[if (IE 8)&!(IEMobile)]><html lang="en-US" prefix="og: https://ogp.me/ns#"><![endif]--> <!--[if gt IE 8]><!--> <html lang="en-US" prefix="og: https://ogp.me/ns#"><!--<![endif]--> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>Should We Chat, Too? Security Analysis of WeChat’s MMTLS Encryption Protocol - The Citizen Lab</title> <meta name="HandheldFriendly" content="True"> <meta name="MobileOptimized" content="320"> <meta name="viewport" content="width=device-width, initial-scale=1"/> <link rel="apple-touch-icon" sizes="57x57" href="https://citizenlab.ca/wp-content/themes/citizenlab-2.1.2/library/images/apple-icon-57x57.png"> <link rel="apple-touch-icon" sizes="60x60" href="https://citizenlab.ca/wp-content/themes/citizenlab-2.1.2/library/images/apple-icon-60x60.png"> <link rel="apple-touch-icon" sizes="72x72" href="https://citizenlab.ca/wp-content/themes/citizenlab-2.1.2/library/images/apple-icon-72x72.png"> <link rel="apple-touch-icon" sizes="76x76" href="https://citizenlab.ca/wp-content/themes/citizenlab-2.1.2/library/images/apple-icon-76x76.png"> <link rel="apple-touch-icon" sizes="114x114" href="https://citizenlab.ca/wp-content/themes/citizenlab-2.1.2/library/images/apple-icon-114x114.png"> <link rel="apple-touch-icon" sizes="120x120" href="https://citizenlab.ca/wp-content/themes/citizenlab-2.1.2/library/images/apple-icon-120x120.png"> <link rel="apple-touch-icon" sizes="144x144" href="https://citizenlab.ca/wp-content/themes/citizenlab-2.1.2/library/images/apple-icon-144x144.png"> <link rel="apple-touch-icon" sizes="152x152" href="https://citizenlab.ca/wp-content/themes/citizenlab-2.1.2/library/images/apple-icon-152x152.png"> <link rel="apple-touch-icon" sizes="180x180" href="https://citizenlab.ca/wp-content/themes/citizenlab-2.1.2/library/images/apple-icon-180x180.png"> <link rel="icon" type="image/png" sizes="192x192" href="https://citizenlab.ca/wp-content/themes/citizenlab-2.1.2/library/images/android-icon-192x192.png"> <link rel="icon" type="image/png" sizes="32x32" href="https://citizenlab.ca/wp-content/themes/citizenlab-2.1.2/library/images/favicon-32x32.png"> <link rel="icon" type="image/png" sizes="96x96" href="https://citizenlab.ca/wp-content/themes/citizenlab-2.1.2/library/images/favicon-96x96.png"> <link rel="icon" type="image/png" sizes="16x16" href="https://citizenlab.ca/wp-content/themes/citizenlab-2.1.2/library/images/favicon-16x16.png"> <link rel="manifest" href="https://citizenlab.ca/wp-content/themes/citizenlab-2.1.2/library/images/manifest.json"> <meta name="msapplication-TileColor" content="#ffffff"> <meta name="msapplication-TileImage" content="/ms-icon-144x144.png"> <meta name="theme-color" content="#ffffff"> <!--[if IE]> <link rel="shortcut icon" href="https://citizenlab.ca/wp-content/themes/citizenlab-2.1.2/favicon.ico"> <![endif]--> <link rel="pingback" href="https://citizenlab.ca/xmlrpc.php"> <style>img:is([sizes="auto" i], [sizes^="auto," i]) { contain-intrinsic-size: 3000px 1500px }</style> <!-- Search Engine Optimization by Rank Math PRO - https://rankmath.com/ --> <meta name="description" content="This report performs the first public analysis of MMTLS, the main network protocol used by WeChat, an app with over one billion users. The report finds that MMTLS is a modified version of TLS, however some of the modifications have introduced cryptographic weaknesses."/> <meta name="robots" content="follow, index, max-snippet:-1, max-video-preview:-1, max-image-preview:large"/> <link rel="canonical" href="https://citizenlab.ca/2024/10/should-we-chat-too-security-analysis-of-wechats-mmtls-encryption-protocol/" /> <meta property="og:locale" content="en_US" /> <meta property="og:type" content="article" /> <meta property="og:title" content="Should We Chat, Too? Security Analysis of WeChat’s MMTLS Encryption Protocol - The Citizen Lab" /> <meta property="og:description" content="This report performs the first public analysis of MMTLS, the main network protocol used by WeChat, an app with over one billion users. The report finds that MMTLS is a modified version of TLS, however some of the modifications have introduced cryptographic weaknesses." /> <meta property="og:url" content="https://citizenlab.ca/2024/10/should-we-chat-too-security-analysis-of-wechats-mmtls-encryption-protocol/" /> <meta property="og:site_name" content="The Citizen Lab" /> <meta property="article:tag" content="Encryption" /> <meta property="article:tag" content="Security" /> <meta property="article:tag" content="WeChat" /> <meta property="article:section" content="App Privacy and Controls" /> <meta property="og:updated_time" content="2024-11-24T13:06:19-05:00" /> <meta property="og:image" content="https://citizenlab.ca/wp-content/uploads/2024/10/featured-image-zoomed-1024x750.png" /> <meta property="og:image:secure_url" content="https://citizenlab.ca/wp-content/uploads/2024/10/featured-image-zoomed-1024x750.png" /> <meta property="og:image:width" content="640" /> <meta property="og:image:height" content="469" /> <meta property="og:image:alt" content="WeChat" /> <meta property="og:image:type" content="image/png" /> <meta property="article:published_time" content="2024-10-15T14:59:12-04:00" /> <meta property="article:modified_time" content="2024-11-24T13:06:19-05:00" /> <meta name="twitter:card" content="summary_large_image" /> <meta name="twitter:title" content="Should We Chat, Too? Security Analysis of WeChat’s MMTLS Encryption Protocol - The Citizen Lab" /> <meta name="twitter:description" content="This report performs the first public analysis of MMTLS, the main network protocol used by WeChat, an app with over one billion users. The report finds that MMTLS is a modified version of TLS, however some of the modifications have introduced cryptographic weaknesses." /> <meta name="twitter:site" content="@citizenlab" /> <meta name="twitter:creator" content="@citizenlab" /> <meta name="twitter:image" content="https://citizenlab.ca/wp-content/uploads/2024/10/featured-image-zoomed-1024x750.png" /> <meta name="twitter:label1" content="Written by" /> <meta name="twitter:data1" content="Mona Wang" /> <meta name="twitter:label2" content="Time to read" /> <meta name="twitter:data2" content="62 minutes" /> <script type="application/ld+json" class="rank-math-schema-pro">{"@context":"https://schema.org","@graph":[{"@type":["CollegeOrUniversity","Organization"],"@id":"https://citizenlab.ca/#organization","name":"The Citizen Lab","url":"https://citizenlab.ca","sameAs":["https://twitter.com/citizenlab"],"logo":{"@type":"ImageObject","@id":"https://citizenlab.ca/#logo","url":"https://citizenlab.ca/wp-content/uploads/2019/02/citlablogo.png","contentUrl":"https://citizenlab.ca/wp-content/uploads/2019/02/citlablogo.png","caption":"The Citizen Lab","inLanguage":"en-US","width":"7824","height":"5216"}},{"@type":"WebSite","@id":"https://citizenlab.ca/#website","url":"https://citizenlab.ca","name":"The Citizen Lab","publisher":{"@id":"https://citizenlab.ca/#organization"},"inLanguage":"en-US"},{"@type":"ImageObject","@id":"https://citizenlab.ca/wp-content/uploads/2024/10/featured-image-zoomed.png","url":"https://citizenlab.ca/wp-content/uploads/2024/10/featured-image-zoomed.png","width":"2316","height":"1696","inLanguage":"en-US"},{"@type":"WebPage","@id":"https://citizenlab.ca/2024/10/should-we-chat-too-security-analysis-of-wechats-mmtls-encryption-protocol/#webpage","url":"https://citizenlab.ca/2024/10/should-we-chat-too-security-analysis-of-wechats-mmtls-encryption-protocol/","name":"Should We Chat, Too? Security Analysis of WeChat\u2019s MMTLS Encryption Protocol - The Citizen Lab","datePublished":"2024-10-15T14:59:12-04:00","dateModified":"2024-11-24T13:06:19-05:00","isPartOf":{"@id":"https://citizenlab.ca/#website"},"primaryImageOfPage":{"@id":"https://citizenlab.ca/wp-content/uploads/2024/10/featured-image-zoomed.png"},"inLanguage":"en-US"},{"@type":"Person","@id":"https://citizenlab.ca/author/monaw/","name":"Mona Wang","url":"https://citizenlab.ca/author/monaw/","image":{"@type":"ImageObject","@id":"https://secure.gravatar.com/avatar/7c71b732360447d934705f4d237b8be9?s=96&d=mm&r=g","url":"https://secure.gravatar.com/avatar/7c71b732360447d934705f4d237b8be9?s=96&d=mm&r=g","caption":"Mona Wang","inLanguage":"en-US"},"worksFor":{"@id":"https://citizenlab.ca/#organization"}},{"@type":"BlogPosting","headline":"Should We Chat, Too? Security Analysis of WeChat\u2019s MMTLS Encryption Protocol - The Citizen Lab","keywords":"WeChat","datePublished":"2024-10-15T14:59:12-04:00","dateModified":"2024-11-24T13:06:19-05:00","author":{"@id":"https://citizenlab.ca/author/monaw/","name":"Mona Wang"},"publisher":{"@id":"https://citizenlab.ca/#organization"},"description":"This report performs the first public analysis of MMTLS, the main network protocol used by WeChat, an app with over one billion users. The report finds that MMTLS is a modified version of TLS, however some of the modifications have introduced cryptographic weaknesses.","name":"Should We Chat, Too? Security Analysis of WeChat\u2019s MMTLS Encryption Protocol - The Citizen Lab","@id":"https://citizenlab.ca/2024/10/should-we-chat-too-security-analysis-of-wechats-mmtls-encryption-protocol/#richSnippet","isPartOf":{"@id":"https://citizenlab.ca/2024/10/should-we-chat-too-security-analysis-of-wechats-mmtls-encryption-protocol/#webpage"},"image":{"@id":"https://citizenlab.ca/wp-content/uploads/2024/10/featured-image-zoomed.png"},"inLanguage":"en-US","mainEntityOfPage":{"@id":"https://citizenlab.ca/2024/10/should-we-chat-too-security-analysis-of-wechats-mmtls-encryption-protocol/#webpage"}}]}</script> <!-- /Rank Math WordPress SEO plugin --> <link rel="alternate" type="application/rss+xml" title="The Citizen Lab » Feed" href="https://citizenlab.ca/feed/" /> <link rel="alternate" type="application/rss+xml" title="The Citizen Lab » Comments Feed" href="https://citizenlab.ca/comments/feed/" /> <script type="text/javascript"> /* <![CDATA[ */ window._wpemojiSettings = {"baseUrl":"https:\/\/s.w.org\/images\/core\/emoji\/15.0.3\/72x72\/","ext":".png","svgUrl":"https:\/\/s.w.org\/images\/core\/emoji\/15.0.3\/svg\/","svgExt":".svg","source":{"concatemoji":"https:\/\/citizenlab.ca\/wp-includes\/js\/wp-emoji-release.min.js"}}; /*! This file is auto-generated */ !function(i,n){var o,s,e;function c(e){try{var t={supportTests:e,timestamp:(new Date).valueOf()};sessionStorage.setItem(o,JSON.stringify(t))}catch(e){}}function p(e,t,n){e.clearRect(0,0,e.canvas.width,e.canvas.height),e.fillText(t,0,0);var t=new Uint32Array(e.getImageData(0,0,e.canvas.width,e.canvas.height).data),r=(e.clearRect(0,0,e.canvas.width,e.canvas.height),e.fillText(n,0,0),new Uint32Array(e.getImageData(0,0,e.canvas.width,e.canvas.height).data));return t.every(function(e,t){return e===r[t]})}function u(e,t,n){switch(t){case"flag":return n(e,"\ud83c\udff3\ufe0f\u200d\u26a7\ufe0f","\ud83c\udff3\ufe0f\u200b\u26a7\ufe0f")?!1:!n(e,"\ud83c\uddfa\ud83c\uddf3","\ud83c\uddfa\u200b\ud83c\uddf3")&&!n(e,"\ud83c\udff4\udb40\udc67\udb40\udc62\udb40\udc65\udb40\udc6e\udb40\udc67\udb40\udc7f","\ud83c\udff4\u200b\udb40\udc67\u200b\udb40\udc62\u200b\udb40\udc65\u200b\udb40\udc6e\u200b\udb40\udc67\u200b\udb40\udc7f");case"emoji":return!n(e,"\ud83d\udc26\u200d\u2b1b","\ud83d\udc26\u200b\u2b1b")}return!1}function f(e,t,n){var r="undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?new OffscreenCanvas(300,150):i.createElement("canvas"),a=r.getContext("2d",{willReadFrequently:!0}),o=(a.textBaseline="top",a.font="600 32px Arial",{});return e.forEach(function(e){o[e]=t(a,e,n)}),o}function t(e){var t=i.createElement("script");t.src=e,t.defer=!0,i.head.appendChild(t)}"undefined"!=typeof Promise&&(o="wpEmojiSettingsSupports",s=["flag","emoji"],n.supports={everything:!0,everythingExceptFlag:!0},e=new Promise(function(e){i.addEventListener("DOMContentLoaded",e,{once:!0})}),new Promise(function(t){var n=function(){try{var e=JSON.parse(sessionStorage.getItem(o));if("object"==typeof e&&"number"==typeof e.timestamp&&(new Date).valueOf()<e.timestamp+604800&&"object"==typeof e.supportTests)return e.supportTests}catch(e){}return null}();if(!n){if("undefined"!=typeof Worker&&"undefined"!=typeof OffscreenCanvas&&"undefined"!=typeof URL&&URL.createObjectURL&&"undefined"!=typeof Blob)try{var e="postMessage("+f.toString()+"("+[JSON.stringify(s),u.toString(),p.toString()].join(",")+"));",r=new Blob([e],{type:"text/javascript"}),a=new Worker(URL.createObjectURL(r),{name:"wpTestEmojiSupports"});return void(a.onmessage=function(e){c(n=e.data),a.terminate(),t(n)})}catch(e){}c(n=f(s,u,p))}t(n)}).then(function(e){for(var t in e)n.supports[t]=e[t],n.supports.everything=n.supports.everything&&n.supports[t],"flag"!==t&&(n.supports.everythingExceptFlag=n.supports.everythingExceptFlag&&n.supports[t]);n.supports.everythingExceptFlag=n.supports.everythingExceptFlag&&!n.supports.flag,n.DOMReady=!1,n.readyCallback=function(){n.DOMReady=!0}}).then(function(){return e}).then(function(){var e;n.supports.everything||(n.readyCallback(),(e=n.source||{}).concatemoji?t(e.concatemoji):e.wpemoji&&e.twemoji&&(t(e.twemoji),t(e.wpemoji)))}))}((window,document),window._wpemojiSettings); /* ]]> */ </script> <style id='wp-emoji-styles-inline-css' type='text/css'> img.wp-smiley, img.emoji { display: inline !important; border: none !important; box-shadow: none !important; height: 1em !important; width: 1em !important; margin: 0 0.07em !important; vertical-align: -0.1em !important; background: none !important; padding: 0 !important; } </style> <link rel='stylesheet' id='wp-block-library-css' href='https://citizenlab.ca/wp-includes/css/dist/block-library/style.min.css' type='text/css' media='all' /> <style id='co-authors-plus-coauthors-style-inline-css' type='text/css'> .wp-block-co-authors-plus-coauthors.is-layout-flow [class*=wp-block-co-authors-plus]{display:inline} </style> <style id='co-authors-plus-avatar-style-inline-css' type='text/css'> .wp-block-co-authors-plus-avatar :where(img){height:auto;max-width:100%;vertical-align:bottom}.wp-block-co-authors-plus-coauthors.is-layout-flow .wp-block-co-authors-plus-avatar :where(img){vertical-align:middle}.wp-block-co-authors-plus-avatar:is(.alignleft,.alignright){display:table}.wp-block-co-authors-plus-avatar.aligncenter{display:table;margin-inline:auto} </style> <style id='co-authors-plus-image-style-inline-css' type='text/css'> .wp-block-co-authors-plus-image{margin-bottom:0}.wp-block-co-authors-plus-image :where(img){height:auto;max-width:100%;vertical-align:bottom}.wp-block-co-authors-plus-coauthors.is-layout-flow .wp-block-co-authors-plus-image :where(img){vertical-align:middle}.wp-block-co-authors-plus-image:is(.alignfull,.alignwide) :where(img){width:100%}.wp-block-co-authors-plus-image:is(.alignleft,.alignright){display:table}.wp-block-co-authors-plus-image.aligncenter{display:table;margin-inline:auto} </style> <style id='rank-math-toc-block-style-inline-css' type='text/css'> .wp-block-rank-math-toc-block nav ol{counter-reset:item}.wp-block-rank-math-toc-block nav ol li{display:block}.wp-block-rank-math-toc-block nav ol li:before{content:counters(item, ".") ". ";counter-increment:item} </style> <style id='classic-theme-styles-inline-css' type='text/css'> /*! This file is auto-generated */ .wp-block-button__link{color:#fff;background-color:#32373c;border-radius:9999px;box-shadow:none;text-decoration:none;padding:calc(.667em + 2px) calc(1.333em + 2px);font-size:1.125em}.wp-block-file__button{background:#32373c;color:#fff;text-decoration:none} </style> <style id='global-styles-inline-css' type='text/css'> :root{--wp--preset--aspect-ratio--square: 1;--wp--preset--aspect-ratio--4-3: 4/3;--wp--preset--aspect-ratio--3-4: 3/4;--wp--preset--aspect-ratio--3-2: 3/2;--wp--preset--aspect-ratio--2-3: 2/3;--wp--preset--aspect-ratio--16-9: 16/9;--wp--preset--aspect-ratio--9-16: 9/16;--wp--preset--color--black: #000000;--wp--preset--color--cyan-bluish-gray: #abb8c3;--wp--preset--color--white: #ffffff;--wp--preset--color--pale-pink: #f78da7;--wp--preset--color--vivid-red: #cf2e2e;--wp--preset--color--luminous-vivid-orange: #ff6900;--wp--preset--color--luminous-vivid-amber: #fcb900;--wp--preset--color--light-green-cyan: #7bdcb5;--wp--preset--color--vivid-green-cyan: #00d084;--wp--preset--color--pale-cyan-blue: #8ed1fc;--wp--preset--color--vivid-cyan-blue: #0693e3;--wp--preset--color--vivid-purple: #9b51e0;--wp--preset--gradient--vivid-cyan-blue-to-vivid-purple: linear-gradient(135deg,rgba(6,147,227,1) 0%,rgb(155,81,224) 100%);--wp--preset--gradient--light-green-cyan-to-vivid-green-cyan: linear-gradient(135deg,rgb(122,220,180) 0%,rgb(0,208,130) 100%);--wp--preset--gradient--luminous-vivid-amber-to-luminous-vivid-orange: linear-gradient(135deg,rgba(252,185,0,1) 0%,rgba(255,105,0,1) 100%);--wp--preset--gradient--luminous-vivid-orange-to-vivid-red: linear-gradient(135deg,rgba(255,105,0,1) 0%,rgb(207,46,46) 100%);--wp--preset--gradient--very-light-gray-to-cyan-bluish-gray: linear-gradient(135deg,rgb(238,238,238) 0%,rgb(169,184,195) 100%);--wp--preset--gradient--cool-to-warm-spectrum: linear-gradient(135deg,rgb(74,234,220) 0%,rgb(151,120,209) 20%,rgb(207,42,186) 40%,rgb(238,44,130) 60%,rgb(251,105,98) 80%,rgb(254,248,76) 100%);--wp--preset--gradient--blush-light-purple: linear-gradient(135deg,rgb(255,206,236) 0%,rgb(152,150,240) 100%);--wp--preset--gradient--blush-bordeaux: linear-gradient(135deg,rgb(254,205,165) 0%,rgb(254,45,45) 50%,rgb(107,0,62) 100%);--wp--preset--gradient--luminous-dusk: linear-gradient(135deg,rgb(255,203,112) 0%,rgb(199,81,192) 50%,rgb(65,88,208) 100%);--wp--preset--gradient--pale-ocean: linear-gradient(135deg,rgb(255,245,203) 0%,rgb(182,227,212) 50%,rgb(51,167,181) 100%);--wp--preset--gradient--electric-grass: linear-gradient(135deg,rgb(202,248,128) 0%,rgb(113,206,126) 100%);--wp--preset--gradient--midnight: linear-gradient(135deg,rgb(2,3,129) 0%,rgb(40,116,252) 100%);--wp--preset--font-size--small: 13px;--wp--preset--font-size--medium: 20px;--wp--preset--font-size--large: 36px;--wp--preset--font-size--x-large: 42px;--wp--preset--spacing--20: 0.44rem;--wp--preset--spacing--30: 0.67rem;--wp--preset--spacing--40: 1rem;--wp--preset--spacing--50: 1.5rem;--wp--preset--spacing--60: 2.25rem;--wp--preset--spacing--70: 3.38rem;--wp--preset--spacing--80: 5.06rem;--wp--preset--shadow--natural: 6px 6px 9px rgba(0, 0, 0, 0.2);--wp--preset--shadow--deep: 12px 12px 50px rgba(0, 0, 0, 0.4);--wp--preset--shadow--sharp: 6px 6px 0px rgba(0, 0, 0, 0.2);--wp--preset--shadow--outlined: 6px 6px 0px -3px rgba(255, 255, 255, 1), 6px 6px rgba(0, 0, 0, 1);--wp--preset--shadow--crisp: 6px 6px 0px rgba(0, 0, 0, 1);}:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}body .is-layout-flex{display: flex;}.is-layout-flex{flex-wrap: wrap;align-items: center;}.is-layout-flex > :is(*, div){margin: 0;}body .is-layout-grid{display: grid;}.is-layout-grid > :is(*, div){margin: 0;}:where(.wp-block-columns.is-layout-flex){gap: 2em;}:where(.wp-block-columns.is-layout-grid){gap: 2em;}:where(.wp-block-post-template.is-layout-flex){gap: 1.25em;}:where(.wp-block-post-template.is-layout-grid){gap: 1.25em;}.has-black-color{color: var(--wp--preset--color--black) !important;}.has-cyan-bluish-gray-color{color: var(--wp--preset--color--cyan-bluish-gray) !important;}.has-white-color{color: var(--wp--preset--color--white) !important;}.has-pale-pink-color{color: var(--wp--preset--color--pale-pink) !important;}.has-vivid-red-color{color: var(--wp--preset--color--vivid-red) !important;}.has-luminous-vivid-orange-color{color: var(--wp--preset--color--luminous-vivid-orange) !important;}.has-luminous-vivid-amber-color{color: var(--wp--preset--color--luminous-vivid-amber) !important;}.has-light-green-cyan-color{color: var(--wp--preset--color--light-green-cyan) !important;}.has-vivid-green-cyan-color{color: var(--wp--preset--color--vivid-green-cyan) !important;}.has-pale-cyan-blue-color{color: var(--wp--preset--color--pale-cyan-blue) !important;}.has-vivid-cyan-blue-color{color: var(--wp--preset--color--vivid-cyan-blue) !important;}.has-vivid-purple-color{color: var(--wp--preset--color--vivid-purple) !important;}.has-black-background-color{background-color: var(--wp--preset--color--black) !important;}.has-cyan-bluish-gray-background-color{background-color: var(--wp--preset--color--cyan-bluish-gray) !important;}.has-white-background-color{background-color: var(--wp--preset--color--white) !important;}.has-pale-pink-background-color{background-color: var(--wp--preset--color--pale-pink) !important;}.has-vivid-red-background-color{background-color: var(--wp--preset--color--vivid-red) !important;}.has-luminous-vivid-orange-background-color{background-color: var(--wp--preset--color--luminous-vivid-orange) !important;}.has-luminous-vivid-amber-background-color{background-color: var(--wp--preset--color--luminous-vivid-amber) !important;}.has-light-green-cyan-background-color{background-color: var(--wp--preset--color--light-green-cyan) !important;}.has-vivid-green-cyan-background-color{background-color: var(--wp--preset--color--vivid-green-cyan) !important;}.has-pale-cyan-blue-background-color{background-color: var(--wp--preset--color--pale-cyan-blue) !important;}.has-vivid-cyan-blue-background-color{background-color: var(--wp--preset--color--vivid-cyan-blue) !important;}.has-vivid-purple-background-color{background-color: var(--wp--preset--color--vivid-purple) !important;}.has-black-border-color{border-color: var(--wp--preset--color--black) !important;}.has-cyan-bluish-gray-border-color{border-color: var(--wp--preset--color--cyan-bluish-gray) !important;}.has-white-border-color{border-color: var(--wp--preset--color--white) !important;}.has-pale-pink-border-color{border-color: var(--wp--preset--color--pale-pink) !important;}.has-vivid-red-border-color{border-color: var(--wp--preset--color--vivid-red) !important;}.has-luminous-vivid-orange-border-color{border-color: var(--wp--preset--color--luminous-vivid-orange) !important;}.has-luminous-vivid-amber-border-color{border-color: var(--wp--preset--color--luminous-vivid-amber) !important;}.has-light-green-cyan-border-color{border-color: var(--wp--preset--color--light-green-cyan) !important;}.has-vivid-green-cyan-border-color{border-color: var(--wp--preset--color--vivid-green-cyan) !important;}.has-pale-cyan-blue-border-color{border-color: var(--wp--preset--color--pale-cyan-blue) !important;}.has-vivid-cyan-blue-border-color{border-color: var(--wp--preset--color--vivid-cyan-blue) !important;}.has-vivid-purple-border-color{border-color: var(--wp--preset--color--vivid-purple) !important;}.has-vivid-cyan-blue-to-vivid-purple-gradient-background{background: var(--wp--preset--gradient--vivid-cyan-blue-to-vivid-purple) !important;}.has-light-green-cyan-to-vivid-green-cyan-gradient-background{background: var(--wp--preset--gradient--light-green-cyan-to-vivid-green-cyan) !important;}.has-luminous-vivid-amber-to-luminous-vivid-orange-gradient-background{background: var(--wp--preset--gradient--luminous-vivid-amber-to-luminous-vivid-orange) !important;}.has-luminous-vivid-orange-to-vivid-red-gradient-background{background: var(--wp--preset--gradient--luminous-vivid-orange-to-vivid-red) !important;}.has-very-light-gray-to-cyan-bluish-gray-gradient-background{background: var(--wp--preset--gradient--very-light-gray-to-cyan-bluish-gray) !important;}.has-cool-to-warm-spectrum-gradient-background{background: var(--wp--preset--gradient--cool-to-warm-spectrum) !important;}.has-blush-light-purple-gradient-background{background: var(--wp--preset--gradient--blush-light-purple) !important;}.has-blush-bordeaux-gradient-background{background: var(--wp--preset--gradient--blush-bordeaux) !important;}.has-luminous-dusk-gradient-background{background: var(--wp--preset--gradient--luminous-dusk) !important;}.has-pale-ocean-gradient-background{background: var(--wp--preset--gradient--pale-ocean) !important;}.has-electric-grass-gradient-background{background: var(--wp--preset--gradient--electric-grass) !important;}.has-midnight-gradient-background{background: var(--wp--preset--gradient--midnight) !important;}.has-small-font-size{font-size: var(--wp--preset--font-size--small) !important;}.has-medium-font-size{font-size: var(--wp--preset--font-size--medium) !important;}.has-large-font-size{font-size: var(--wp--preset--font-size--large) !important;}.has-x-large-font-size{font-size: var(--wp--preset--font-size--x-large) !important;} :where(.wp-block-post-template.is-layout-flex){gap: 1.25em;}:where(.wp-block-post-template.is-layout-grid){gap: 1.25em;} :where(.wp-block-columns.is-layout-flex){gap: 2em;}:where(.wp-block-columns.is-layout-grid){gap: 2em;} :root :where(.wp-block-pullquote){font-size: 1.5em;line-height: 1.6;} </style> <link rel='stylesheet' id='bigfoot-number-css' href='https://citizenlab.ca/wp-content/plugins/bigfoot_footnotes/library/bigfoot-number.css' type='text/css' media='all' /> <link rel='stylesheet' id='__EPYT__style-css' href='https://citizenlab.ca/wp-content/plugins/youtube-embed-plus/styles/ytprefs.min.css' type='text/css' media='all' /> <style id='__EPYT__style-inline-css' type='text/css'> .epyt-gallery-thumb { width: 33.333%; } </style> <link rel='stylesheet' id='bones-base-stylesheet-css' href='https://citizenlab.ca/wp-content/themes/citizenlab-2.1.2/library/css/tachyons.css' type='text/css' media='all' /> <link rel='stylesheet' id='bones-stylesheet-css' href='https://citizenlab.ca/wp-content/themes/citizenlab-2.1.2/library/css/style.css' type='text/css' media='all' /> <!--[if lt IE 9]> <link rel='stylesheet' id='bones-ie-only-css' href='https://citizenlab.ca/wp-content/themes/citizenlab-2.1.2/library/css/ie.css' type='text/css' media='all' /> <![endif]--> <link rel='stylesheet' id='fontawesome-css' href='https://citizenlab.ca/wp-content/themes/citizenlab-2.1.2/library/fontawesome/css/fontawesome.min.css' type='text/css' media='all' /> <link rel='stylesheet' id='fontawesome-brands-css' href='https://citizenlab.ca/wp-content/themes/citizenlab-2.1.2/library/fontawesome/css/brands.min.css' type='text/css' media='all' /> <link rel='stylesheet' id='fontawesome-solid-css' href='https://citizenlab.ca/wp-content/themes/citizenlab-2.1.2/library/fontawesome/css/solid.min.css' type='text/css' media='all' /> <script type="text/javascript" src="https://citizenlab.ca/wp-includes/js/jquery/jquery.min.js" id="jquery-core-js"></script> <script type="text/javascript" src="https://citizenlab.ca/wp-includes/js/jquery/jquery-migrate.min.js" id="jquery-migrate-js"></script> <script type="text/javascript" id="__ytprefs__-js-extra"> /* <![CDATA[ */ var _EPYT_ = {"ajaxurl":"https:\/\/citizenlab.ca\/wp-admin\/admin-ajax.php","security":"75e3448dae","gallery_scrolloffset":"20","eppathtoscripts":"https:\/\/citizenlab.ca\/wp-content\/plugins\/youtube-embed-plus\/scripts\/","eppath":"https:\/\/citizenlab.ca\/wp-content\/plugins\/youtube-embed-plus\/","epresponsiveselector":"[\"iframe.__youtube_prefs__\",\"iframe[src*='youtube.com']\",\"iframe[src*='youtube-nocookie.com']\",\"iframe[data-ep-src*='youtube.com']\",\"iframe[data-ep-src*='youtube-nocookie.com']\",\"iframe[data-ep-gallerysrc*='youtube.com']\"]","epdovol":"1","version":"14.2.1.2","evselector":"iframe.__youtube_prefs__[src], iframe[src*=\"youtube.com\/embed\/\"], iframe[src*=\"youtube-nocookie.com\/embed\/\"]","ajax_compat":"","maxres_facade":"eager","ytapi_load":"light","pause_others":"","stopMobileBuffer":"1","facade_mode":"","not_live_on_channel":"","vi_active":"","vi_js_posttypes":[]}; /* ]]> */ </script> <script type="text/javascript" src="https://citizenlab.ca/wp-content/plugins/youtube-embed-plus/scripts/ytprefs.min.js" id="__ytprefs__-js"></script> <script type="text/javascript" src="https://citizenlab.ca/wp-content/themes/citizenlab-2.1.2/library/js/libs/modernizr.custom.min.js" id="bones-modernizr-js"></script> <link rel='shortlink' href='https://citizenlab.ca/?p=81063' /> <link rel="alternate" title="oEmbed (JSON)" type="application/json+oembed" href="https://citizenlab.ca/wp-json/oembed/1.0/embed?url=https%3A%2F%2Fcitizenlab.ca%2F2024%2F10%2Fshould-we-chat-too-security-analysis-of-wechats-mmtls-encryption-protocol%2F" /> <link rel="alternate" title="oEmbed (XML)" type="text/xml+oembed" href="https://citizenlab.ca/wp-json/oembed/1.0/embed?url=https%3A%2F%2Fcitizenlab.ca%2F2024%2F10%2Fshould-we-chat-too-security-analysis-of-wechats-mmtls-encryption-protocol%2F&format=xml" /> <script type="text/javascript" id="google_gtagjs" src="https://www.googletagmanager.com/gtag/js?id=G-RCDQQLPVF0" async="async"></script> <script type="text/javascript" id="google_gtagjs-inline"> /* <![CDATA[ */ window.dataLayer = window.dataLayer || [];function gtag(){dataLayer.push(arguments);}gtag('js', new Date());gtag('config', 'G-RCDQQLPVF0', {'anonymize_ip': true} ); /* ]]> */ </script> </head> <body itemscope itemtype="http://schema.org/WebPage"> <!-- <div class="mw-12 pv3 ph3 pv3-l ph6-l bg-lab-dark-brown"> --> <header id="header" role="banner" itemscope itemtype="http://schema.org/WPHeader"> <div id="header__inner" class="flex-ns items-center justify-between"> <div class="v-mid flex justify-between items-center"> <div class="mr-auto"> <a href="https://citizenlab.ca" rel="nofollow" id="logo" itemscope itemtype="http://schema.org/Organization"> <img src="https://citizenlab.ca/wp-content/themes/citizenlab-2.1.2/library/images/CL-logo-3-headed.png" alt="The Citizen Lab"/> </a> <img src="https://citizenlab.ca/wp-content/themes/citizenlab-2.1.2/library/images/MunkSchool-WHT.png" class="munk-logo" alt="Munk School of Global Affairs & Public Policy | University of Toronto" /> </div> <!-- Visible on mobile --> <a href="#main-menu" id="homepage" aria-label="Open main menu"> <span class="fa-solid fa-bars-staggered white dib" title="Open Menu"></span> <span class="screen-reader-text">Open main menu</span> </a> </div> <!-- Main navigation menu --> <a class="skip-main" href="#main">Skip to main content</a> <div class="flex-ns main-menu" id="main-menu"> <a href="#homepage" id="homepage" class="menu-close" aria-label="Close main menu"> <span class="fa-solid fa-x white dib" title="Close Menu"></span> <span class="screen-reader-text">Close main menu</span> </a> <nav id="nav-main" role="navigation" itemscope itemtype="http://schema.org/SiteNavigationElement" class="tc tl-l"> <ul id="menu-top-menu" class="list ma0 mt2 mt0-ns pa0 b dib-ns"><li id="menu-item-29705" class="menu-item menu-item-type-taxonomy menu-item-object-category current-post-ancestor menu-item-has-children menu-item-29705 dib-ns f5-l f4 ttu pv2 "><a href="https://citizenlab.ca/category/research/" class="white no-underline h-underline pr2 ml0">Research</a> <ul class="sub-menu"> <li id="menu-item-72358" class="menu-item menu-item-type-taxonomy menu-item-object-category menu-item-72358 dib-ns f5-l f4 ttu pv2 "><a href="https://citizenlab.ca/category/research/targeted-threats/" class="white no-underline h-underline pr2">Targeted Threats</a></li> <li id="menu-item-72357" class="menu-item menu-item-type-taxonomy menu-item-object-category menu-item-72357 dib-ns f5-l f4 ttu pv2 "><a href="https://citizenlab.ca/category/research/free-expression-online/" class="white no-underline h-underline pr2 mr0">Free Expression Online</a></li> <li id="menu-item-72359" class="menu-item menu-item-type-taxonomy menu-item-object-category menu-item-72359 dib-ns f5-l f4 ttu pv2 "><a href="https://citizenlab.ca/category/research/transparency/" class="white no-underline h-underline pr2">Transparency and Accountability</a></li> <li id="menu-item-72360" class="menu-item menu-item-type-taxonomy menu-item-object-category current-post-ancestor current-menu-parent current-post-parent menu-item-72360 dib-ns f5-l f4 ttu pv2 "><a href="https://citizenlab.ca/category/research/app-privacy-and-security/" class="white no-underline h-underline pr2">App Privacy and Controls</a></li> <li id="menu-item-72362" class="menu-item menu-item-type-taxonomy menu-item-object-category menu-item-72362 dib-ns f5-l f4 ttu pv2 "><a href="https://citizenlab.ca/category/research/global-research-network/" class="white no-underline h-underline pr2">Global Research Network</a></li> <li id="menu-item-72385" class="menu-item menu-item-type-taxonomy menu-item-object-category menu-item-72385 dib-ns f5-l f4 ttu pv2 "><a href="https://citizenlab.ca/category/research/tools-resources/" class="white no-underline h-underline pr2">Tools & Resources</a></li> <li id="menu-item-72361" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-72361 dib-ns f5-l f4 ttu pv2 "><a href="https://citizenlab.ca/publications/" class="white no-underline h-underline pr2">Publications</a></li> </ul> </li> <li id="menu-item-29706" class="menu-item menu-item-type-taxonomy menu-item-object-category menu-item-has-children menu-item-29706 dib-ns f5-l f4 ttu pv2 "><a href="https://citizenlab.ca/category/lab-news/" class="white no-underline h-underline pr2">News</a> <ul class="sub-menu"> <li id="menu-item-72363" class="menu-item menu-item-type-taxonomy menu-item-object-category menu-item-72363 dib-ns f5-l f4 ttu pv2 "><a href="https://citizenlab.ca/category/lab-news/mentions/" class="white no-underline h-underline pr2">In the Media</a></li> <li id="menu-item-72364" class="menu-item menu-item-type-taxonomy menu-item-object-category menu-item-72364 dib-ns f5-l f4 ttu pv2 "><a href="https://citizenlab.ca/category/lab-news/events/" class="white no-underline h-underline pr2">Events</a></li> <li id="menu-item-72365" class="menu-item menu-item-type-taxonomy menu-item-object-category menu-item-72365 dib-ns f5-l f4 ttu pv2 "><a href="https://citizenlab.ca/category/lab-news/opportunities/" class="white no-underline h-underline pr2">Opportunities</a></li> </ul> </li> <li id="menu-item-29707" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-has-children menu-item-29707 dib-ns f5-l f4 ttu pv2 "><a href="https://citizenlab.ca/about/" class="white no-underline h-underline pr2">About</a> <ul class="sub-menu"> <li id="menu-item-72367" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-72367 dib-ns f5-l f4 ttu pv2 "><a href="https://citizenlab.ca/about/" class="white no-underline h-underline pr2">About The Citizen Lab</a></li> <li id="menu-item-72368" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-72368 dib-ns f5-l f4 ttu pv2 "><a href="https://citizenlab.ca/media/" class="white no-underline h-underline pr2">Media Resources</a></li> <li id="menu-item-72369" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-72369 dib-ns f5-l f4 ttu pv2 "><a href="https://citizenlab.ca/people/" class="white no-underline h-underline pr2">People</a></li> <li id="menu-item-72370" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-72370 dib-ns f5-l f4 ttu pv2 "><a href="https://citizenlab.ca/teaching/" class="white no-underline h-underline pr2">Teaching</a></li> <li id="menu-item-72387" class="menu-item menu-item-type-custom menu-item-object-custom menu-item-72387 dib-ns f5-l f4 ttu pv2 "><a href="https://engage.utoronto.ca/site/SPageServer?pagename=donate#/fund/847" class="white no-underline h-underline pr2">Donate</a></li> <li id="menu-item-74537" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-74537 dib-ns f5-l f4 ttu pv2 "><a href="https://citizenlab.ca/disclosure-of-security-vulnerabilities/" class="white no-underline h-underline pr2">Security Vulnerabilities</a></li> </ul> </li> </ul> </nav> <!-- Search bar --> <div class="flex items-start justify-center searchbar"> <form class="db-l ma0 pa0 b0 lh0 f5" role="search" method="get" id="menuSearchform" action="https://citizenlab.ca/"> <div id="menuSearchContainer" class="ml3 dib w0 transition-width overflow-hidden"> <input type="search" id="menuSearch" name="s" value="" class="b--none ma0 pa1 w-100" placeholder="Search"/> </div> <!--end of menuSearchContainer--> </form> <div id="menuSearchButton" class="db-l ml3 pointer items-end"> <span class="fa-solid fa-magnifying-glass white f5" aria-label="Search" title="Search"></span> </div> </div> <!--end of searchbar--> </div> <!--end of main-menu --> </div> <!-- end of header__inner --> </header> <!-- </div> --> <div id="container" class="pa3 pv4-l ph5-l"> <!--TODO move to stylesheet --> <main id="main" role="main" itemscope itemprop="mainContentOfPage" itemtype="http://schema.org/Blog"> <section id="content" class="container"> <article id="post-81063" dir="ltr" 81063role="article" itemscope itemprop="blogPost" itemtype="http://schema.org/BlogPosting" class="lh-copy"> <header> <span class="f6 mt0" dir="ltr"><a href="https://citizenlab.ca/category/research/" class="breadcrumbs"><a href="https://citizenlab.ca/category/research/" class="breadcrumbs">Research</a><span class="fa-solid fa-chevron-right mh2" aria-hidden="true"></span></a><a href="https://citizenlab.ca/category/research/app-privacy-and-security/" class="breadcrumbs">App Privacy and Controls</a></span> <h1 itemprop="headline" rel="bookmark" class="ma0 mt5 lh-title"> <span class="db f2 f1-ns black lh-solid mb2-ns no-hyphen">Should We Chat, Too? Security Analysis of WeChat’s MMTLS Encryption Protocol</span> </h1> <div dir="ltr" class="mt2"> <div class="f5 mr4 b dark-gray dib">By <a href="https://citizenlab.ca/author/monaw/" title="Posts by Mona Wang" class="author url fn" rel="author">Mona Wang</a>, <a href="https://citizenlab.ca/author/pellaeon/" title="Posts by Pellaeon Lin" class="author url fn" rel="author">Pellaeon Lin</a>, and <a href="https://citizenlab.ca/author/jknockel/" title="Posts by Jeffrey Knockel" class="author url fn" rel="author">Jeffrey Knockel</a></div> <time class="dark-gray dib f5 mr4" datetime="2024-10-15" itemprop="datePublished">October 15, 2024</time> <!-- Display other versions of the post --> <div id="other_version_of_post" class="dib f5"> <p><a href="https://citizenlab.ca/2024/10/【我們繼續聊天?】繁體中文摘要-zh-tw/">閱讀繁體中文摘要</a></p> </div> </div> <!-- Display the link for the PDF version of the post --> </header> <section itemprop="articleBody" class="article-body mb4 mt4 pt2 bt b--light-gray"> <div class="box box--info">This report’s <a href="https://citizenlab.ca/2024/10/%E3%80%90%E6%88%91%E5%80%91%E7%B9%BC%E7%BA%8C%E8%81%8A%E5%A4%A9%EF%BC%9F%E3%80%91%E7%B9%81%E9%AB%94%E4%B8%AD%E6%96%87%E6%91%98%E8%A6%81-zh-tw/" class="pointer">key findings are translated into Chinese</a>, it has an <a title="Should We Chat Too FAQ" href="https://citizenlab.ca/2024/10/should-we-chat-too-faq/" target="_blank" rel="noopener" class="pointer">accompanying FAQ</a>, and the FAQ is translated into <a href="https://citizenlab.ca/2024/10/%E6%88%91%E4%BB%AC%E7%BB%A7%E7%BB%AD%E8%81%8A%E5%A4%A9%EF%BC%9F%E5%B8%B8%E9%97%AE%E9%97%AE%E9%A2%98-zh-cn/" class="pointer">Simplified</a> and <a href="https://citizenlab.ca/2024/10/%E6%88%91%E5%80%91%E7%B9%BC%E7%BA%8C%E8%81%8A%E5%A4%A9%EF%BC%9F%E5%B8%B8%E8%A6%8B%E5%95%8F%E9%A1%8C-zh_tw/" class="pointer">Traditional Chinese</a>.</div> <h1 id="key-contributions" class="lh-solid mb3">Key contributions</h1> <ul class="mt0"> <li class="mt2">We performed the first public analysis of the security and privacy properties of MMTLS, the main network protocol used by WeChat, an app with over one billion monthly active users.</li> <li class="mt2">We found that MMTLS is a modified version of TLS 1.3, with many of the modifications that WeChat developers made to the cryptography introducing weaknesses.</li> <li class="mt2">Further analysis revealed that earlier versions of WeChat used a less secure, custom-designed protocol that contains multiple vulnerabilities, which we describe as “Business-layer encryption”. This layer of encryption is still being used in addition to MMTLS in modern WeChat versions.</li> <li class="mt2">Although we were unable to develop an attack to completely defeat WeChat’s encryption, the implementation is inconsistent with the level of cryptography you would expect in an app used by a billion users, such as its use of deterministic IVs and lack of forward secrecy.</li> <li class="mt2">These findings contribute to a larger body of work that suggests that apps in the Chinese ecosystem fail to adopt cryptographic best practices, opting instead to invent their own, often problematic systems.</li> <li class="mt2">We are releasing technical tools and further documentation of our technical methodologies in an accompanying <a href="https://github.com/citizenlab/wechat-security-report/" class="pointer" target="_blank" rel="noopener">Github repository</a>. These tools and documents, along with this main report, will assist future researchers to study WeChat’s inner workings.</li> </ul> <h1 id="introduction" class="lh-solid mb3">Introduction</h1> <p class="mt0">WeChat, with over <a href="https://www.messengerpeople.com/global-messenger-usage-statistics" class="pointer" target="_blank" rel="noopener"><u>1.2 billion monthly active users</u></a>, stands as the most popular messaging and social media platform in China and third globally. As indicated by market research, WeChat’s network traffic <a href="https://walkthechat.com/wechat-impact-report-2016/" class="pointer" target="_blank" rel="noopener"><u>accounted for 34%</u></a> of Chinese mobile traffic in 2018. WeChat’s dominance has monopolized messaging in China, making it increasingly unavoidable for those in China to use. With an ever-expanding array of features, WeChat has also grown beyond its original purpose as a messaging app.</p> <p>Despite the universality and importance of WeChat, there has been little study of the proprietary network encryption protocol, MMTLS, used by the WeChat application. This knowledge gap serves as a barrier for researchers in that it hampers additional security and privacy study of such a critical application. In addition, <a href="https://citizenlab.ca/2016/02/privacy-security-issues-baidu-browser/" class="pointer"><u>home</u></a>–<a href="https://citizenlab.ca/2016/03/privacy-security-issues-qq-browser/" class="pointer"><u>rolled</u></a> <a href="https://arxiv.org/abs/1802.03367" class="pointer" target="_blank" rel="noopener"><u>cryptography</u></a> <a href="https://citizenlab.ca/2015/05/a-chatty-squirrel-privacy-and-security-issues-with-uc-browser/" class="pointer"><u>is</u></a> <a href="https://citizenlab.ca/2016/08/a-tough-nut-to-crack-look-privacy-and-security-issues-with-uc-browser/" class="pointer"><u>unfortunately</u></a> <a href="https://www.usenix.org/conference/foci16/workshop-program/presentation/knockel" class="pointer" target="_blank" rel="noopener"><u>common</u></a> <a href="https://citizenlab.ca/2020/04/move-fast-roll-your-own-crypto-a-quick-look-at-the-confidentiality-of-zoom-meetings/" class="pointer"><u>in</u></a> <a href="https://citizenlab.ca/2022/01/cross-country-exposure-analysis-my2022-olympics-app/" class="pointer"><u>many</u></a> incredibly popular Chinese applications, and there have historically <a href="https://citizenlab.ca/2023/08/vulnerabilities-in-sogou-keyboard-encryption/" class="pointer"><u>been</u></a> <a href="https://citizenlab.ca/2024/04/vulnerabilities-across-keyboard-apps-reveal-keystrokes-to-network-eavesdroppers/" class="pointer"><u>issues</u></a> with cryptosystems developed independently of well-tested standards such as TLS.</p> <p>This work is a deep dive into the mechanisms behind MMTLS and the core workings of the WeChat program. We compare the security and performance of MMTLS to TLS 1.3 and discuss our overall findings. We also provide public documentation and tooling to decrypt WeChat network traffic. These tools and documents, along with our report, will assist future researchers to study WeChat’s privacy and security properties, as well as its other inner workings.</p> <p>This report consists of a technical description of <a href="#launching-a-wechat-network-request" class="pointer">how WeChat launches a network request</a> and its <a href="#wechat-network-request-encryption" class="pointer">encryption protocols</a>, followed by a <a href="#security-issues" class="pointer">summary of weaknesses in WeChat’s protocol</a>, and finally a <a href="#discussion" class="pointer">high-level discussion</a> of WeChat’s design choices and their impact. The report is intended for privacy, security, or other technical researchers interested in furthering the privacy and security study of WeChat. For non-technical audiences, we have summarized our findings in this FAQ.</p> <h3 id="prior-work-on-mmtls-and-wechat-transport-security" class="lh-solid mb3">Prior work on MMTLS and WeChat transport security</h3> <p class="mt0">Code internal to the WeChat mobile app refers to its proprietary TLS stack as MMTLS (MM is short for MicroMessenger, which is a direct translation of 微信, the Chinese name for WeChat) and uses it to encrypt the bulk of its traffic.</p> <p>There is limited public documentation of the MMTLS protocol. This <a href="https://github.com/WeMobileDev/article/blob/master/%E5%9F%BA%E4%BA%8ETLS1.3%E7%9A%84%E5%BE%AE%E4%BF%A1%E5%AE%89%E5%85%A8%E9%80%9A%E4%BF%A1%E5%8D%8F%E8%AE%AEmmtls%E4%BB%8B%E7%BB%8D.md" class="pointer" target="_blank" rel="noopener"><u>technical document</u></a> from WeChat developers describes in which ways it is similar and different from TLS 1.3, and attempts to justify various decisions they made to either simplify or change how the protocol is used. In this document, there are various key differences they identify between MMTLS and TLS 1.3, which help us understand the various modes of usage of MMTLS.</p> <p><a href="https://wenku.baidu.com/view/67762def4b35eefdc9d33374.html" class="pointer" target="_blank" rel="noopener"><u>Wan et al.</u></a> conducted the most comprehensive study of WeChat transport security in 2015 using standard security analysis techniques. However, this analysis was performed before the deployment of MMTLS, WeChat’s upgraded security protocol. In 2019, <a href="https://link.springer.com/chapter/10.1007/978-3-030-24268-8_27" class="pointer" target="_blank" rel="noopener"><u>Chen et al.</u></a> studied the login process of WeChat and specifically studied packets that are encrypted with TLS and not MMTLS.</p> <p>As for MMTLS itself, in 2016 WeChat developers published <a href="https://github.com/WeMobileDev/article/blob/master/%E5%9F%BA%E4%BA%8ETLS1.3%E7%9A%84%E5%BE%AE%E4%BF%A1%E5%AE%89%E5%85%A8%E9%80%9A%E4%BF%A1%E5%8D%8F%E8%AE%AEmmtls%E4%BB%8B%E7%BB%8D.md" class="pointer" target="_blank" rel="noopener"><u>a document</u></a> describing the design of the protocol at a high level that compares the protocol with TLS 1.3. Other MMTLS publications focus on <a href="https://ieeexplore.ieee.org/abstract/document/8711267" class="pointer" target="_blank" rel="noopener"><u>website fingerprinting-type</u></a> <a href="https://ieeexplore.ieee.org/abstract/document/8456067" class="pointer" target="_blank" rel="noopener"><u>attacks</u></a>, but none specifically perform a security evaluation. A few <a href="https://github.com/anonymous5l/mmtls" class="pointer" target="_blank" rel="noopener"><u>Github repositories</u></a> and <a href="https://bbs.pediy.com/thread-257942.htm" class="pointer" target="_blank" rel="noopener"><u>blog posts</u></a> look briefly into the wire format of MMTLS, though none are comprehensive. Though there has been little work studying MMTLS specifically, previous Citizen Lab reports have discovered security flaws of <a href="https://citizenlab.ca/2016/03/privacy-security-issues-qq-browser/" class="pointer"><u>other</u></a> <a href="https://arxiv.org/abs/1802.03367" class="pointer" target="_blank" rel="noopener"><u>cryptographic</u></a> protocols designed and implemented by Tencent.</p> <h1 id="methodology" class="lh-solid mb3">Methodology</h1> <p class="mt0">We analyzed two versions of WeChat Android app:</p> <ul> <li class="mt2">Version 8.0.23 (APK “versionCode” 2160) released on May 26, 2022, downloaded from the WeChat website.</li> <li class="mt2">Version 8.0.21 (APK “versionCode” 2103) released on April 7, 2022, downloaded from Google Play Store.</li> </ul> <p>All findings in this report apply to both of these versions.</p> <p>We used an account registered to a U.S. phone number for the analysis, which changes the behavior of the application compared to a mainland Chinese number. Our setup may not be representative of all WeChat users, and the full limitations are discussed further below.</p> <p>For dynamic analysis, we analyzed the application installed on a rooted Google Pixel 4 phone and an emulated Android OS. We used <a href="https://frida.re/" class="pointer" target="_blank" rel="noopener"><u>Frida</u></a> to hook the app’s functions and manipulate and export application memory. We also performed network analysis of WeChat’s network traffic using <a href="https://www.wireshark.org/" class="pointer" target="_blank" rel="noopener"><u>Wireshark</u></a>. However, due to WeChat’s use of nonstandard cryptographic libraries like MMTLS, standard network traffic analysis tools that might work with HTTPS/TLS do not work for all of WeChat’s network activity. Our use of Frida was paramount for capturing the data and information flows we detail in this report. These Frida scripts are designed to intercept WeChat’s request data immediately before WeChat sends it to its MMTLS encryption module. The Frida scripts we used are published in <a href="https://github.com/citizenlab/wechat-report-data" class="pointer" target="_blank" rel="noopener"><u>our Github repository</u></a>.</p> <p>For static analysis, we used <a href="https://github.com/skylot/jadx" class="pointer" target="_blank" rel="noopener"><u>Jadx</u></a>, a popular Android decompiler, to decompile WeChat’s Android Dex files into Java code. We also used <a href="https://ghidra-sre.org/" class="pointer" target="_blank" rel="noopener"><u>Ghidra</u></a> and <a href="https://hex-rays.com/ida-pro/" class="pointer" target="_blank" rel="noopener"><u>IDA Pro</u></a> to decompile the native libraries (written in C++) bundled with WeChat.</p> <h2 id="notation" class="lh-solid mb3">Notation</h2> <p class="mt0">In this report, we reference a lot of code from the WeChat app. When we reference any code (including file names and paths), we will style the text using <code>monospace fonts</code> to indicate it is code. If a function is referenced, we will add empty parentheses after the function name, like this: <code>somefunction()</code>. The names of variables and functions that we show may come from one of the three following:</p> <ol type="1"> <li class="mt2">The original decompiled name.</li> <li class="mt2">In cases where the name cannot be decompiled into a meaningful string (e.g., the symbol name was not compiled into the code), we rename it according to how the nearby internal log messages reference it.</li> <li class="mt2">In cases where there is not enough information for us to tell the original name, we name it according to our understanding of the code. In such cases, we will note that these names are given by us.</li> </ol> <p>In the cases where the decompiled name and log message name of functions are available, they are generally consistent. Bolded or italicized terms can refer to higher-level concepts or parameters we have named.</p> <h2 id="utilization-of-open-source-components" class="lh-solid mb3">Utilization of open source components</h2> <p class="mt0">We also identified open source components being used by the project, the two largest being <a href="https://openssl-library.org/" class="pointer" target="_blank" rel="noopener"><u>OpenSSL</u></a> and <a href="https://github.com/Tencent/mars" class="pointer" target="_blank" rel="noopener"><u>Tencent Mars</u></a>. Based on our analysis of decompiled WeChat code, large parts of its code are identical to Mars. Mars is an “infrastructure component” for mobile applications, providing common features and abstractions that are needed by mobile applications, such as networking and logging.</p> <p>By compiling these libraries separately with debug symbols, we were able to import function and class definitions into Ghidra for further analysis. This helped tremendously to our understanding of other non-open-source code in WeChat. For instance, when we were analyzing the network functions decompiled from WeChat, we found a lot of them to be highly similar to the open source Mars, so we could just read the source code and comments to understand what a function was doing. What was not included in open source Mars are encryption related functions, so we still needed to read decompiled code, but even in these cases we were aided by various functions and structures that we already know from the open source Mars.</p> <h3 id="matching-decompiled-code-to-its-source" class="lh-solid mb3">Matching decompiled code to its source</h3> <p class="mt0">In the internal logging messages of WeChat, which contain source file paths, we noticed three top level directories, which we have highlighted below:</p> <ul> <li class="mt2"><code>/home/android/devopsAgent/workspace/p-e118ef4209d745e1b9ea0b1daa0137ab/src/<mark>mars</mark>/</code></li> <li class="mt2"><code>/home/android/devopsAgent/workspace/p-e118ef4209d745e1b9ea0b1daa0137ab/src/<mark>mars-wechat</mark>/</code></li> <li class="mt2"><code>/home/android/devopsAgent/workspace/p-e118ef4209d745e1b9ea0b1daa0137ab/src/<mark>mars-private</mark>/</code></li> </ul> <p>The source files under “mars” can all be found in the <a href="https://github.com/Tencent/mars" class="pointer" target="_blank" rel="noopener"><u>open source Mars repository</u></a> as well, while source files in the other two top level directories cannot be found in the open source repository. To illustrate, below is a small section of decompiled code from <code>libwechatnetwork.so</code> :</p> <div> <pre class="box box--code pre"> XLogger::XLogger((XLogger *)&local_2c8,5,"mars::stn", "/home/android/devopsAgent/workspace/p-e118ef4209d745e1b9ea0b1daa0137ab/src/mars/mars/stn/src/longlink.cc" ,"Send",0xb2,false,(FuncDef0 *)0x0); XLogger::Assert((XLogger *)&local_2c8,"tracker_.get()"); XLogger::~XLogger((XLogger *)&local_2c8); </pre> </div> <p>From its similarity, is highly likely that this section of code was compiled from <a href="https://github.com/Tencent/mars/blob/1583548111ed055836bdc2a344e45084ec775e6d/mars/stn/src/longlink.cc#L178" class="pointer" target="_blank" rel="noopener"><u>this line</u></a> in the Send() function, defined in longlink.cc file from the open source repository:</p> <div class="box box--code"><code>xassert2(tracker_.get());</code></div> <p>Reusing this observation, whenever our decompiler is unable to determine the name of a function, we can use logging messages within the compiled code to determine its name. Moreover, if the source file is from open source Mars, we can read its source code as well.</p> <h3 id="three-parts-of-mars" class="lh-solid mb3">Three parts of Mars</h3> <p class="mt0">In a few articles on the <a href="https://github.com/Tencent/mars/wiki" class="pointer" target="_blank" rel="noopener"><u>Mars wiki</u></a>, Tencent developers provided the following motivations to develop Mars:</p> <ul> <li class="mt2">The need for a <a href="https://cloud.tencent.com/developer/article/1005495" class="pointer" target="_blank" rel="noopener"><u>cross-platform</u></a> networking library, to reduce the development and maintenance costs of two separate network libraries on Android and iOS.</li> <li class="mt2">The need to <a href="http://mp.weixin.qq.com/s?__biz=MzAwNDY1ODY2OQ==&mid=2649286458&idx=1&sn=320f690faa4f97f7a49a291d4de174a9&chksm=8334c3b8b4434aae904b6d590027b100283ef175938610805dd33ca53f004bd3c56040b11fa6#rd" class="pointer" target="_blank" rel="noopener"><u>customize parameters of the TCP handshake process</u></a>, in order for faster connection establishment.</li> </ul> <p>According to its developers, Mars and its STN module are comparable to networking libraries such as <a href="https://github.com/AFNetworking/AFNetworking" class="pointer" target="_blank" rel="noopener"><u>AFNetworking</u></a> and <a href="https://square.github.io/okhttp/" class="pointer" target="_blank" rel="noopener"><u>OkHttp</u></a>, which are widely used in other mobile apps.</p> <p><a href="https://github.com/WeMobileDev/article/blob/master/%E5%BE%AE%E4%BF%A1%E7%BB%88%E7%AB%AF%E8%B7%A8%E5%B9%B3%E5%8F%B0%E7%BB%84%E4%BB%B6%20Mars%20%E7%B3%BB%E5%88%97%20-%20%E6%88%91%E4%BB%AC%E5%A6%82%E7%BA%A6%E8%80%8C%E8%87%B3.md?plain=1" class="pointer" target="_blank" rel="noopener"><u>One of the technical articles</u></a> released by the WeChat development team wrote about the process of open-sourcing Mars. According to the article, they had to separate WeChat-specific code, which was kept private, from the general use code, which was open sourced. In the end, three parts were separated from each other:</p> <ul> <li class="mt2">mars-open: to be open sourced, independent repository.</li> <li class="mt2">mars-private: potentially open sourced, depends on mars-open.</li> <li class="mt2">mars-wechat: WeChat business logic code, depends on mars-open and mars-private.</li> </ul> <p>These three names match the top level directories we found earlier if we take “mars-open” to be in the “mars” top-level directory. Using this knowledge, when reading decompiled WeChat code, we could easily know whether it was WeChat-specific or not. From our reading of the code, mars-open contains basic and generic structures and functions, for instance, <a href="https://github.com/Tencent/mars/blob/master/mars/comm/autobuffer.cc" class="pointer" target="_blank" rel="noopener"><u>buffer structures</u></a>, <a href="https://github.com/Tencent/mars/blob/6c71f72ff770f8a2b111ef27f1ccf72511801bbd/mars/comm/ini.h#L60" class="pointer" target="_blank" rel="noopener"><u>config stores</u></a>, <a href="https://github.com/Tencent/mars/tree/6c71f72ff770f8a2b111ef27f1ccf72511801bbd/mars/comm/unix/thread" class="pointer" target="_blank" rel="noopener"><u>thread management</u></a> and, most importantly, the module named “STN” responsible for network transmission. (We were unable to determine what STN stands for.) On the other hand, mars-wechat contains the MMTLS implementation, and mars-private is not closely related to the features within our research scope.</p> <p>As a technical side note, the open source Mars <a href="https://github.com/Tencent/mars/wiki/Mars-Android-%E6%8E%A5%E5%85%A5%E6%8C%87%E5%8D%97#%E6%9C%AC%E5%9C%B0%E7%BC%96%E8%AF%91" class="pointer" target="_blank" rel="noopener"><u>compiles</u></a> to just one object file named “libmarsstn.so”. However, in WeChat, multiple shared object files reference code within the open source Mars, including the following:</p> <ul> <li class="mt2"><code>libwechatxlog.so</code></li> <li class="mt2"><code>libwechatbase.so</code></li> <li class="mt2"><code>libwechataccessory.so</code></li> <li class="mt2"><code>libwechathttp.so</code></li> <li class="mt2"><code>libandromeda.so</code></li> <li class="mt2"><code>libwechatmm.so</code></li> <li class="mt2"><code>libwechatnetwork.so</code></li> </ul> <p>Our research focuses on the transport protocol and encryption of WeChat, which is implemented mainly in libwechatmm.so and libwechatnetwork.so. In addition, we inspected libMMProtocalJni.so, which is not part of Mars but contains functions for cryptographic calculations. We did not inspect the other shared object files.</p> <h3 id="matching-mars-versions" class="lh-solid mb3">Matching Mars versions</h3> <p class="mt0">Despite being able to find open source code to parts of WeChat, in the beginning of our research, we were unable to pinpoint the specific version of the source code of mars-open that was used to build WeChat. Later, we found version strings contained in <code>libwechatnetwork.so</code>. For WeChat 8.0.21, searching for the string “MARS_” yielded the following:</p> <div class="box box--code">MARS_BRANCH: HEAD<br> MARS_COMMITID: d92f1a94604402cf03939dc1e5d3af475692b551<br> MARS_PRIVATE_BRANCH: HEAD<br> MARS_PRIVATE_COMMITID: 193e2fb710d2bb42448358c98471cd773bbd0b16<br> MARS_URL:<br> MARS_PATH: HEAD<br> MARS_REVISION: d92f1a9<br> MARS_BUILD_TIME: 2022-03-28 21:52:49<br> MARS_BUILD_JOB: rb/2022-MAR-p-e118ef4209d745e1b9ea0b1daa0137ab-22.3_1040</div> <p>The specific MARS_COMMITID (d92f1a…) exists in the open source Mars repository. This version of the source code also matches the decompiled code.</p> <p>Pinpointing the specific source code version helped us tremendously with Ghidra’s decompilation. Since a lot of the core data structures used in WeChat are from Mars, by importing the known data structures, we can observe the non-open-sourced code accessing structure fields, and inferring its purpose.</p> <h2 id="limitations" class="lh-solid mb3">Limitations</h2> <p class="mt0">This investigation only looks at client behavior and is therefore subject to other common limitations in privacy research that can only perform client analysis. Much of the data that the client transmits to WeChat servers may be required for functionality of the application. For instance, WeChat servers can certainly see chat messages since WeChat can censor them according to their content. We cannot always measure what Tencent is doing with the data that they collect, but we can make inferences about what is possible. <a href="https://citizenlab.ca/2020/05/we-chat-they-watch/" class="pointer"><u>Previous work</u></a> has made certain limited inferences about data sharing, such as that messages sent by non-mainland-Chinese users are used to train censorship algorithms for mainland Chinese users. In this report, we focus on the version of WeChat for non-mainland-Chinese users.</p> <p>Our investigation was also limited due to legal and ethical constraints. It has become increasingly difficult to obtain Chinese phone numbers for investigation due to the strict phone number and associated government ID requirements. Therefore, we did not test on Chinese phone numbers, which causes WeChat to behave differently. In addition, without a mainland Chinese account, the types of interaction with certain features and Mini Programs were limited. For instance, we did not perform financial transactions on the application.</p> <p>Our primary analysis was limited to analyzing only two versions of WeChat Android (8.0.21 and 8.0.23). However, we also re-confirmed our tooling works on WeChat 8.0.49 for Android (released April 2024) and that the MMTLS network format matches that used by WeChat 8.0.49 for iOS. Testing different versions of WeChat, the backwards-compatibility of the servers with older versions of the application, and testing on a variety of Android operating systems with variations in API version, are great avenues for future work.</p> <p>Within the WeChat Android app, we focused on its networking components. Usually, within a mobile application (and in most other programs as well), all other components will defer the work of communicating over the network to the networking components. Our research is not a complete security and privacy audit of the WeChat app, as even if the network communication is properly protected, other parts of the app still need to be secure and private. For instance, an app would not be secure if the server accepts any password to an account login, even if the password is confidentially transmitted.</p> <h2 id="tooling-for-studying-wechat-and-mmtls" class="lh-solid mb3">Tooling for studying WeChat and MMTLS</h2> <p class="mt0">In the <a href="https://github.com/citizenlab/wechat-security-report/" class="pointer" target="_blank" rel="noopener"><u>Github repository</u></a>, we have released tooling that can log keys using Frida and decrypt network traffic that is captured during the same period of time, as well as samples of decrypted payloads. In addition, we have provided additional documentation and our reverse-engineering notes from studying the protocol. We hope that these tools and documentation will further aid researchers in the study of WeChat.</p> <h1 id="launching-a-wechat-network-request" class="lh-solid mb3">Launching a WeChat network request</h1> <p class="mt0">As with any other apps, WeChat is composed of various components. Components within WeChat can invoke the networking components to send or receive network transmissions. In this section, we provide a highly simplified description of the process and components surrounding sending a network request in WeChat. The actual process is much more complex, which we explain in more detail in a <a href="https://github.com/citizenlab/wechat-security-report/blob/main/docs/networking_components.md" class="pointer" target="_blank" rel="noopener"><u>separate document</u></a>. The specifics of data encryption is discussed in the next section “WeChat network request encryption”.</p> <p>In the WeChat source code, each API is referred to as a different “Scene”. For instance, during the registration process, there is one API that submits all new account information provided by the user, called <code>NetSceneReg</code>. <code>NetSceneReg</code> is referred to by us as a “Scene class”, Other components could start a network request towards an API by calling the particular Scene class. In the case of <code>NetSceneReg</code>, it is usually invoked by a click event of a button UI component.</p> <p>Upon invocation, the Scene class would prepare the request data. The structure of the request data (as well as the response) is defined in “RR classes”. (We dub them RR classes because they tend to have “ReqResp” in their names.) Usually, one Scene class would correspond to one RR class. In the case of <code>NetSceneReg</code>, it corresponds to the RR class <code>MMReqRespReg2</code>, and contains fields like the desired username and phone number. For each API, its RR class also defines a unique internal URI (usually starting with “/cgi-bin”) and a “request type” number (an approximately 2–4 digit integer). The internal URI and request type number is often used throughout the code to identify different APIs. Once the data is prepared by the Scene class, it is sent to <code>MMNativeNetTaskAdapter</code>.</p> <p><code>MMNativeNetTaskAdapter</code> is a task queue manager, it manages and monitors the progress of each network connection and API requests. When a Scene Class calls <code>MMNativeNetTaskAdapter</code>, it places the new request (a task) onto the task queue, and calls the req2Buf() function. req2Buf() serializes the request <a href="https://protobuf.dev/" class="pointer" target="_blank" rel="noopener"><u>Protobuf</u></a> object that was prepared by the Scene Class into bytes, then encrypts the bytes using <em>Business-layer Encryption</em>.</p> <p>Finally, the resultant ciphertext from Business-layer encryption is sent to the “STN” module, which is part of Mars. STN then encrypts the data again using <em>MMTLS Encryption</em>. Then, STN establishes the network transport connection, and sends the MMTLS Encryption ciphertext over it. In STN, there are two types of transport connections: <em>Shortlink</em> and <em>Longlink</em>. Shortlink refers to an HTTP connection that carries MMTLS ciphertext. Shortlink connections are closed after one request-response cycle. Longlink refers to a long-lived TCP connection. A Longlink connection can carry multiple MMTLS encrypted requests and responses without being closed.</p> <h1 id="wechat-network-request-encryption" class="lh-solid mb3">WeChat network request encryption</h1> <p class="mt0">WeChat network requests are encrypted twice, with different sets of keys. Serialized request data is first encrypted using what we call the <em>Business-layer Encryption</em>, as internal encryption is referred to in this <a href="https://github.com/WeMobileDev/article/blob/master/%E5%9F%BA%E4%BA%8ETLS1.3%E7%9A%84%E5%BE%AE%E4%BF%A1%E5%AE%89%E5%85%A8%E9%80%9A%E4%BF%A1%E5%8D%8F%E8%AE%AEmmtls%E4%BB%8B%E7%BB%8D.md" class="pointer" target="_blank" rel="noopener"><u>blog post</u></a> as occurring at the <strong>“</strong>Business-layer<strong>”</strong>. The Business-layer Encryption has two modes: <em>Symmetric Mode</em> and <em>Asymmetric Mode</em>. The resultant Business-layer-encrypted ciphertext is appended to metadata about the Business-layer request. Then, the Business-layer requests (i.e., request metadata and inner ciphertext) are additionally encrypted, using <em>MMTLS Encryption</em>. The final resulting ciphertext is then serialized as an <em>MMTLS Request</em> and sent over the wire.</p> <p>WeChat’s network encryption system is disjointed and seems to still be a combination of at least three different cryptosystems. The encryption process described in the Tencent documentation mostly matches our findings about MMTLS Encryption, but the document does not seem to describe in detail the Business-layer Encryption<strong>,</strong> whose operation differs when <strong>logged-in</strong> and when <strong>logged-out</strong>. Logged-in clients use Symmetric Mode while logged-out clients use Asymmetric Mode. We also observed WeChat utilizing HTTP, HTTPS, and QUIC to transmit large, static resources such as translation strings or transmitted files. The endpoint hosts for these communications are different from MMTLS server hosts. Their domain names also suggest that they belong to <a href="https://en.wikipedia.org/wiki/Content_delivery_network" class="pointer" target="_blank" rel="noopener"><u>CDNs</u></a>. However, the endpoints that are interesting to us are those that download dynamically generated, often confidential resources (i.e., generated by the server on every request) or endpoints where users transmit, often confidential, data to WeChat’s servers. These types of transmissions are made using MMTLS.</p> <p>As a final implementation note, WeChat, across all these cryptosystems, uses internal OpenSSL bindings that are compiled into the program. In particular, the libwechatmm.so library seems to have been compiled with <a href="https://mta.openssl.org/pipermail/openssl-announce/2021-August/000206.html" class="pointer" target="_blank" rel="noopener"><u>OpenSSL version 1.1.1l</u></a>, though the other libraries that use OpenSSL bindings, namely <code>libMMProtocalJni.so</code> and <code>libwechatnetwork.so</code> were not compiled with the OpenSSL version strings. We note that OpenSSL internal APIs can be confusing and are often <a href="https://www.cs.utexas.edu/~shmat/shmat_ccs12.pdf" class="pointer" target="_blank" rel="noopener"><u>misused</u></a> by well-intentioned developers. Our full notes about each of the OpenSSL APIs that are used can be found in the <a href="https://github.com/citizenlab/wechat-security-report/blob/main/docs/reversing-notes.md" class="pointer" target="_blank" rel="noopener"><u>Github repository</u></a>.</p> <p>In Table 1, we have summarized each of the relevant cryptosystems, how their keys are derived, how encryption and authentication are achieved, and which libraries contain the relevant encryption and authentication functions. We will discuss cryptosystem’s details in the coming sections.</p> <figure class="center mw-100 table-overflow" style="min-width: 50%"><table border="0" cellspacing="0" class="ba b--light-gray"> <thead> <tr> <th></th> <th><strong>Key derivation</strong></th> <th class="w3"><strong>Encryption</strong></th> <th><strong>Authentication</strong></th> <th class="w4"><strong>Library</strong></th> <th><strong>Functions that perform the symmetric encryption</strong></th> </tr> </thead> <tbody> <tr class="striped--light-gray"> <td><strong>MMTLS, Longlink</strong></td> <td>Diffie-Hellman (DH)</td> <td>AES-GCM</td> <td>AES-GCM tag</td> <td><code>libwechatnetwork.so</code></td> <td><code>Crypt()</code></td> </tr> <tr class="striped--light-gray"> <td><strong>MMTLS, Shortlink</strong></td> <td>DH with session resumption</td> <td>AES-GCM</td> <td>AES-GCM tag</td> <td><code>libwechatnetwork.so</code></td> <td><code>Crypt()</code></td> </tr> <tr class="striped--light-gray"> <td><strong>Business-layer, Asymmetric Mode</strong></td> <td>Static DH with fresh client keys</td> <td>AES-GCM</td> <td>AES-GCM tag</td> <td><code>libwechatmm.so</code></td> <td><code>HybridEcdhEncrypt(),</code> <code>AesGcmEncryptWithCompress()</code></td> </tr> <tr class="striped--light-gray"> <td><strong>Business-layer, Symmetric Mode</strong></td> <td>Fixed key from server</td> <td>AES-CBC</td> <td>Checksum + MD5</td> <td><code>libMMProtocalJNI.so</code></td> <td><code>pack(), EncryptPack(), genSignature()</code></td> </tr> </tbody> </table><figcaption class="figcaption pa2 bt b--gray bw2 bg-light-gray"><p class="ma0"><em>Table 1: Overview of different cryptosystems for WeChat network request encryption, how keys are derived, how encryption and authentication are performed, and which libraries perform them.</em></p></figcaption></figure> <h2 id="mmtls-wire-format" class="lh-solid mb3">1. MMTLS Wire Format</h2> <p class="mt0">Since MMTLS can go over various transports, we refer to an <em>MMTLS packet</em> as a unit of correspondence within MMTLS. Over Longlink, MMTLS packets can be split across multiple TCP packets. Over Shortlink, MMTLS packets are generally contained within an HTTP POST request or response body.<a id="fnref1" class="footnote-ref pointer" role="doc-noteref" href="#fn1"><sup>1</sup></a></p> <p>Each MMTLS packet contains one or more <em>MMTLS records</em> (which are similar in structure and purpose to <a href="https://datatracker.ietf.org/doc/html/rfc8446#section-5" class="pointer" target="_blank" rel="noopener"><u>TLS records</u></a>). Records are units of messages that carry handshake data, application data, or alert/error message data within each MMTLS packet.</p> <h3 id="a.-mmtls-records" class="lh-solid mb3">1A. MMTLS Records</h3> <p class="mt0">Records can be identified by different <em>record headers</em>, a fixed 3-byte sequence preceding the record contents. In particular, we observed 4 different record types, with the corresponding <em>record headers</em>:</p> <figure class="center mw-100 table-overflow" style="min-width: 50%"><table class="wb-normal w-auto ba b--light-gray" border="0" cellspacing="0"> <tbody> <tr class="striped--light-gray"> <td>Handshake-Resumption Record</td> <td><code>19 f1 04</code></td> </tr> <tr class="striped--light-gray"> <td>Handshake Record</td> <td><code>16 f1 04</code></td> </tr> <tr class="striped--light-gray"> <td>Data Record</td> <td><code>17 f1 04</code></td> </tr> <tr class="striped--light-gray"> <td>Alert Record</td> <td><code>15 f1 04</code></td> </tr> </tbody> </table></figure> <p><em>Handshake</em> records contain metadata and the key establishment material needed for the other party to derive the same shared session key using Diffie-Hellman. <em>Handshake-Resumption</em> record contains sufficient metadata for “resuming” a previously established session, by re-using previously established key material. <em>Data</em> records can contain encrypted ciphertext that carries meaningful WeChat request data. Some <em>Data</em> packets simply contain an encrypted no-op heartbeat. <em>Alert</em> records signify errors or signify that one party intends to end a connection. In MMTLS, all non-handshake records are encrypted, but the key material used differs based on which stage of the handshake has been completed.</p> <p>Here is an annotated MMTLS packet from the server containing a <em>Handshake</em> record:<br> <a href="https://citizenlab.ca/wp-content/uploads/2024/10/handshake-record.png" class="pointer"><img decoding="async" class="alignnone wp-image-81120 size-full" src="https://citizenlab.ca/wp-content/uploads/2024/10/handshake-record.png" alt="" width="331" height="63" title="Should We Chat, Too? Security Analysis of WeChat’s MMTLS Encryption Protocol 1" srcset="https://citizenlab.ca/wp-content/uploads/2024/10/handshake-record.png 331w, https://citizenlab.ca/wp-content/uploads/2024/10/handshake-record-300x57.png 300w, https://citizenlab.ca/wp-content/uploads/2024/10/handshake-record-297x57.png 297w, https://citizenlab.ca/wp-content/uploads/2024/10/handshake-record-180x34.png 180w" sizes="(max-width: 331px) 100vw, 331px" /></a><br> Here is an example of a <strong>Data</strong> record sent from the client to the server:<br> <a href="https://citizenlab.ca/wp-content/uploads/2024/10/data-record.png" class="pointer"><img decoding="async" class="alignnone wp-image-81121 size-full" src="https://citizenlab.ca/wp-content/uploads/2024/10/data-record.png" alt="" width="331" height="33" title="Should We Chat, Too? Security Analysis of WeChat’s MMTLS Encryption Protocol 2" srcset="https://citizenlab.ca/wp-content/uploads/2024/10/data-record.png 331w, https://citizenlab.ca/wp-content/uploads/2024/10/data-record-300x30.png 300w, https://citizenlab.ca/wp-content/uploads/2024/10/data-record-297x30.png 297w, https://citizenlab.ca/wp-content/uploads/2024/10/data-record-180x18.png 180w" sizes="(max-width: 331px) 100vw, 331px" /></a></p> <p>To give an example of how these records interact, generally the client and server will exchange <em>Handshake</em> records until the Diffie-Hellman handshake is complete and they have established shared key material. Afterwards, they will exchange <em>Data</em> records, encrypted using the shared key material. When either side wants to close the connection, they will send an <em>Alert</em> record. More illustrations of each record type’s usage will be made in the following section.</p> <h3 id="b.-mmtls-extensions" class="lh-solid mb3">1B. MMTLS Extensions</h3> <p class="mt0">As MMTLS’ wire protocol is heavily modeled after TLS, we note that it has also borrowed the wire format of “<a href="https://datatracker.ietf.org/doc/html/rfc6066" class="pointer" target="_blank" rel="noopener"><u>TLS Extensions</u></a>” to exchange relevant encryption data during the handshake. Specifically, MMTLS uses the same format as TLS Extensions for the Client to communicate their key share (i.e. the client’s public key) for Diffie-Hellman, similar to TLS 1.3’s <a href="https://datatracker.ietf.org/doc/html/rfc8446#section-4.2.8" class="pointer" target="_blank" rel="noopener"><em><u>key_share</u></em></a> extension, and to communicate session data for session resumption (similar to TLS 1.3’s <a href="https://datatracker.ietf.org/doc/html/rfc8446#section-4.2.11" class="pointer" target="_blank" rel="noopener"><em><u>pre_shared_key</u></em></a> extension). In addition, MMTLS has support for <a href="https://datatracker.ietf.org/doc/html/rfc8446#section-4.3.1" class="pointer" target="_blank" rel="noopener"><em><u>Encrypted Extensions</u></em></a>, similar to TLS, but they are currently not used in MMTLS (i.e., the <em>Encrypted Extensions</em> section is always empty).</p> <h2 id="mmtls-encryption" class="lh-solid mb3">2. MMTLS Encryption</h2> <p class="mt0">This section describes the outer layer of encryption, that is, what keys and encryption functions are used to encrypt and decrypt the ciphertexts found in the <strong>“</strong>MMTLS Wire Format” section, and how the encryption keys are derived.</p> <p>The encryption and decryption at this layer occurs in the STN module, in a separate spawned “com.tencent.mm:push”<a id="fnref2" class="footnote-ref pointer" role="doc-noteref" href="#fn2"><sup>2</sup></a> process on Android. The spawned process ultimately transmits and receives data over the network. The code for all of the MMTLS Encryption and MMTLS serialization were analyzed from the library <code>libwechatnetwork.so</code>. In particular, we studied the <code>Crypt()</code> function, a central function used for all encryption and decryption whose name we derived from debug logging code. We also hooked all calls to <a href="https://github.com/OneSignal/openssl/blob/main/crypto/kdf/hkdf.c#L26" class="pointer" target="_blank" rel="noopener"><u>HKDF_Extract</u></a>() and <a href="https://github.com/OneSignal/openssl/blob/main/crypto/kdf/hkdf.c#L31" class="pointer" target="_blank" rel="noopener"><u>HKDF_Expand</u></a>(), the OpenSSL functions for <a href="https://en.wikipedia.org/wiki/HKDF" class="pointer" target="_blank" rel="noopener"><u>HKDF</u></a>, in order to understand how keys are derived.</p> <p>When the “:push” process is spawned, it starts an event loop in HandshakeLoop(), which processes all outgoing and incoming MMTLS Records. We hooked all functions called by this event loop to understand how each MMTLS Record is processed. The code for this study, as well as the internal function addresses identified for the particular version of WeChat we studied, can be found in the <a href="https://github.com/citizenlab/wechat-security-report/tree/main" class="pointer" target="_blank" rel="noopener"><u>Github repository</u></a>.</p> <figure class="center mw-100 ba b--light-gray" style="width:861px;"><div class="tc pa2 bg-white"><a href="https://citizenlab.ca/wp-content/uploads/2024/10/image2.png" class="pointer"><img fetchpriority="high" decoding="async" class="wp-image-81071 size-full" src="https://citizenlab.ca/wp-content/uploads/2024/10/image2.png" alt="" width="861" height="473" title="Should We Chat, Too? Security Analysis of WeChat’s MMTLS Encryption Protocol 3" srcset="https://citizenlab.ca/wp-content/uploads/2024/10/image2.png 861w, https://citizenlab.ca/wp-content/uploads/2024/10/image2-300x165.png 300w, https://citizenlab.ca/wp-content/uploads/2024/10/image2-768x422.png 768w, https://citizenlab.ca/wp-content/uploads/2024/10/image2-605x332.png 605w, https://citizenlab.ca/wp-content/uploads/2024/10/image2-297x163.png 297w, https://citizenlab.ca/wp-content/uploads/2024/10/image2-500x275.png 500w, https://citizenlab.ca/wp-content/uploads/2024/10/image2-180x99.png 180w" sizes="(max-width: 861px) 100vw, 861px" /></a></div><figcaption class="f5-ns f6 black-70 pa2 bg-light-gray">Figure 1: Network requests: MMTLS encryption connection over longlink and over shortlink. Each box is an MMTLS Record, and each arrow represents an “MMTLS packet” sent over either Longlink (i.e., a single TCP packet) or shortlink (i.e., in the body of HTTP POST). Once both sides have received the DH keyshare, all further records are encrypted.</figcaption></figure> <h3 id="a.-handshake-and-key-establishment" class="lh-solid mb3">2A. Handshake and key establishment</h3> <p class="mt0">In order for Business-layer Encryption to start sending messages and establish keys, it has to use the MMTLS Encryption tunnel. Since the key material for the MMTLS Encryption has to be established first, the handshakes in this section happen before any data can be sent or encrypted via Business-layer Encryption. The end goal of the MMTLS Encryption handshake discussed in this section is to establish a common secret value that is known only to the client and server.</p> <p>On a fresh startup of WeChat, it tries to complete one MMTLS handshake over Shortlink, and one MMTLS handshake over Longlink, resulting in two MMTLS encryption tunnels, each using different sets of encryption keys. For Longlink, after the handshake completes, the same Longlink (TCP) connection is kept open to transport future encrypted data. For Shortlink, the MMTLS handshake is completed in the first HTTP request-response cycle, then the first HTTP connection closes. The established keys are stored by the client and server, and when data needs to be sent over Shortlink, those established keys are used for encryption, then sent over a newly established Shortlink connection. In the remainder of this section, we describe details of the handshakes.</p> <h4 id="clienthello" class="lh-solid mb3">ClientHello</h4> <p class="mt0">First, the client generates keypairs on the <a href="https://neuromancer.sk/std/secg/secp256r1" class="pointer" target="_blank" rel="noopener"><u>SECP256R1</u> <u>elliptic curve</u></a>. Note that these elliptic curve keys are entirely separate pairs from those generated in the Business-layer Encryption section. The client also reads some Resumption Ticket data from a file stored on local storage named <strong><code>psk.key</code>,</strong> if it exists. The <code><strong>psk.key</strong></code> file is written to after the first ServerHello is received, so, on a fresh install of WeChat, the resumption ticket is omitted from the ClientHello.</p> <p>The client first simultaneously sends a ClientHello message (contained in a Handshake record) over both the Shortlink and Longlink. The first of these two handshakes that completes successfully is the one that the initial Business-layer Encryption handshake occurs over (details of Business-layer Encryption are discussed in Section 4). Both Shortlink and Longlink connections are used afterwards for sending other data.</p> <p>In both the initial Shortlink and Longlink handshake, each ClientHello packet contains the following data items:</p> <ul> <li class="mt2">ClientRandom (32 bytes of randomness)</li> <li class="mt2">Resumption Ticket data read from psk.key, if available</li> <li class="mt2">Client public key</li> </ul> <p>An abbreviated version of the MMTLS ClientHello is shown below.</p> <div class="box box--code"> <pre><span class="bg-light-blue">16 f1 04 (Handshake Record header) . . .</span> 01 04 f1 (ClientHello) . . . 08 cd 1a 18 f9 1c . . . (ClientRandom) . . . 00 0c c2 78 00 e3 . . . (Resumption Ticket from psk.key) . . . 04 0f 1a 52 7b 55 . . . (Client public key) . . . </pre> </div> <p>Note that the client generates <strong>a separate keypair</strong> for the Shortlink ClientHello and the Longlink ClientHello. The Resumption Ticket sent by the client is the same on both ClientHello packets because it is always read from the same psk.key file. On a fresh install of WeChat, the Resumption Ticket is omitted since there is no psk.key file.</p> <h4 id="serverhello" class="lh-solid mb3">ServerHello</h4> <p class="mt0">The client receives a ServerHello packet in response to each ClientHello packet. Each contains:</p> <ul> <li class="mt2">A record containing ServerRandom and Server public key</li> <li class="mt2">Records containing <strong>encrypted</strong> server certificate, new resumption ticket, and a ServerFinished message.</li> </ul> <p>An abbreviated version of the MMTLS ServerHello is shown below; a full packet sample with labels can be found in the <a href="https://docs.google.com/document/d/1Ub195WcLUY8YGJoGKW5IlhIIGaUWFzcM7aovNWqnFL4/edit" class="pointer" target="_blank" rel="noopener"><u>annotated network capture</u></a>.</p> <div class="box box--code"> <pre><span class="bg-light-blue">16 f1 04 (Handshake Record header) . . .</span> 02 04 f1 (ServerHello) . . . 2b a6 88 7e 61 5e 27 eb . . . (ServerRandom) . . . 04 fa e3 dc 03 4a 21 d9 . . . (Server public key) . . . <span class="bg-light-blue">16 f1 04 (Handshake Record header) . . .</span> <span class="bg-light-green">b8 79 a1 60 be 6c</span> . . . (<strong>ENCRYPTED</strong> server certificate) . . . <span class="bg-light-blue">16 f1 04 (Handshake Record header) . . .</span> <span class="bg-light-green">1a 6d c9 dd 6e f1</span> . . . (<strong>ENCRYPTED</strong> NEW resumption ticket) . . . <span class="bg-light-blue">16 f1 04 (Handshake Record header) . . .</span> <span class="bg-light-green">b8 79 a1 60 be 6c </span>. . . (<strong>ENCRYPTED</strong> ServerFinished) . . . </pre> </div> <p>On receiving the server public key, the client generates</p> <div class="box box--code"><code>secret = ecdh(client_private_key, server_public_key).</code></div> <p>Note that since each MMTLS encrypted tunnel uses a different pair of client keys, the shared secret, and any derived keys and IVs will be different between MMTLS tunnels. This also means Longlink handshake and Shortlink handshake each compute a different shared secret.</p> <p>Then, the shared secret is used to derive several sets of cryptographic parameters via HKDF, a mathematically secure way to transform a short secret value into a long secret value. In this section, we will focus on the <em>handshake parameters</em>. Alongside each set of keys, <a href="https://www.techtarget.com/whatis/definition/initialization-vector-IV" class="pointer" target="_blank" rel="noopener"><u>initialization vectors (IVs)</u></a> are also generated. The IV is a value that is needed to initialize the <a href="https://en.wikipedia.org/wiki/Galois/Counter_Mode" class="pointer" target="_blank" rel="noopener"><u>AES-GCM</u></a> encryption algorithm. IVs do not need to be kept secret. However, they need to be random and not reused.</p> <p>The <em>handshake parameters</em> are generated using HKDF (“handshake key expansion” is a constant string in the program, as well as other monotype double quoted strings in this section):</p> <div class="box box--code"><code>key_enc, key_dec, iv_enc, iv_dec = HKDF(secret, 56, “handshake key expansion”)</code></div> <p>Using <code>key_dec</code> and <code>iv_dec</code>, the client can decrypt the remainder of the ServerHello records. Once decrypted, the client validates the server certificate. Then, the client also saves the new Resumption Ticket to the file <code>psk.key</code>.</p> <p>At this point, since the shared <code>secret</code> has been established, the MMTLS Encryption Handshake is considered completed. To start encrypting and sending data, the client derives other sets of parameters via HKDF from the shared secret. The details of which keys are derived and used for which connections are fully specified in <a href="https://github.com/citizenlab/wechat-security-report/blob/main/docs/outer-crypto.md#full-key-derivation-details" class="pointer" target="_blank" rel="noopener"><u>these notes</u></a> where we annotate the keys and connections created on WeChat startup.</p> <h3 id="b.-data-encryption" class="lh-solid mb3">2B. Data encryption</h3> <p class="mt0">After the handshake, MMTLS uses AES-GCM with a particular key and IV, which are tied to the particular MMTLS tunnel, to encrypt data. The IV is incremented by the number of records previously encrypted with this key. This is important because re-using an IV with the same key destroys the confidentiality provided in AES-GCM, as it can lead to a key recovery attack using the known tag.</p> <div class="box box--code"><code>ciphertext, tag = AES-GCM(input, key, iv+n)</code><br> <code>ciphertext = ciphertext | tag</code></div> <p>The 16-byte tag is appended to the end of the ciphertext. This tag is authentication data computed by <a href="https://en.wikipedia.org/wiki/Galois/Counter_Mode" class="pointer" target="_blank" rel="noopener"><u>AES-GCM</u></a>; it functions as a <a href="https://en.wikipedia.org/wiki/Message_authentication_code" class="pointer" target="_blank" rel="noopener"><u>MAC</u></a> in that when verified properly, this data provides authentication and integrity. In many cases, if this is a Data record being encrypted, <code>input</code> contains metadata and ciphertext that has already been encrypted as described in the Business-layer Encryption section.</p> <p>We separately discuss data encryption in Longlink and Shortlink in the following subsections.</p> <h4 id="b1.-longlink" class="lh-solid mb3">2B1. Longlink</h4> <p class="mt0">Client-side Encryption for Longlink packets is done using AES-GCM with <strong>key_enc</strong> and <strong>iv_enc</strong> derived earlier in the handshake. Client-side Decryption uses <strong>key_dec</strong> and <strong>iv_dec</strong>. Below is a sample Longlink (TCP) packet containing a single data record containing an encrypted heartbeat message from the server<a id="fnref3" class="footnote-ref pointer" role="doc-noteref" href="#fn3"><sup>3</sup></a>:</p> <div class="box box--code"> <pre><span class="bg-light-blue">17 f1 04</span> RECORD HEADER (of type “DATA”) 00 20 RECORD LENGTH <span class="bg-light-green">e6 55 7a d6 82 1d a7 f4 2b 83 d4 b7 78 56 18 f3</span> <span class="bg-light-green">ENCRYPTED DATA</span> <span class="bg-yellow">1b 94 27 e1 1e c3 01 a6 f6 23 6a bc 94 eb 47 39</span> <span class="bg-yellow">TAG (MAC)</span> </pre> </div> <p>Within a long-lived Longlink connection, the IV is incremented for each record encrypted. If a new Longlink connection is created, the handshake is restarted and new key material is generated.</p> <h4 id="b2.-shortlink" class="lh-solid mb3">2B2. Shortlink</h4> <p class="mt0">Shortlink connections can only contain a single MMTLS packet request and a single MMTLS packet response (via HTTP POST request and response, respectively). After the initial Shortlink ClientHello sent on startup, WeChat will send ClientHello with Handshake Resumption packets. These records have the header 19 f1 04 instead of the 16 f1 04 on the regular ClientHello/ServerHello handshake packets.</p> <p>An abbreviated sample of a Shortlink request packet containing Handshake Resumption is shown below.</p> <div class="box box--code"> <pre><span class="bg-light-blue">19 f1 04 (Handshake Resumption Record header) . . .</span> 01 04 f1 (ClientHello) . . . 9b c5 3c 42 7a 5b 1a 3b . . . (ClientRandom) . . . 71 ae ce ff d8 3f 29 48 . . . (NEW Resumption Ticket) . . . <span class="bg-light-blue">19 f1 04 (Handshake Resumption Record header) . . .</span> <span class="bg-light-green">47 4c 34 03 71 9e</span> . . . (<strong>ENCRYPTED</strong> Extensions) . . . <span class="bg-light-blue">17 f1 04 (Data Record header) . . .</span> <span class="bg-light-green">98 cd 6e a0 7c 6b</span> . . . (<strong>ENCRYPTED</strong> EarlyData) . . . <span class="bg-light-blue">15 f1 04 (Alert Record header)</span> . . . <span class="bg-light-green">8a d1 c3 42 9a 30</span> . . . (<strong>ENCRYPTED</strong> Alert (ClientFinished)) . . . </pre> </div> <p>Note that, based on our understanding of the MMTLS protocol, the ClientRandom sent in this packet is not used at all by the server, because there is no need to re-run Diffie-Hellman in a resumed session. The Resumption Ticket is used by the server to identify which prior-established shared secret should be used to decrypt the following packet content.</p> <p>Encryption for Shortlink packets is done using AES-GCM with the <em>handshake parameters</em> <strong>key_enc</strong> and <strong>iv_enc</strong>. (Note that, despite their identical name, <strong>key_enc</strong> and <strong>iv_enc</strong> here are different from those of the Longlink, since Shortlink and Longlink each complete their own handshake using different elliptic curve client keypair.) The <strong>iv_enc</strong> is incremented for each record encrypted. Usually, EarlyData records sent over <strong>S</strong>hortlink contain ciphertext that has been encrypted with Business-layer Encryption as well as associated metadata. This metadata and ciphertext will then be additionally encrypted at this layer.</p> <p>The reason this is referred to as EarlyData internally in WeChat is likely due to it being borrowed from <a href="https://datatracker.ietf.org/doc/html/rfc8446#section-4.2.10" class="pointer" target="_blank" rel="noopener"><u>TLS</u></a>; typically, it refers to the data that is encrypted with a key derived from a pre-shared key, before the establishment of a regular session key via Diffie-Hellman. However, in this case, when using Shortlink, there is no data sent “after the establishment of a regular session key”, so almost all Shortlink data is encrypted and sent in this EarlyData section.</p> <p>Finally, <code>ClientFinished</code> indicates that the client has finished its side of the handshake. It is an encrypted Alert record with a fixed message that always follows the EarlyData Record. From our reverse-engineering, we found that the handlers for this message referred to it as <code>ClientFinished</code>.</p> <h2 id="business-layer-request" class="lh-solid mb3">3. Business-layer Request</h2> <p class="mt0">MMTLS Data Records either carry an “Business-layer request” or heartbeat messages. In other words, if one decrypts the payload from an MMTLS Data Record, the result will often be messages described below.</p> <p>This Business-layer request contains several metadata parameters that describe the purpose of the request, including the internal URI and the request type number, which we briefly described in the “Launching a WeChat network request” section.</p> <p>When logged-in, the format of a Business-layer request looks like the following:</p> <div class="box box--code"> <pre><span class="green">00 00 00 7b</span> (total data length) <span class="green">00 24</span> (URI length) /cgi-bin/micromsg-bin/... (URI) <span class="green">00 12</span> (hostname length) sgshort.wechat.com (hostname) <span class="green">00 00 00 3D</span> (length of rest of data) <span class="green">BF B6 5F</span> (request flags) <span class="green">41 41 41 41</span> (user ID) <span class="green">42 42 42 42</span> (device ID) <span class="green">FC 03 48 02 00 00 00 00</span> (cookie) <span class="green">1F 9C 4C 24 76 0E 00</span> (cookie) <span class="green">D1 05 varint</span> (request_type) <span class="green">0E 0E 00 02</span> (4 more varints) <span class="green">BD 95 80 BF 0D varint</span> (signature) <span class="green">FE</span> (flag) <span class="green">80 D2 89 91</span> <span class="green">04 00 00</span> (marks start of data) <span class="green">08 A6 29 D1 A4 2A CA F1</span> ... (ciphertext) </pre> </div> <p>Responses are formatted very similarly:</p> <div class="box box--code"> <pre><span class="green">bf b6 5f</span> (flags) <span class="green">41 41 41 41</span> (user ID) <span class="green">42 42 42 42</span> (device ID) <span class="green">fc 03 48 02 00 00 00 00</span> (cookie) <span class="green">1f 9c 4c 24 76 0e 00</span> (cookie) <span class="green">fb 02 varint</span> (request_type) <span class="green">35 35 00 02 varints</span> <span class="green">a9 ad 88 e3 08 varint</span> (signature) <span class="green">fe</span> <span class="green">ba da e0 93</span> <span class="green">04 00 00</span> (marks start of data) <span class="green">b6 f8 e9 99 a1 f4 d1 20</span> . . . ciphertext </pre> </div> <p>This request then contains another encrypted ciphertext, which is encrypted by what we refer to as Business-layer Encryption. Business-layer Encryption is separate from the system we described in the <strong>MMTLS Encryption</strong> section. The <code>signature</code> mentioned above is the output of <code>genSignature()</code>, which is discussed in the “Integrity check” section. Pseudocode for the serialization schemes and more samples of WeChat’s encrypted request header can be found in our <a href="https://github.com/citizenlab/wechat-security-report/blob/main/docs/wechat-encrypted-request-format.md" class="pointer" target="_blank" rel="noopener"><u>Github repository</u></a>.</p> <h2 id="business-layer-encryption" class="lh-solid mb3">4. Business-layer Encryption</h2> <p class="mt0"><a href="https://docs.google.com/drawings/d/1WSZY_R8XBliTDrb3tSkmoZpaq3lsud0sBaWRuygGuJo/edit" class="pointer" target="_blank" rel="noopener"><u>WeChat Crypto diagrams (inner layer)</u></a></p> <p>This section describes how the Business-layer requests described in <strong>Section 3</strong> are encrypted and decrypted, and how the keys are derived. We note that the set of keys and encryption processes introduced in this section are completely separate from those referred to in the MMTLS Encryption section. Generally, for Business-layer Encryption, much of the protocol logic is handled in the Java code, and the Java code calls out to the C++ libraries for encryption and decryption calculations. Whereas for MMTLS Encryption everything is handled in C++ libraries, and occurs on a different process entirely. There is very little interplay between these two layers of encryption.</p> <p>The Business-layer Encryption has two modes using different cryptographic processes: <em>Asymmetric Mode</em> and <em>Symmetric Mode</em>. To transition into Symmetric Mode, WeChat needs to perform an <em>Autoauth</em> request. Upon startup, WeChat typically goes through the three following stages:</p> <ol type="1"> <li class="mt2">Before the user logs in to their account, Business-layer Encryption first uses asymmetric cryptography to derive a shared secret via <a href="https://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange#Ephemeral_and/or_static_keys" class="pointer" target="_blank" rel="noopener"><u>static Diffie-Hellman</u></a> (static DH), then uses the shared secret as a key to AES-GCM encrypt the data. We name this Asymmetric Mode. In Asymmetric Mode, the client derives a new shared secret for each request.</li> <li class="mt2">Using Asymmetric Mode, WeChat can send an Autoauth request, to which the server would return an Autoauth response, which contains a <code><strong>session_key</strong></code>.</li> <li class="mt2">After the client obtains <code>session_key</code>, Business-layer Encryption uses it to <a href="https://docs.anchormydata.com/docs/what-is-aes-256-cbc" class="pointer" target="_blank" rel="noopener"><u>AES-CBC</u></a> encrypt the data. We name this Symmetric Mode since it only uses symmetric cryptography. Under Symmetric Mode, the same <code>session_key</code> can be used for multiple requests.</li> </ol> <p>For <em>Asymmetric Mode</em>, we performed dynamic and static analysis of C++ functions in libwechatmm.so; in particular the <code>HybridEcdhEncrypt()</code> and <code>HybridEcdhDecrypt()</code> functions, which call <code>AesGcmEncryptWithCompress()</code> / <code>AesGcmDecryptWithUncompress()</code>, respectively.</p> <p>For <em>Symmetric Mode</em>, the requests are handled in <code>pack()</code>, <code>unpack()</code>, and <code>genSignature()</code> functions in <code>libMMProtocalJNI.so</code>. Generally, <code>pack()</code> handles outgoing requests, and <code>unpack()</code> handles incoming responses to those requests. They also perform encryption/decryption. Finally, <code>genSignature()</code> computes a checksum over the full request. In the Github repository, we’ve uploaded pseudocode for <a href="https://github.com/citizenlab/wechat-security-report/blob/main/docs/reversing-notes.md#pack-notes" class="pointer" target="_blank" rel="noopener"><u>pack</u></a>, <a href="https://github.com/citizenlab/wechat-security-report/blob/main/docs/reversing-notes.md#aesencrypt-cbc" class="pointer" target="_blank" rel="noopener"><u>AES-CBC</u></a> encryption, and the <a href="https://github.com/citizenlab/wechat-security-report/blob/main/docs/reversing-notes.md#gensignature" class="pointer" target="_blank" rel="noopener"><u>genSignature</u></a> routine.</p> <p>The Business-layer Encryption is also tightly integrated with WeChat’s user authentication system. The user needs to log in to their account before the client is able to send an Autoauth request. For clients that have not logged in, they exclusively use Asymmetric Mode. For clients that have already logged in, their first Business-layer packet would most often be an Autoauth request encrypted using Asymmetric Mode, however, the second and onward Business-layer packets are encrypted using Symmetric Mode.</p> <figure class="center mw-100 ba b--light-gray" style="width:842px;"><div class="tc pa2 bg-white"><a href="https://citizenlab.ca/wp-content/uploads/2024/10/image3.png" class="pointer"><img loading="lazy" decoding="async" class="wp-image-81072 size-full" src="https://citizenlab.ca/wp-content/uploads/2024/10/image3.png" alt="" width="842" height="363" title="Should We Chat, Too? Security Analysis of WeChat’s MMTLS Encryption Protocol 4" srcset="https://citizenlab.ca/wp-content/uploads/2024/10/image3.png 842w, https://citizenlab.ca/wp-content/uploads/2024/10/image3-300x129.png 300w, https://citizenlab.ca/wp-content/uploads/2024/10/image3-768x331.png 768w, https://citizenlab.ca/wp-content/uploads/2024/10/image3-605x261.png 605w, https://citizenlab.ca/wp-content/uploads/2024/10/image3-297x128.png 297w, https://citizenlab.ca/wp-content/uploads/2024/10/image3-500x216.png 500w, https://citizenlab.ca/wp-content/uploads/2024/10/image3-180x78.png 180w" sizes="auto, (max-width: 842px) 100vw, 842px" /></a></div><figcaption class="f5-ns f6 black-70 pa2 bg-light-gray"><strong>Figure 2: Business-layer encryption, logged-out, logging-in, and logged-in:</strong> Swimlane diagrams showing at a high-level what Business-layer Encryption requests look like, including which secrets are used to generate the key material used for encryption. 🔑secret is generated via DH(static server public key, client private key), and 🔑<strong>new_secret</strong> is DH(server public key, client private key). 🔑<strong>session</strong> is decrypted from the first response when logged-in. Though it isn’t shown above, 🔑<strong>new_secret</strong> is also used in <strong>genSignature()</strong> when logged-in; this signature is sent with request and response metadata.</figcaption></figure> <h3 id="a.-business-layer-encryption-asymmetric-mode" class="lh-solid mb3">4A. Business-layer Encryption, Asymmetric Mode</h3> <p class="mt0">Before the user logs in to their WeChat account, the Business-layer Encryption process uses a <em>static server public key</em>, and generates new client keypair to agree on a static Diffie-Hellman shared secret for every WeChat network request. The shared secret is run through the HKDF function and any data is encrypted with AES-GCM and sent alongside the generated client public key so the server can calculate the shared secret.</p> <p>For each request, the client generates a public, private keypair for use with <a href="https://en.wikipedia.org/wiki/Elliptic-curve_Diffie%E2%80%93Hellman" class="pointer" target="_blank" rel="noopener"><u>ECDH</u></a>. We also note that the client has a static server public key pinned in the application. The client then calculates an initial secret.</p> <div class="box box--code"><code>secret = <a href="https://github.com/openssl/openssl/blob/79c8dcf3985a7b75eac8e53eb8652728af6c5d3d/crypto/ec/ec_kmeth.c#L151" class="pointer" target="_blank" rel="noopener"><u>ECDH</u></a>(static_server_pub, client_priv)</code><br> <code>hash = sha256(client_pub)</code><br> <code>client_random = <32 randomly generated bytes></code><br> <code>derived_key = HKDF(secret)</code></div> <p><code>derived_key</code> is then used to AES-GCM encrypt the data, which we describe in detail in the next section.</p> <h3 id="b.-business-layer-encryption-obtaining-session_key" class="lh-solid mb3">4B. Business-layer Encryption, obtaining session_key</h3> <p class="mt0">If the client is logged-in (i.e., the user has logged in to a WeChat account on a previous app run), the first request will be a very large data packet authenticating the client to the server (referred to as Autoauth in WeChat internals) which also contains key material. We refer to this request as the Autoauth request. In addition, the client pulls a locally-stored key <code>autoauth_key</code>, which we did not trace the provenance of, since it does not seem to be used other than in this instance. The key for encrypting this initial request (<code>authrequest_data</code>) is derived_key, calculated in the same way as in Section 4A. The encryption described in the following is the Asymmetric Mode encryption, albeit a special case where the data is the <code>authrequest_data</code>.</p> <p>Below is an abbreviated version of a serialized and encrypted Autoauth request:</p> <div class="box box--code"> <pre> 08 01 12 . . . [Header metadata] 04 46 40 96 4d 3e 3e 7e [client_publickey] . . . fa 5a 7d a7 78 e1 ce 10 . . . [ClientRandom encrypted w secret] a1 fb 0c da . . . [IV] 9e bc 92 8a 5b 81 . . . [tag] db 10 d3 0f f8 e9 a6 40 . . . [ClientRandom encrypted w autoauth_key] 75 b4 55 30 . . . [IV] d7 be 7e 33 a3 45 . . . [tag] c1 98 87 13 eb 6f f3 20 . . . [<strong>authrequest_data</strong> encrypted w derived_key] 4c ca 86 03 . . [IV] 3c bc 27 4f 0e 7b . . . [tag] </pre> </div> <p>A full sample of the Autoauth request and response at each layer of encryption can be found in the <a href="https://github.com/citizenlab/wechat-security-report/tree/main/data" class="pointer" target="_blank" rel="noopener"><u>Github repository</u></a>. Finally, we note that the <code>autoauth_key</code> above does not seem to be actively used outside of encrypting in this particular request. We suspect this is vestigial from a legacy encryption protocol used by WeChat.</p> <p>The client encrypts here using AES-GCM with a randomly generated IV, and uses a SHA256 hash of the preceding message contents as <a href="https://datatracker.ietf.org/doc/html/rfc5084#section-1.4" class="pointer" target="_blank" rel="noopener"><u>AAD</u></a>. At this stage, the messages (including the ClientRandom messages) are always <a href="https://en.wikipedia.org/wiki/Zlib" class="pointer" target="_blank" rel="noopener"><u>ZLib</u></a> compressed before encryption.</p> <div class="box box--code"><code>iv = <12 random bytes><br> compressed = zlib_compress(plaintext)<br> ciphertext, tag = AESGCM_encrypt(compressed, aad = hash(previous), derived_key, iv)<br> </code></div> <p>In the above, previous is the header of the request (i.e. all header bytes preceding the 04 00 00 marker of data start). The client appends the 12-byte IV, then the 16-byte tag, onto the ciphertext. This tag can be used by the server to verify the integrity of the ciphertext, and essentially functions as a <a href="https://en.wikipedia.org/wiki/Message_authentication_code" class="pointer" target="_blank" rel="noopener"><u>MAC</u></a>.</p> <h4 id="b1.-obtaining-session_key-autoauth-response" class="lh-solid mb3">4B1. Obtaining session_key: Autoauth Response</h4> <p class="mt0">The response to autoauth is serialized similarly to the request:</p> <div class="box box--code"> <pre>08 01 12 . . . [Header metadata] 04 46 40 96 4d 3e 3e 7e [new_server_pub] . . . c1 98 87 13 eb 6f f3 20 . . . [<strong>authresponse_data</strong> encrypted w new_secret] 4c ca 86 03 . . [IV] 3c bc 27 4f 0e 7b . . . [tag] </pre> </div> <p>With the newly received server public key (<code>new_server_pub</code>), which is different from the <code>static_server_pub</code> hardcoded in the app, the client then derives a new secret (<code>new_secret</code>). new_secret is then used as the key to AES-GCM decrypt <code>authresponse_data</code>. The client can also verify <code>authresponse_data</code> with the given tag.</p> <div class="box box--code"><code>new_secret = <a href="https://github.com/openssl/openssl/blob/79c8dcf3985a7b75eac8e53eb8652728af6c5d3d/crypto/ec/ec_kmeth.c#L151" class="pointer" target="_blank" rel="noopener"><u>ECDH</u></a>(new_server_pub, client_privatekey)<br> authresponse_data= AESGCM_decrypt(aad = hash(authrequest_data),<br> new_secret, iv)<br> </code></div> <p><code>authresponse_data</code> is a serialized Protobuf containing a lot of important data for WeChat to start, starting with a helpful <strong>“</strong><code>Everything is ok</code><strong>”</strong> status message. A full sample of this Protobuf can be found in the <a href="https://github.com/citizenlab/wechat-security-report/blob/main/data/autoauth-response.json" class="pointer" target="_blank" rel="noopener"><u>Github repository</u></a>. Most importantly, <code>authresponse_data</code> contains <code><strong>session_key</strong></code>, which is the key used for future AES-CBC encryption under Symmetric Mode. From here on out, <code>new_secret</code> is only used in <code>genSignature()</code>, which is discussed below in Section 4C2 Integrity Check.</p> <p>We measured the entropy of the session_key provided by the server, as it is used for future encryption. This key exclusively uses printable ASCII characters, and is thus limited to around ~100 bits of entropy.</p> <p>The WeChat code refers to three different keys: <em>client_session</em>, <em>server_session</em>, and <em>single_session</em>. Generally, <em>client_session</em> refers to the <code>client_publickey</code>, <code>server_session</code> refers to the <em>shared secret key</em> generated using ECDH i.e. <code>new_secret</code>, and <code>single_session</code> refers to the <code>session_key</code> provided by the server.</p> <h3 id="c.-business-layer-encryption-symmetric-mode" class="lh-solid mb3">4C. Business-layer Encryption, Symmetric Mode</h3> <p class="mt0">After the client receives session_key from the server, future data is encrypted using Symmetric Mode. Symmetric Mode encryption is mostly done using AES-CBC instead of AES-GCM, with the exception of some large files being encrypted with <code>AesGcmEncryptWithCompress()</code>. As <code>AesGcmEncryptWithCompress()</code> requests are the exception, we focus on the more common use of AES-CBC.</p> <p>Specifically, the Symmetric Mode uses AES-CBC with PKCS-7 padding, with the session_key as a symmetric key:</p> <div class="box box--code"><code>ciphertext = AES-CBC(PKCS7_pad(plaintext), session_key, iv = session_key)<br> </code></div> <p>This <code>session_key</code> is doubly used as the IV for encryption.</p> <h4 id="c1.-integrity-check" class="lh-solid mb3">4C1. Integrity check</h4> <p class="mt0">In Symmetric Mode, a function called <code>genSignature()</code> calculates a pseudo-integrity code on the plaintext. This function first calculates the <strong>MD5 hash</strong> of WeChat’s assigned user ID for the logged-in user (<code><span class="green">uin</span></code>), <code><span class="green">new_secret</span></code>, and the plaintext length. Then, genSignature() uses <a href="https://en.wikipedia.org/wiki/Adler-32" class="pointer" target="_blank" rel="noopener"><strong><u>Adler32</u></strong></a>, a checksumming function, on the MD5 hash concatenated with the plaintext.</p> <div class="box box--code"> <pre>signature = adler32(md5(uin | new_secret | plaintext_len) | plaintext) </pre> </div> <p>The result from Adler32 is concatenated to the ciphertext as metadata (see Section 3A for how it is included in the request and response headers), and is referred to as a <code>signature</code> in WeChat’s codebase. We note that though it is referred to as a <code>signature</code>, it does not provide any cryptographic properties; details can be found in the Security Issues section. The full pseudocode for this function can also be found in <a href="https://github.com/citizenlab/wechat-security-report/blob/main/docs/reversing-notes.md#gensignature" class="pointer" target="_blank" rel="noopener"><u>the Github repository</u></a>.</p> <h2 id="protobuf-data-payload" class="lh-solid mb3">5. Protobuf data payload</h2> <p class="mt0">The input to Business-layer Encryption is generally a serialized Protobuf, optionally compressed with Zlib. When logged-in, many of the Protobufs sent to the server contain the following header data:</p> <div class="box box--code"> <pre>"1": { "1": "\u0000", "2": "1111111111", # User ID (assigned by WeChat) "3": "AAAAAAAAAAAAAAA\u0000", # Device ID (assigned by WeChat) "4": "671094583", # Client Version "5": "android-34", # Android Version "6": "0" }, </pre> </div> <p>The Protobuf structure is defined in each API’s corresponding RR class, as we previously mentioned in the “Launching a WeChat network request” section.</p> <h2 id="putting-it-all-together" class="lh-solid mb3">6. Putting it all together</h2> <p class="mt0">In the below diagram, we demonstrate the network flow for the most common case of opening the WeChat application. We note that in order to prevent further complicating the diagram, HKDF derivations are not shown; for instance, when “🔑<code>mmtls</code>” is used, HKDF is used to derive a key from “🔑<code>mmtls</code>”, and the derived key is used for encryption. The specifics of how keys are derived, and which derived keys are used to encrypt which data, can be found in <a href="https://github.com/citizenlab/wechat-security-report/blob/main/docs/outer-crypto.md#full-key-derivation-details" class="pointer" target="_blank" rel="noopener"><u>these notes</u></a>.</p> <figure class="center mw-100 ba b--light-gray" style="width:914px;"><div class="tc pa2 bg-white"><a href="https://citizenlab.ca/wp-content/uploads/2024/10/image1.png" class="pointer"><img loading="lazy" decoding="async" class="wp-image-81073 size-full" src="https://citizenlab.ca/wp-content/uploads/2024/10/image1.png" alt="" width="914" height="1331" title="Should We Chat, Too? Security Analysis of WeChat’s MMTLS Encryption Protocol 5" srcset="https://citizenlab.ca/wp-content/uploads/2024/10/image1.png 914w, https://citizenlab.ca/wp-content/uploads/2024/10/image1-206x300.png 206w, https://citizenlab.ca/wp-content/uploads/2024/10/image1-703x1024.png 703w, https://citizenlab.ca/wp-content/uploads/2024/10/image1-768x1118.png 768w, https://citizenlab.ca/wp-content/uploads/2024/10/image1-233x340.png 233w, https://citizenlab.ca/wp-content/uploads/2024/10/image1-137x199.png 137w, https://citizenlab.ca/wp-content/uploads/2024/10/image1-190x277.png 190w, https://citizenlab.ca/wp-content/uploads/2024/10/image1-180x262.png 180w" sizes="auto, (max-width: 914px) 100vw, 914px" /></a></div><figcaption class="f5-ns f6 black-70 pa2 bg-light-gray">Figure 3: Swimlane diagram demonstrating the encryption setup and network flow of the most common case (user is logged in, opens WeChat application).</figcaption></figure> <p>We note that other configurations are possible. For instance, we have observed that if the Longlink MMTLS handshake completes first, the Business-layer “Logging-in” request and response can occur over the Longlink connection instead of over several <strong>shortlink</strong> connections. In addition, if the user is logged-out, Business-layer requests are simply encrypted with 🔑secret (resembling <strong>Shortlink 2</strong> requests)</p> <h1 id="security-issues" class="lh-solid mb3">Security issues</h1> <p class="mt0">In this section, we outline potential security issues and privacy weaknesses we identified with the construction of the <strong>MMTLS encryption</strong> and <strong>Business-layer</strong> encryption layers. There could be other issues as well.</p> <h2 id="issues-with-mmtls-encryption" class="lh-solid mb3">Issues with MMTLS encryption</h2> <p class="mt0">Below we detail the issues we found with WeChat’s MMTLS encryption.</p> <h3 id="deterministic-iv" class="lh-solid mb3">Deterministic IV</h3> <p class="mt0">The MMTLS encryption process generates a single IV once per connection. Then, they increment the IV for each subsequent record encrypted in that connection. Generally, NIST <a href="https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf" class="pointer" target="_blank" rel="noopener"><u>recommends</u></a> not using a wholly deterministic derivation for IVs in AES-GCM since it is easy to accidentally re-use IVs. In the case of AES-GCM, reuse of the (key, IV) tuple is catastrophic as it <a href="https://csrc.nist.gov/csrc/media/projects/block-cipher-techniques/documents/bcm/comments/800-38-series-drafts/gcm/joux_comments.pdf" class="pointer" target="_blank" rel="noopener"><u>allows key recovery</u></a> from the AES-GCM authentication tags. Since these tags are appended to AES-GCM ciphertexts for authentication, this enables plaintext recovery from as few as 2 ciphertexts encrypted with the same key and IV pair.</p> <p>In addition, <a href="https://eprint.iacr.org/2016/564.pdf" class="pointer" target="_blank" rel="noopener"><u>Bellare and Tackmann</u></a> have shown that the use of a deterministic IV can make it possible for a powerful adversary to brute-force a particular (key, IV) combination. This type of attack applies to powerful adversaries, if the crypto system is deployed to a very large (i.e., the size of the Internet) pool of (key, IV) combinations being chosen. Since WeChat has over a billion users, this order of magnitude puts this attack within the realm of feasibility.</p> <h3 id="lack-of-forward-secrecy" class="lh-solid mb3">Lack of forward secrecy</h3> <p class="mt0">Forward secrecy is <a href="https://github.com/ssllabs/research/wiki/SSL-and-TLS-Deployment-Best-Practices" class="pointer" target="_blank" rel="noopener"><u>generally expected</u></a> of modern communications protocols to reduce the importance of session keys. Generally, TLS itself is forward-secret by design, except in the case of the first packet of a “resumed” session. This first packet is encrypted with a “pre-shared key”, or PSK established during a previous handshake.</p> <p>MMTLS makes heavy use of PSKs by design. Since the Shortlink transport format only supports a single round-trip of communication (via a single HTTP POST request and response), any encrypted data sent via the transport format is encrypted with a pre-shared key. Since leaking the shared `PSK_ACCESS` secret would enable a third-party to decrypt any EarlyData sent across multiple MMTLS connections, data encrypted with the pre-shared key is not forward secret. The vast majority of records encrypted via MMTLS are sent via the Shortlink transport, which means that the majority of network data sent by WeChat is not forward-secret between connections. In addition, when opening the application, WeChat creates a single long-lived Longlink connection. This long-lived Longlink connection is open for the duration of the WeChat application, and any encrypted data that needs to be sent is sent over the same connection. Since most WeChat requests are either encrypted using (A) a session-resuming PSK or (B) the application data key of the long-lived Longlink connection, WeChat’s network traffic often does not retain forward-secrecy between network requests.</p> <h2 id="issues-with-business-layer-encryption" class="lh-solid mb3">Issues with Business-layer encryption</h2> <p class="mt0">On its own, the business-layer encryption construction, and, in particular the Symmetric Mode, AES-CBC construction, has many severe issues. Since the requests made by WeChat are double-encrypted, and these concerns only affect the inner, business layer of encryption, we did not find an immediate way to exploit them. However, in older versions of WeChat which exclusively used business-layer encryption, these issues would be exploitable.</p> <h3 id="metadata-leak" class="lh-solid mb3">Metadata leak</h3> <p class="mt0">Business-layer encryption does not encrypt metadata such as the user ID and request URI, as shown in the “Business-layer request” section. This issue is also <a href="https://cloud.tencent.com/developer/article/1005518" class="pointer" target="_blank" rel="noopener"><u>acknowledged</u></a> by the WeChat developers themselves to be one of the motivations to develop MMTLS encryption.</p> <h3 id="forgeable-gensignature-integrity-check" class="lh-solid mb3">Forgeable genSignature integrity check</h3> <p class="mt0">While the purpose of the <code>genSignature</code> code is not entirely clear, if it is being used for authentication (since the <code>ecdh_key</code> is included in the MD5) or integrity, it fails on both parts. A valid forgery can be calculated with any known <code>plaintext</code> without knowledge of the <code>ecdh_key</code>. If the client generates the following for some known plaintext message <code>plaintext</code>:</p> <div class="box box--code"><code>sig = adler32(md5(uin | ecdh_key | plaintext_len) | plaintext)</code></div> <p>We can do the following to forge the signature <code>evil_sig</code> for some <code>evil_plaintext</code> with length <code>plaintext_len</code>:</p> <div class="box box--code"><code>evil_sig = sig - adler32(plaintext) + adler32(evil_plaintext)</code></div> <p>Subtracting and adding from <code>adler32</code> checksums is achievable by solving for a system of equations <a href="https://en.wikipedia.org/wiki/Adler-32#Weakness" class="pointer" target="_blank" rel="noopener"><u>when the message is short</u></a>. Code for subtracting and adding to <code>adler32</code> checksum, thereby forging this integrity check, can be found in <code>adler.py</code> in <a href="https://github.com/citizenlab/wechat-security-report/blob/main/code/adler.py" class="pointer" target="_blank" rel="noopener"><u>our Github repository</u></a>.</p> <h3 id="possible-aes-cbc-padding-oracle" class="lh-solid mb3">Possible AES-CBC padding oracle</h3> <p class="mt0">Since AES-CBC is used alongside <a href="https://en.wikipedia.org/wiki/PKCS_7" class="pointer" target="_blank" rel="noopener"><u>PKCS7</u></a> padding, it is possible that the use of this encryption on its own would be susceptible to an <a href="https://en.wikipedia.org/wiki/Padding_oracle_attack" class="pointer" target="_blank" rel="noopener"><u>AES-CBC padding oracle</u></a>, which can lead to recovery of the encrypted plaintext. Earlier this year, we found that another custom cryptography scheme developed by a Tencent company was <a href="https://citizenlab.ca/2023/08/vulnerabilities-in-sogou-keyboard-encryption/" class="pointer"><u>susceptible to this exact attack</u></a>.</p> <h3 id="key-iv-re-use-in-block-cipher-mode" class="lh-solid mb3">Key, IV re-use in block cipher mode</h3> <p class="mt0">Re-using the key as the IV for AES-CBC, as well as re-using the same key for all encryption in a given session (i.e., the length of time that the user has the application opened) introduces some privacy issues for encrypted plaintexts. For instance, since the key and the IV provide all the randomness, re-using both means that if two plaintexts are identical, they will encrypt to the same ciphertext. In addition, due to the use of <a href="https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_block_chaining_(CBC)" class="pointer" target="_blank" rel="noopener"><u>CBC mode</u></a> in particular, two plaintexts with identical N block-length prefixes will encrypt to the same first N ciphertext blocks.</p> <h3 id="encryption-key-issues" class="lh-solid mb3">Encryption key issues</h3> <p class="mt0">It is highly unconventional for the server to choose the encryption key used by the client. In fact, we note that the encryption key generated by the server (the “session key”) exclusively uses printable ASCII characters. Thus, even though the key is 128 bits long, the entropy of this key is at most 106 bits.</p> <h3 id="no-forward-secrecy" class="lh-solid mb3">No forward secrecy</h3> <p class="mt0">As mentioned in the previous section, forward-secrecy is a standard property for modern network communication encryption. When the user is logged-in, all communication with WeChat, at this encryption layer, is done with the exact same key. The client does not receive a new key until the user closes and restarts WeChat.</p> <h1 id="other-versions-of-wechat" class="lh-solid mb3">Other versions of WeChat</h1> <p class="mt0">To confirm our findings, we also tested our decryption code on WeChat 8.0.49 for Android (released April 2024) and found that the MMTLS network format matches that used by WeChat 8.0.49 for iOS.</p> <h2 id="previous-versions-of-wechat-network-encryption" class="lh-solid mb3">Previous versions of WeChat network encryption</h2> <p class="mt0">To understand how WeChat’s complex cryptosystems are tied together, we also briefly reverse-engineered an older version of WeChat that did not utilize MMTLS. The newest version of WeChat that did not utilize MMTLS was v6.3.16, released in 2016. Our full notes on this reverse-engineering can be found <a href="https://github.com/citizenlab/wechat-security-report/blob/main/docs/wechat-v6.3.16.md" class="pointer" target="_blank" rel="noopener"><u>here</u></a>.</p> <p>While logged-out, requests were largely using the Business-layer Encryption cryptosystem, using RSA public-key encryption rather than static Diffie-Hellman plus symmetric encryption via AES-GCM. We observed requests to the internal URIs <code>cgi-bin/micromsg-bin/encryptcheckresupdate</code> and <code>cgi-bin/micromsg-bin/getkvidkeystrategyrsa</code>.</p> <p>There was also another encryption mode used, DES with a static key. This mode was used for sending crash logs and memory stacks; POST requests to the URI <code>/cgi-bin/mmsupport-bin/stackreport</code> were encrypted using DES.</p> <p>We were not able to login to this version for dynamic analysis, but from our static analysis, we determined that the encryption behaves the same as Business-layer Encryption when logged-in (i.e. using a <code>session_key</code> provided by the server for AES-CBC encryption).</p> <h1 id="discussion" class="lh-solid mb3">Discussion</h1> <h3 id="why-does-business-layer-encryption-matter" class="lh-solid mb3">Why does Business-layer encryption matter?</h3> <p class="mt0">Since Business-layer encryption is wrapped in MMTLS, why should it matter whether or not it is secure? First, from our study of previous versions of WeChat, Business-layer encryption was the sole layer of encryption for WeChat network requests until 2016. Second, from the the fact that Business-layer encryption exposes internal request URI unencrypted, one of the possible architectures for WeChat would be to host different internal servers to handle different types of network requests (corresponding to different “requestType” values and different cgi-bin request URLs). It could be the case, for instance, that after MMTLS is terminated at the front WeChat servers (handles MMTLS decryption), the inner WeChat request that is forwarded to the corresponding internal WeChat server is not re-encrypted, and therefore solely encrypted using Business-layer encryption. A network eavesdropper, or network tap, placed within WeChat’s intranet could then attack the Business-layer encryption on these forwarded requests. However, this scenario is purely conjectural. <span style="font-weight: 400">Tencent’s response to our disclosure is concerned with issues in Business-layer encryption and implies they are slowly migrating from the more problematic AES-CBC to AES-GCM, so Tencent is also concerned with this.</span></p> <h3 id="why-not-use-tls" class="lh-solid mb3">Why not use TLS?</h3> <p class="mt0">According to <a href="https://docs.google.com/document/d/14Gsqi3vWjXLhF3odeTjhq_njtTk4aFPr-LptPwuzMsw/edit" class="pointer" target="_blank" rel="noopener"><u>public documentation</u></a> and confirmed by our own findings, MMTLS (the “Outer layer” of encryption) is based heavily on TLS 1.3. In fact, the document demonstrates that the architects of MMTLS have a decent understanding of asymmetric cryptography in general.</p> <p>The document contains reasoning for not using TLS. It explains that the way WeChat uses network requests necessitates something like <a href="https://www.haproxy.com/glossary/what-is-zero-round-trip-time-resumption-0-rtt" class="pointer" target="_blank" rel="noopener"><u>0-RTT</u></a> session resumption, because the majority of WeChat data transmission needs only one request-response cycle (i.e., Shortlink). MMTLS only required one round-trip handshake to establish the underlying TCP connection before any application data can be sent; according to this document, introducing another round-trip for the TLS 1.2 handshake was a non-starter.</p> <blockquote><p>Fortunately, TLS1.3 proposes a 0-RTT (no additional network delay) method for the protocol handshake. In addition, the protocol itself provides extensibility through the version number, CipherSuite, and Extension mechanisms. However, TLS1.3 is still in draft phases, and its implementation may still be far away. TLS1.3 is also a general-purpose protocol for all apps, given the characteristics of WeChat, there is great room for optimization. Therefore, at the end, we chose to design and implement our own secure transport protocol, MMTLS, based on the TLS1.3 draft standard. [originally written in Chinese]</p></blockquote> <p>However, even at the time of writing in 2016, TLS 1.2 did provide an option for <a href="https://blog.cloudflare.com/tls-session-resumption-full-speed-and-secure/" class="pointer" target="_blank" rel="noopener"><u>session resumption</u></a>. In addition, since WeChat controls both the servers and the clients, it doesn’t seem unreasonable to deploy the fully-fledged TLS 1.3 implementations that were being tested at the time, even if the IETF draft was incomplete.</p> <p>Despite the architects of MMTLS’ best effort, generally, the security protocols used by WeChat seem both less performant and less secure than TLS 1.3. Generally speaking, designing a secure and performant transport protocol is no easy feat.</p> <p>The issue of performing an extra round-trip for a handshake has been a perennial issue for application developers. The TCP and TLS handshake each require a single round-trip, meaning each new data packet sent requires two round-trips. Today, TLS-over-QUIC combines the transport-layer and encryption-layer handshakes, requiring only a single handshake. QUIC provides the best of both worlds, both strong, forward-secret encryption, and halving the number of round-trips needed for secure communication. <strong>Our recommendation would be for WeChat to migrate to a standard QUIC implementation.</strong></p> <p>Finally, there is also the issue of client-side performance, in addition to network performance. Since WeChat’s encryption scheme performs two layers of encryption per request, the client is performing double the work to encrypt data, than if they used a single standardized cryptosystem.</p> <h3 id="the-trend-of-home-rolled-cryptography-in-chinese-applications" class="lh-solid mb3">The trend of home-rolled cryptography in Chinese applications</h3> <p class="mt0">The findings here contribute to much of <a href="https://citizenlab.ca/2024/04/vulnerabilities-across-keyboard-apps-reveal-keystrokes-to-network-eavesdroppers/" class="pointer"><u>our</u></a> <a href="https://citizenlab.ca/2016/03/privacy-security-issues-qq-browser/" class="pointer"><u>prior</u></a> <a href="https://citizenlab.ca/2015/05/a-chatty-squirrel-privacy-and-security-issues-with-uc-browser/" class="pointer"><u>research</u></a> that suggests the popularity of home-grown cryptography in Chinese applications. In general, the avoidance of TLS and the preference for proprietary and non-standard cryptography is a departure from cryptographic best practices. While there may have been many legitimate reasons to distrust TLS in 2011 (like <a href="https://www.usenix.org/legacy/events/sec11/tech/slides/eckersley.pdf" class="pointer" target="_blank" rel="noopener"><u>EFF</u></a> and <a href="https://www.accessnow.org/wp-content/uploads/archive/docs/Weakest_Link_in_the_Chain.pdf" class="pointer" target="_blank" rel="noopener"><u>Access Now</u></a>’s concerns over the certificate authority ecosystem), the TLS ecosystem has largely <a href="https://letsencrypt.org/stats/" class="pointer" target="_blank" rel="noopener"><u>stabilized</u></a> since then, and is more auditable and <a href="https://certificate.transparency.dev/" class="pointer" target="_blank" rel="noopener"><u>transparent</u></a>. Like MMTLS, all the proprietary protocols we have researched in the past contain weaknesses relative to TLS, and, in some cases, could even be <a href="https://citizenlab.ca/2024/04/vulnerabilities-across-keyboard-apps-reveal-keystrokes-to-network-eavesdroppers/" class="pointer"><u>trivially decrypted</u></a> by a network adversary. This is a growing, concerning trend unique to the Chinese security landscape as the global Internet progresses towards technologies like QUIC or TLS to protect data in transit.</p> <h3 id="anti-dns-hijacking-mechanisms" class="lh-solid mb3">Anti-DNS-hijacking mechanisms</h3> <p class="mt0">Similar to how Tencent wrote their own cryptographic system, we found that in Mars they also wrote a proprietary domain lookup system. This system is part of STN and has the ability to support domain name to IP address lookups over HTTP. This feature is referred to as “NewDNS” in Mars. Based on our dynamic analysis, this feature is regularly used in WeChat. At first glance, NewDNS duplicates the same functions already provided by DNS (Domain Name System), which is already built into nearly all internet-connected devices.</p> <p>WeChat is not the only app in China that utilizes such a system. Major cloud computing providers in China such as <a href="https://cn.aliyun.com/product/httpdns?from_alibabacloud=" class="pointer" target="_blank" rel="noopener"><u>Alibaba Cloud</u></a> and <a href="https://cloud.tencent.com/developer/article/2180368" class="pointer" target="_blank" rel="noopener"><u>Tencent Cloud</u></a> both offer their own DNS over HTTP service. A VirusTotal search for apps that tries to contact <a href="https://github.com/TencentCloud/httpdns-sdk-android" class="pointer" target="_blank" rel="noopener"><u>Tencent Cloud’s DNS over HTTP service endpoint (119.29.29.98)</u></a> yielded <a href="https://www.virustotal.com/gui/search/behavior_network%253A%2522119.29.29.98%2522/files" class="pointer" target="_blank" rel="noopener"><u>3,865 unique results</u></a>.</p> <p>One likely reason for adopting such a system is that ISPs in China often implement <a href="https://en.wikipedia.org/wiki/DNS_hijacking" class="pointer" target="_blank" rel="noopener"><u>DNS hijacking</u></a> to insert ads and redirect web traffic to perform <a href="https://www.oracle.com/uk/advertising/measurement/ad-fraud-invalid-traffic/" class="pointer" target="_blank" rel="noopener"><u>ad fraud</u></a>. The problem was so serious that six Chinese internet giants <a href="https://www.thepaper.cn/newsDetail_forward_1413110" class="pointer" target="_blank" rel="noopener"><u>issued a joint statement in 2015</u></a> urging ISPs to improve. According to the news article, about 1–2% of traffic to Meituan (an online shopping site) suffers from DNS hijacking. Ad fraud by Chinese ISPs seems to remain a <a href="https://web.archive.org/web/20240131063911/https://www.v2ex.com/t/651746" class="pointer" target="_blank" rel="noopener"><u>widespread</u></a> <a href="https://web.archive.org/web/20240131063930/https://m.thepaper.cn/baijiahao_15406173" class="pointer" target="_blank" rel="noopener"><u>problem</u></a> in recent years.</p> <p>Similar to their MMTLS cryptographic system, Tencent’s NewDNS domain lookup system was motivated by trying to meet the needs of the Chinese networking environment. DNS proper over the years has proven to have multiple <a href="https://en.wikipedia.org/w/index.php?title=Domain_Name_System&oldid=1232087380#Security_issues" class="pointer" target="_blank" rel="noopener"><u>security</u></a> and <a href="https://en.wikipedia.org/w/index.php?title=Domain_Name_System&oldid=1232087380#Privacy_and_tracking_issues" class="pointer" target="_blank" rel="noopener"><u>privacy</u></a> issues. Compared to TLS, we found that WeChat’s MMTLS has additional deficiencies. However, it remains an open question as to, when compared to DNS proper, whether NewDNS is more or less problematic. We leave this question for future work.</p> <h3 id="use-of-mars-stn-outside-wechat" class="lh-solid mb3">Use of Mars STN outside WeChat</h3> <p class="mt0">We speculate that there is a widespread adoption of Mars (mars-open) outside of WeChat, based on the following observations:</p> <ul> <li class="mt2">There are numerous <a href="https://github.com/Tencent/mars/issues" class="pointer" target="_blank" rel="noopener"><u>issues</u></a> opened on the Mars GitHub repository.</li> <li class="mt2">There are <a href="http://www.li5jun.com/article/662.html" class="pointer" target="_blank" rel="noopener"><u>plenty</u></a> <a href="https://blog.csdn.net/BunnyCoffer/article/details/80051766" class="pointer" target="_blank" rel="noopener"><u>of</u></a> technical <a href="https://segmentfault.com/a/1190000016558538" class="pointer" target="_blank" rel="noopener"><u>articles</u></a> outlining building instant messaging systems using Mars.</li> <li class="mt2">There is already <a href="https://docs.wildfirechat.cn/" class="pointer" target="_blank" rel="noopener"><u>a white-label instant messaging system product</u></a> that is based on Mars.</li> </ul> <p>The adoption of Mars outside of WeChat is concerning because Mars by default does not provide any transport encryption. As we have mentioned in the “Three Parts of Mars” section, the MMTLS encryption used in WeChat is part of mars-wechat, which <a href="https://github.com/Tencent/mars/issues/1023" class="pointer" target="_blank" rel="noopener"><u>is not open source</u></a>. The Mars developers <a href="https://github.com/Tencent/mars/issues/81" class="pointer" target="_blank" rel="noopener"><u>also have no plans to add support of TLS, and expect other developers using Mars to implement their own encryption in the upper layers</u></a>. To make matters worse, implementing TLS within Mars <a href="https://github.com/Tencent/mars/issues/184" class="pointer" target="_blank" rel="noopener"><u>seems to require a fair bit of architectural changes</u></a>. Even though it would not be unfair for Tencent to keep MMTLS proprietary, MMTLS is still the main encryption system that Mars was designed for, leaving MMTLS proprietary would mean other developers using Mars would have to either devote significant resources to integrate a different encryption system with Mars, or leave everything unencrypted.</p> <p>Mars is also lacking in documentation. The official <a href="https://github.com/Tencent/mars/wiki/" class="pointer" target="_blank" rel="noopener"><u>wiki</u></a> only contains a few, old articles on how to integrate with Mars. Developers using Mars often resort to <a href="https://github.com/Tencent/mars/issues/639" class="pointer" target="_blank" rel="noopener"><u>asking questions on GitHub</u></a>. The lack of documentation means that developers are more prone to making mistakes, and ultimately reducing security.</p> <p>Further research is needed in this area to analyze the security of apps that use Tencent’s Mars library.</p> <h3 id="tinker-a-dynamic-code-loading-module" class="lh-solid mb3">“Tinker”, a dynamic code-loading module</h3> <p class="mt0">In this section, we tentatively refer to the APK downloaded from the Google Play Store as “WeChat APK”, and the APK downloaded from WeChat’s official website as “Weixin APK”. The distinction between WeChat and Weixin seems blurry. The WeChat APK and Weixin APK contain partially different code, as we will later discuss in this section. However, when installing both of these APKs to an English-locale Android Emulator, they both show their app names as “WeChat”. Their application ID, which is used by the Android system and Google Play Store to identify apps, are also both “com.tencent.mm”. We were also able to login to our US-number accounts using both APKs.</p> <p>Unlike the WeChat APK, we found that the Weixin APK contains Tinker, <a href="https://github.com/Tencent/tinker" class="pointer" target="_blank" rel="noopener"><u>“a hot-fix solution library”</u></a>. Tinker allows the developer to update the app itself without calling Android’s system APK installer by using a technique called “dynamic code loading”. In an earlier report we found a similar <a href="https://citizenlab.ca/2021/03/tiktok-vs-douyin-security-privacy-analysis/" class="pointer"><u>distinction</u></a> between TikTok and Douyin, where we found Douyin to have a similar dynamic code-loading feature that was not present in TikTok. This feature raises three concerns:</p> <ol type="1"> <li class="mt2">If the process for downloading and loading the dynamic code does not sufficiently authenticate the downloaded code (e.g., that it is cryptographically signed with the correct public key, that it is not out of date, and that it is the code intended to be downloaded and not other cryptographically signed and up-to-date code), an attacker might be able to exploit this process to run malicious code on the device (e.g., by injecting arbitrary code, by performing a downgrade attack, or by performing a sidegrade attack). Back in 2016, we found such instances in <a href="https://citizenlab.ca/2016/02/privacy-security-issues-baidu-browser/" class="pointer"><u>other</u></a> <a href="https://citizenlab.ca/2016/03/privacy-security-issues-qq-browser/" class="pointer"><u>Chinese</u></a> <a href="https://citizenlab.ca/2016/08/a-tough-nut-to-crack-look-privacy-and-security-issues-with-uc-browser/" class="pointer"><u>apps</u></a>.</li> <li class="mt2">Even if the code downloading and loading mechanism contains no weaknesses, the dynamic code loading feature still allows the application to load code without notifying the user, bypassing users’ consent to decide what program could run on their device. For example, the developer may push out an unwanted update, and the users do not have a choice to keep using the old version. Furthermore, a developer may selectively target a user with an update that compromises their security or privacy. In 2016, a Chinese security analyst <a href="https://web.archive.org/web/20160330060941/news.boxun.com/news/gb/china/2016/02/201602231542.shtml" class="pointer" target="_blank" rel="noopener"><u>accused</u></a> Alibaba of pushing dynamically loaded code to Alipay to surreptitiously take photos and record audio on his device.</li> <li class="mt2">Dynamically loading code deprives app store reviewers from reviewing all relevant behavior of an app’s execution. As such, the <a href="https://support.google.com/googleplay/android-developer/answer/14906471?hl=en" class="pointer" target="_blank" rel="noopener"><u>Google Play Developer Program Policy</u></a> does not permit apps to use dynamic code loading.</li> </ol> <p>When analyzing the WeChat APK, we found that, while it retains some components of Tinker. The component which seems to handle the downloading of app updates is present, however the core part of Tinker that handles loading and executing the downloaded app updates has been replaced with “no-op” functions, which perform no actions. We did not analyze the WeChat binaries available from other third party app stores.</p> <p>Further research is needed to analyze the security of Tinker’s app update process, whether WeChat APKs from other sources contain the dynamic code loading feature, as well as any further differences between the WeChat APK and Weixin APK.</p> <h1 id="recommendations" class="lh-solid mb3">Recommendations</h1> <p class="mt0">In this section, we make recommendations based on our findings to relevant audiences.</p> <h3 id="to-application-developers" class="lh-solid mb3">To application developers</h3> <p class="mt0">Implementing proprietary encryption is more expensive, less performant, and <a href="https://www.schneier.com/blog/archives/2011/04/schneiers_law.html" class="pointer" target="_blank" rel="noopener"><u>less secure</u></a> than using well-scrutinized standard encryption suites. Given the sensitive nature of data that can be sent by applications, we encourage application developers to use tried-and-true encryption suites and protocols and to avoid rolling their own crypto. SSL/TLS has seen almost three decades of various improvements as a result of rigorous public and academic scrutiny. TLS configuration is now easier than ever before, and the advent of QUIC-based TLS has dramatically improved performance.</p> <h3 id="to-tencent-and-wechat-developers" class="lh-solid mb3">To Tencent and WeChat developers</h3> <p class="mt0">Below is a copy of the recommendations we sent to WeChat and Tencent in our disclosure. The full disclosure correspondence can be found in the <a href="#appendix" class="pointer">Appendix</a>.</p> <blockquote><p>In <a href="https://github.com/WeMobileDev/article/blob/master/%E5%9F%BA%E4%BA%8ETLS1.3%E7%9A%84%E5%BE%AE%E4%BF%A1%E5%AE%89%E5%85%A8%E9%80%9A%E4%BF%A1%E5%8D%8F%E8%AE%AEmmtls%E4%BB%8B%E7%BB%8D.md" class="pointer" target="_blank" rel="noopener"><u>this post from 2016</u></a>, WeChat developers note that they wished to upgrade their encryption, but the addition of another round-trip for the TLS 1.2 handshake would significantly degrade WeChat network performance, as the application relies on many short bursts of communication. At that time, TLS 1.3 was not yet an RFC (though session resumption extensions were available for TLS 1.2), so they opted to “roll their own” and incorporate TLS 1.3’s session resumption model into MMTLS.</p> <p>This issue of performing an extra round-trip for a handshake has been a perennial issue for application developers around the world. The TCP and TLS handshake each require a single round-trip, meaning each new data packet sent requires two round-trips. Today, TLS-over-QUIC combines the transport-layer and encryption-layer handshakes, requiring only a single handshake. QUIC was developed for this express purpose, and can provide both strong, forward-secret encryption, while halving the number of round-trips needed for secure communication. We also note that WeChat seems to already use QUIC for some large file downloads. <strong>Our recommendation would be for WeChat to migrate entirely to a standard TLS or QUIC+TLS implementation.</strong></p> <p>There is also the issue of client-side performance, in addition to network performance. Since WeChat’s encryption scheme performs two layers of encryption per request, the client is performing double the work to encrypt data than if WeChat used a single standardized cryptosystem.</p></blockquote> <h3 id="to-operating-systems" class="lh-solid mb3">To operating systems</h3> <p class="mt0">On the web, client-side browser <a href="https://security.googleblog.com/2016/09/moving-towards-more-secure-web.html" class="pointer" target="_blank" rel="noopener"><u>security</u></a> <a href="https://blog.mozilla.org/security/2017/01/20/communicating-the-dangers-of-non-secure-http/" class="pointer" target="_blank" rel="noopener"><u>warnings</u></a> and the use of HTTPS as <a href="https://developers.google.com/search/blog/2014/08/https-as-ranking-signal" class="pointer" target="_blank" rel="noopener"><u>a ranking factor</u></a> in search engines contributed to widespread TLS adoption. We can draw loose analogies to the mobile ecosystem’s operating systems and application stores.</p> <p>Is there any platform or OS-level permission model that can indicate regular usage of standard encrypted network communications? As we mentioned in our prior work studying proprietary cryptography in <a href="https://citizenlab.ca/2024/04/vulnerabilities-across-keyboard-apps-reveal-keystrokes-to-network-eavesdroppers/" class="pointer"><u>Chinese IME keyboards</u></a>, OS developers could consider device permission models that surface whether applications use lower-level system calls for network access.</p> <h3 id="to-high-risk-users-with-privacy-concerns" class="lh-solid mb3">To high-risk users with privacy concerns</h3> <p class="mt0">Many WeChat users use it out of necessity rather than choice. For users with privacy concerns who are using WeChat out of necessity, our recommendations from <a href="https://citizenlab.ca/2023/06/privacy-in-the-wechat-ecosystem-full-report/" class="pointer"><u>the previous report</u></a> still hold:</p> <ul> <li class="mt2">Avoid features delineated as “Weixin” services if possible. We note that many core “Weixin” services (such as Search, Channels, Mini Programs) as delineated by the Privacy Policy perform more tracking than core “WeChat” services.</li> <li class="mt2">When possible, prefer web or applications over Mini Programs or other such embedded functionality.</li> <li class="mt2">Use stricter device permissions and update your software and OS regularly for security features.</li> </ul> <p>In addition, due to the risks introduced by dynamic code loading in WeChat downloaded from the official website, we recommend users to instead download WeChat from the Google Play Store whenever possible. For users who have already installed WeChat from the official website, removing and re-installing the Google Play Store version would also mitigate the risk.</p> <h3 id="to-security-and-privacy-researchers" class="lh-solid mb3">To security and privacy researchers</h3> <p class="mt0">As WeChat has over one billion users, we posit that the order of magnitude of global MMTLS users is on a similar order of magnitude as global TLS users. Despite this, there is little-to-no third-party analysis or scrutiny of MMTLS, as there is in TLS. At this scale of influence, MMTLS deserves similar scrutiny as TLS. We implore future security and privacy researchers to build on this work to continue the study of the MMTLS protocol, as from our correspondences, Tencent insists on continuing to use and develop MMTLS for WeChat connections.</p> <h1 class="lh-solid mb3"><span style="font-weight: 400">Acknowledgments</span></h1> <p class="mt0"><span style="font-weight: 400">We would like to thank Jedidiah Crandall, Jakub Dalek, Prateek Mittal, and Jonathan Mayer for their guidance and feedback on this report. Research for this project was supervised by Ron Deibert.</span></p> <h1 id="appendix" class="lh-solid mb3">Appendix</h1> <p class="mt0">In this appendix, we detail our disclosure to Tencent concerning our findings and their response.</p> <h2 id="april-24-2024-our-disclosure" class="lh-solid mb3">April 24, 2024 — Our disclosure</h2> <p class="mt0">To Whom It May Concern:</p> <p>The Citizen Lab is an academic research group based at the Munk School of Global Affairs & Public Policy at the University of Toronto in Toronto, Canada.</p> <p>We analyzed WeChat v8.0.23 on Android and iOS as part of our ongoing work analyzing popular mobile and desktop apps for security and privacy issues. We found that WeChat’s proprietary network encryption protocol, MMTLS, contains weaknesses compared to modern network encryption protocols, such as TLS or QUIC+TLS. For instance, the protocol is not forward-secret and may be susceptible to replay attacks. We plan on publishing a documentation of the MMTLS network encryption protocol and strongly suggest that WeChat, which is responsible for the network security of over 1 billion users, switch to a strong and performant encryption protocol like TLS or QUIC+TLS.</p> <p>For further details, please see the <a href="https://citizenlab.ca/wp-content/uploads/2024/10/CL-Disclosure-042424.docx" class="pointer"><strong>attached document</strong></a>.</p> <p><strong>Timeline to Public Disclosure</strong></p> <p>The Citizen Lab is committed to research transparency and will publish details regarding the security vulnerabilities it discovers in the context of its research activities, absent exceptional circumstances, on its website: https://citizenlab.ca/.</p> <p>The Citizen Lab will publish the details of our analysis no sooner than 45 calendar days from the date of this communication.</p> <p>Should you have any questions about our findings please let us know. We can be reached at this email address: <a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="d8bcb1abbbb4b7abadaabd98bbb1acb4b9baf6adacb7aab7b6acb7f6bbb9">[email protected]</a>.</p> <p>Sincerely,</p> <p>The Citizen Lab</p> <h2 id="may-17-2024-tencents-response" class="lh-solid mb3">May 17, 2024 — Tencent’s response</h2> <p class="mt0">Thank you for your report.Since receiving your report on April 25th, 2024, we have conducted a careful evaluation.The core of WeChat’s security protocol is outer layer mmtls encryption, currently ensuring that outer layer mmtls encryption is secure. On the other hand, the encryption issues in the inner layer are handled as follows: the core data traffic has been switched to AES-GCM encryption, while other traffic is gradually switching from AES-CBC to AES-GCM.If you have any other questions, please let us know.thanks.</p> <section id="footnotes" class="footnotes footnotes-end-of-document" role="doc-endnotes"> <hr> <ol> <li id="fn1" class="mt2">The terms “shortlink” and “longlink” do not seem to be specific to WeChat, since it was also mentioned in <a href="http://www.52im.net/thread-3908-1-1.html" class="pointer" target="_blank" rel="noopener"><u>other technical blogs</u></a>.<a class="footnote-back pointer" role="doc-backlink" href="#fnref1">↩︎</a></li> <li id="fn2" class="mt2">On Android, the main process is named after the app ID, “com.tencent.mm”. (The process name can be seen using the ps command in adb shell.) When an app starts a new process, it assigns a name. The assigned name will be added to the app ID to form the full name of the new process. So the “:push” process’s full name is “com.tencent.mm:push”.<a class="footnote-back pointer" role="doc-backlink" href="#fnref2">↩︎</a></li> <li id="fn3" class="mt2">This server heartbeat is a reply to a prior client-sent heartbeat.<a class="footnote-back pointer" role="doc-backlink" href="#fnref3">↩︎</a></li> </ol> </section> </section> <footer> </footer> </article> <aside class="social-sidebar"> <div id="social-sidebar" role="complementary" class="w-100"> </div> </aside> </section> </main> </div> <footer role="contentinfo" itemscope itemtype="http://schema.org/WPFooter" class="footer"> <div class="footer__container"> <nav role="navigation" class="footer__nav"> <h2>Research</h2> <div class="footer-links cf"><ul id="menu-research" class="list pa0"><li id="menu-item-29711" class="menu-item menu-item-type-taxonomy menu-item-object-category menu-item-29711"><a href="https://citizenlab.ca/category/research/targeted-threats/" class="lh-title mb2 db white b no-underline underline-hover">Targeted Threats</a></li> <li id="menu-item-29709" class="menu-item menu-item-type-taxonomy menu-item-object-category menu-item-29709"><a href="https://citizenlab.ca/category/research/free-expression-online/" class="lh-title mb2 db white b no-underline underline-hover">Free Expression Online</a></li> <li id="menu-item-29712" class="menu-item menu-item-type-taxonomy menu-item-object-category menu-item-29712"><a href="https://citizenlab.ca/category/research/transparency/" class="lh-title mb2 db white b no-underline underline-hover">Transparency and Accountability</a></li> <li id="menu-item-29708" class="menu-item menu-item-type-taxonomy menu-item-object-category current-post-ancestor current-menu-parent current-post-parent menu-item-29708"><a href="https://citizenlab.ca/category/research/app-privacy-and-security/" class="lh-title mb2 db white b no-underline underline-hover">App Privacy and Controls</a></li> <li id="menu-item-29710" class="menu-item menu-item-type-taxonomy menu-item-object-category menu-item-29710"><a href="https://citizenlab.ca/category/research/global-research-network/" class="lh-title mb2 db white b no-underline underline-hover">Global Research Network</a></li> <li id="menu-item-72386" class="menu-item menu-item-type-taxonomy menu-item-object-category menu-item-72386"><a href="https://citizenlab.ca/category/research/tools-resources/" class="lh-title mb2 db white b no-underline underline-hover">Tools & Resources</a></li> <li id="menu-item-29713" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-29713"><a href="https://citizenlab.ca/publications/" class="lh-title mb2 db white b no-underline underline-hover">All Publications</a></li> </ul></div> </nav> <nav role="navigation" class="footer__nav"> <h2>News</h2> <div class="footer-links cf"><ul id="menu-news" class="list pa0"><li id="menu-item-29714" class="menu-item menu-item-type-taxonomy menu-item-object-category menu-item-29714"><a href="https://citizenlab.ca/category/lab-news/mentions/" class="lh-title mb2 db white b no-underline underline-hover">In the Media</a></li> <li id="menu-item-29715" class="menu-item menu-item-type-taxonomy menu-item-object-category menu-item-29715"><a href="https://citizenlab.ca/category/lab-news/events/" class="lh-title mb2 db white b no-underline underline-hover">Events</a></li> <li id="menu-item-29716" class="menu-item menu-item-type-taxonomy menu-item-object-category menu-item-29716"><a href="https://citizenlab.ca/category/lab-news/opportunities/" class="lh-title mb2 db white b no-underline underline-hover">Opportunities</a></li> <li id="menu-item-29717" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-29717"><a href="https://citizenlab.ca/newsletter/archives/" class="lh-title mb2 db white b no-underline underline-hover">Newsletter Archives</a></li> </ul></div> </nav> <nav role="navigation" class="footer__nav"> <h2>About</h2> <div class="footer-links cf"><ul id="menu-about" class="list pa0"><li id="menu-item-29718" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-29718"><a href="https://citizenlab.ca/about/" class="lh-title mb2 db white b no-underline underline-hover">About The Citizen Lab</a></li> <li id="menu-item-29720" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-29720"><a href="https://citizenlab.ca/people/" class="lh-title mb2 db white b no-underline underline-hover">People</a></li> <li id="menu-item-68022" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-68022"><a href="https://citizenlab.ca/media/" class="lh-title mb2 db white b no-underline underline-hover">Media Resources</a></li> <li id="menu-item-29721" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-29721"><a href="https://citizenlab.ca/teaching/" class="lh-title mb2 db white b no-underline underline-hover">Teaching</a></li> <li id="menu-item-68345" class="menu-item menu-item-type-custom menu-item-object-custom menu-item-68345"><a href="https://donate.utoronto.ca/give/show/84" class="lh-title mb2 db white b no-underline underline-hover">Donate</a></li> </ul></div> </nav> </div> <!-- Social Media & Newletter --> <div class="footer__container mt4 relative pt3-ns bt b--gray"> <div class="flex-ns justify-between w-100"> <div class="w-30-ns w-100 mb3 mr3-ns pt3"> <h2 class="ttu mt0 mb2 f4">Connect</h2> <div class="social-media"> <a class="dim" href="https://x.com/citizenlab" aria-label="Visit our Twitter/X account"><span class="fa-brands fa-twitter white" aria-hidden="true"></span></a> <a class="dim" rel="me" href="https://mastodon.social/@citizenlab" aria-label="Follow our Mastodon account"><span class="fa-brands fa-mastodon white" aria-hidden="true"></span></a> <a class="dim" href="https://www.youtube.com/channel/UCf5Aunw7xvt3lAFrLhiCA5w" aria-label="Visit our Youtube page"><span class="fa-brands fa-youtube white" aria-hidden="true"></span></a> <a class="dim" href="/cdn-cgi/l/email-protection#2f46415e5a465d464a5c6f4c465b46554a41434e4d014c4e" aria-label="Email us"><span class="fa-solid fa-envelope white" aria-hidden="true"></span></a> <a class="dim" href="https://github.com/citizenlab" aria-label="Visit oour Github"><span class="fa-brands fa-github white" aria-hidden="true"></span></a> </div> </div> <div class="w-60-ns f6 w-100 pt3"> <h2 class="f4 ttu mb2 mt3 mt0-ns">Newsletter</h2> <div id="text-3"> <div class="textwidget"><script data-cfasync="false" src="/cdn-cgi/scripts/5c5dd728/cloudflare-static/email-decode.min.js"></script><script>(function() { window.mc4wp = window.mc4wp || { listeners: [], forms: { on: function(evt, cb) { window.mc4wp.listeners.push( { event : evt, callback: cb } ); } } } })(); </script><!-- Mailchimp for WordPress v4.9.19 - https://wordpress.org/plugins/mailchimp-for-wp/ --><form id="mc4wp-form-1" class="mc4wp-form mc4wp-form-29703" method="post" data-id="29703" data-name="" ><div class="mc4wp-form-fields"><input type="email" name="EMAIL" placeholder="Your email address" required class="dib pv1 mr2 mv1 lh-solid mw4"/><input type="submit" value="Sign up" class="link br1 b--none lh-solid cta-button-orange b pointer"/></div><label style="display: none !important;">Leave this field empty if you're human: <input type="text" name="_mc4wp_honeypot" value="" tabindex="-1" autocomplete="off" /></label><input type="hidden" name="_mc4wp_timestamp" value="1732505326" /><input type="hidden" name="_mc4wp_form_id" value="29703" /><input type="hidden" name="_mc4wp_form_element_id" value="mc4wp-form-1" /><div class="mc4wp-response"></div></form><!-- / Mailchimp for WordPress Plugin --> </div> </div> </div> </div> </div> </footer> <div id="privacy-footer"> <div class="mv0 dib"> <div id="text-5"> <div class="textwidget"><p><a class="db white dim" href="https://citizenlab.ca/privacy/">Privacy Policy</a></p> </div> </div> </div> <div class="mv0 dib ph3-l"> <div id="text-4"> <div class="textwidget"><p>Unless otherwise noted this site and its contents are licensed under a <a class="white dim" href="https://creativecommons.org/licenses/by/2.5/ca/">Creative Commons Attribution 2.5 Canada</a> license.</p> </div> </div> </div> <div class="dib mv0 mt2 lh0 mw5"> <a href="http://munkschool.utoronto.ca/" target="blank"> <img src="https://citizenlab.ca/wp-content/themes/citizenlab-2.1.2/library/images/MunkSchool-WHT.png" alt="Munk School of Global Affairs & Public Policy | University of Toronto" /> </a> </div> </div> <script>(function() {function maybePrefixUrlField () { const value = this.value.trim() if (value !== '' && value.indexOf('http') !== 0) { this.value = 'http://' + value } } const urlFields = document.querySelectorAll('.mc4wp-form input[type="url"]') for (let j = 0; j < urlFields.length; j++) { urlFields[j].addEventListener('blur', maybePrefixUrlField) } })();</script><script type="text/javascript" src="https://citizenlab.ca/wp-content/plugins/bigfoot_footnotes/library/bigfoot.js" id="bigfoot-js"></script> <script type="text/javascript" src="https://citizenlab.ca/wp-content/plugins/bigfoot_footnotes/library/bigfoot.min.js" id="bigfoot-min-js"></script> <script type="text/javascript" src="https://citizenlab.ca/wp-content/plugins/bigfoot_footnotes/library/bigfoot-function.js" id="bigfoot-function-js"></script> <script type="text/javascript" src="https://citizenlab.ca/wp-content/plugins/youtube-embed-plus/scripts/fitvids.min.js" id="__ytprefsfitvids__-js"></script> <script type="text/javascript" src="https://citizenlab.ca/wp-content/themes/citizenlab-2.1.2/library/js/search-menu.js" id="search-menu-js"></script> <script type="text/javascript" src="https://citizenlab.ca/wp-content/themes/citizenlab-2.1.2/library/js/jquery-details/jquery.details.min.js" id="jquery-details-js"></script> <script type="text/javascript" defer src="https://citizenlab.ca/wp-content/plugins/mailchimp-for-wp/assets/js/forms.js" id="mc4wp-forms-api-js"></script> </body> </html> <!-- end of site. what a ride! --> <!-- Performance optimized by Redis Object Cache. Learn more: https://wprediscache.com Retrieved 2877 objects (1 MB) from Redis using PhpRedis (v6.0.2). -->