initial readme and generic impersonation code
This commit is contained in:
parent
b94553c2d8
commit
1bd45ec2fa
166
lg_webOS/README.md
Normal file
166
lg_webOS/README.md
Normal file
@ -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 <device`, we get:
|
||||
|
||||
```
|
||||
PORT STATE SERVICE VERSION
|
||||
1175/tcp open upnp
|
||||
3000/tcp open http LG smart TV http service
|
||||
3001/tcp open ssl/http LG smart TV http service
|
||||
9998/tcp open http Google Chromecast httpd
|
||||
```
|
||||
|
||||
aside from the obvious flag running of both HTTP and HTTPS versions of (likely) the same service,
|
||||
interested to see that the Chromecast plugged in to the TV is also being exposed on the same IP as the TV.
|
||||
|
||||
since there is an [LG smart TV](TODO) app available for Android/iOS, assuming that there is an API of some sort running on `3000` or `3001`, so:
|
||||
|
||||
```
|
||||
$ curl http://<device>: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
|
||||
<REQUEST>
|
||||
<PRODUCT_NM>webOSTV 3.0</PRODUCT_NM>
|
||||
<MODEL_NM>HE_DTV_W16P_AFADATAA</MODEL_NM>
|
||||
<SW_TYPE>FIRMWARE</SW_TYPE>
|
||||
<MAJOR_VER>04</MAJOR_VER>
|
||||
<MINOR_VER>30.40</MINOR_VER>
|
||||
<COUNTRY>US2</COUNTRY>
|
||||
<COUNTRY_GROUP>US</COUNTRY_GROUP>
|
||||
<DEVICE_ID>de:ad:be:ef:ca:fe</DEVICE_ID>
|
||||
<AUTH_FLAG>N</AUTH_FLAG>
|
||||
<IGNORE_DISABLE>N</IGNORE_DISABLE>
|
||||
<ECO_INFO>01</ECO_INFO>
|
||||
<CONFIG_KEY>00</CONFIG_KEY>
|
||||
<LANGUAGE_CODE>en-US</LANGUAGE_CODE>
|
||||
</REQUEST>
|
||||
```
|
||||
|
||||
pretty standard, but the `auth_flag`, `ignore_disable` and `config_key` values are potentially interesting
|
||||
|
||||
response:
|
||||
```xml
|
||||
<RESPONSE>
|
||||
<RESULT_CD>900</RESULT_CD>
|
||||
<MSG>Success</MSG>
|
||||
<REQ_ID>00000000000000000001</REQ_ID>
|
||||
<IMAGE_URL></IMAGE_URL>
|
||||
<IMAGE_SIZE></IMAGE_SIZE>
|
||||
<IMAGE_NAME></IMAGE_NAME>
|
||||
<UPDATE_MAJOR_VER></UPDATE_MAJOR_VER>
|
||||
<UPDATE_MINOR_VER></UPDATE_MINOR_VER>
|
||||
<FORCE_FLAG></FORCE_FLAG>
|
||||
<KE></KE>
|
||||
<GMT>16 Nov 2016 08:23:56 GMT</GMT>
|
||||
<ECO_INFO>01</ECO_INFO>
|
||||
<CDN_URL></CDN_URL>
|
||||
<CONTENTS></CONTENTS>
|
||||
</RESPONSE>
|
||||
```
|
||||
|
||||
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
|
89
lg_webOS/impersonate-lge.com.rb
Normal file
89
lg_webOS/impersonate-lge.com.rb
Normal file
@ -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
|
Loading…
Reference in New Issue
Block a user