2020-07-05

Gramblr - how I "hacked an Instagram" once

Intro.

Back in the beginning of 2016 when I got my first smartphone and was introduced with smart-world I was looking for an options to upload the image in Instagram (IG) from the PC directly. And I found this partially crippled but so widely used system - Gramblr. It was built online but it has its own downloadable web client. It was more than just an image uploader for Instagram. It has its own lets-make-profit system with users earning credits by giving likes to other user IG images. It had this 2:1 system - you have to like two other images and then you got one credit. And you can spend this Gramblr credit exchanging it to a one "like". There was a list of 100 images loaded for you as an user to check whether you like an image or not. Of course, simple users were earning these credits by clicking on all images posted to maximize their credit profit. The whole system was flawed by this "earning credits" system but somebody got profit. If you want you could buy credits. If I`m not mistaken, 100 guaranteed likes (credits) costed 5 bucks.
Earn Coints to Get More Likes via Gramblr - Techtippr
This is the moment where things got interesting for me. It should be possible to automatize this gaining credits algorithm. And how does the credit system work? And I started to explore.

Tech

I was wondering how does this web client Gramblr.exe works. My antivirus and google chrome shows it as a dangerous malware software. I`m not a security expert at all, I just wanted to see, what the hell it is doing and how does it work and processes and etc. It was blended rather deep in the windows (Yea, I`m sitting on Win 7) automatic startup - it had its service, it had registry entries.
And how does it communicate? How does the data is sent, encoded?

Next finding was that my client is actually a simple web-server passing and receiving web requests to the Gramblr online webserver. Well, this gives a relatively simple approach to automatize requests. Of course, requests need analysis, whats in them, how do they work. And I started to play with Python 2.7. The required libraries were:

  1. import requests
  2. import json
  3. import random
  4. import re
  5. import time

so - how to connect? There is a login screen, so I have to be logged in. I was lazy and didnt make any login requests, I noticed that there is a cookie which changes every time I log in and log out and as I mostly stayed logged in, I cheated and logged in in the web client and copied cookie`s id from there for my script.
This was all I need to do a handshake with a server.

  1.  def gramblr_req(self):
  2.         """
  3.         Does GET request to get basic info of user
  4.         """
  5.         headers = {
  6.             "Accept-Encoding":self.h_encoding,
  7.             "Accept-Language":self.h_language,
  8.             "User-Agent":self.h_useragent,
  9.             "Accept":"application/json, text/plain, */*",
  10.             "Referer":self.localhost_url,
  11.             "Cookie":self.h_cookie,
  12.             "Connection":self.h_connection
  13.         }
  14.         try:
  15.             results = requests.get(self.gramblr_url, headers=headers)
  16.             self.coins=json.loads(results._content)["coins"]
  17.         except:
  18.             self.gramblr_req()

as I was lazy I didnt check was all header info actually needed.
After successful handshake the result was a JSON file with all info about my profile. What I was after was credits in the system, called coins. 1 credit = 5 coins. From the script one can recognize a Python class.

When I am authenticated in the system now, next thing was to get a list with an images and all necessary info which were posted by other users to gain likes. In the web client these images were loaded in the "Earn Coins" section. User could click on the images he/she likes and gain those coins. So, I had to emulate this request.

  1.  def GET_list(self):


  2.         results={}
  3.         results["list"]=False
  4.         try:
  5.             counter=0
  6.             while not results["list"]:
  7.                 results = requests.get(self.earn_coins_url, self.headers)
  8.                 self.results=results
  9.                 localtime = time.asctime( time.localtime(time.time()) )
  10.                 counter=counter+1
  11.                 results=json.loads(results._content)
  12.                 time.sleep(1)
  13.             fullstrings=""
  14.             iterstrings=iter(results["list"])

  15.             for each in iterstrings:
  16.                 fullstrings=fullstrings+'{"id":'+str(each["id"])+'},'
  17.             self.liste='{"liked":[],"skipped":[],"ignored":['+fullstrings[:-1]+'],"ig_user":"myinstagramusername"}'

  18.             self.results= self.results._content
  19.         except:
  20.             self.GET_list()
  21.         try:
  22.             outF=open("myOutFile.txt","r+")
  23.             userlist=set(str(line.strip()) for line in outF)
  24.             outF.close()
  25.             for counter, each in enumerate(results["list"]):
  26.                  userlist.add(str(each["details"]["username"]))
  27.             userlist=sorted(userlist)
  28.             outF=open("myOutFile.txt","r+")

  29.             for each in userlist:
  30.                 outF.write(each)
  31.                 outF.write("\n")
  32.             outF.close()
  33.         
  34.         except:
  35.             pass

This was kinda tricky. I noticed that when I was jumping between multiple web clients the list of images sometimes loaded incompletely, sometimes disappeared. From the code snipped one can see the recursion in case of failed requests. Yea, as I was interested in making an automation, its good as long as it works. And it worked.
The other thing I was interested was unique users using Gramblr.  That is why the last part of the script is doing - scraping the usernames and accumulating in the list. I managed to find 98`976 unique users which means max likes one could get is this number which is unrealistic.
When I know which pictures are in list, I can move to "giving likes" request for each picture. But before there, notice the line 40 in previous snippet. A class variable "liste" in JSON format was prepared and this was crucial for the "giving likes" request:

  1. def POST_list(self):
  2.         values=self.liste
  3.         results = requests.post(self.give_like_url,values,self.headers)


Just that simple. Previously prepared JSON data was sent to the server containing which images got likes, which did not. The whole list was passed to the server and it had a weird behavior and also consequences. Through experiments I found a flaw here. The order of requests was the key to successfully gain credits more than I expected with just a simple bot-like automation. It could be done manually with two web-clients open simultaneously too and by clicking on images with a mouse just in a right order.

And this is the last request important for the whole "gaining free more than expected credits" story - request to give a like to some IG image. In reality it meant that this image showed up in the Gramblr image list for credits and everybody who wanted to gain some credits had to give a like to it to earn one credit.

  1.  def POST_like(self,fname):
  2.   
  3.         self.pickIGfromFile(fname)
  4.          try:
  5.             values='{"ig_user":"myinstagramusername",' \
  6.                    '"likes_qty":1,' \
  7.                    '"local_likes":false,' \
  8.                    '"media_pk":"'+self.igUser["media_pk"]+'",' \
  9.                    '"user_pk":'+str(self.igUser["user_pk"])+'}'

  10.             results = requests.post(self.add_likes_url,values,self.headers)
  11.         except:
  12.             self.POST_like(fname)       #recursion

What one can see in this snippet is that I had IG links already prepared in a file and there is another helper function which just picks random link in that file and passes it to the Gramblr request.


I already mentioned "The right order of requests" which was the successful key of gaining more than I expected of this automation. By sending requests in particular order they somewhat reset the credit counting and accumulation in the Gramblr online webserver.

  1. if __name__=="__main__":
  2.     G = Gramblrscripter()
  3.     G.gramblr_req()
  4.     coins_b=G.coins
  5.     ii=1000

  6.     for i in range(ii):

  7.         G.GET_list() #loads image list 
  8.         G.POST_like('iglinks.txt') #gives one like to an IG image (link needed)
  9.         G.POST_list() #noposto listi #posts image list which got likes and which didnt

  10.         G.gramblr_req()
  11.         coins_e=G.coins
  12.         print "gained likes: \t\t\t\t\t|\t", (coins_e-coins_b)/10
  13.         coins_b=coins_e

Most likely this "giving like" request in between both image list requests made Gramblr system to reset credit counting/waiting/managing flag in their database.

The final math and maximized credit gaining data flow would look like this:

0) Lets say, I have 2 credits in the beginning

1) Image List loaded with 100 images - max 100 credits waiting for me to get
2) IG image liked -online system, db reset, 2 credits spent. Now I have 0 credits.
3) modified Image List posted with first image getting only one like from me - I spend 2 credits, I gain 100 credits instead of 1.

///cycle continues///Now I have 100 credits

4) Image List loaded with 99 images (the one I previously gave like in the step 3 is already out of the list) - max 99 credits waiting for me to get
5) IG image liked - online system, db reset. 2 credits spent. Now I have 98 credits.
6) modified Image List posted with "next first" image getting only like from me - I spend 2 credits, I gain 99 credits instead of 1.
Altogether I have 98+99 = 197 credits.

// cycle continues until there is no image left in the loaded image list.


Full python code can be found and analysed here - https://pastebin.com/vV9j1nSD

Conclusions and final thoughts

I highly doubt that this is what killed Gramblr. The system by concept was having flaws, I did not manage to explore and analyse "gaining followers" part, but I had a suspicion that somewhat users were automatically added as followers. My IG account suddenly was following more than 3k users without my knowledge. That means the system is malware in my opinion. 
The amount of unique users (98`976) where most of them most likely were bots and commercial users. In the beginning I was choosing one of my own IG images to find how many likes that IG picture could get. It gained close to 9k likes which is 10% of users. At that time I noticed the system was used by ~30k users, which would say ~30% of the system users could be the actual amount of likes possible to get.
The IG itself is also worth to mention. It has its artificial intelligence for sure. IG top picks and exploration I suspect originally was built on hashtags. But because of the bots and the systems which artificially increase the number of likes, some intellectual measures IG had to implement. Time as a crucial parameter can be measured. If the image in the IG got close to 500 likes within 5 seconds it has to be suspicious, spiking. Rate of change is a perfect indicator to flag unusual and suspicious activities. That was another flaw what Gramblr had. If I had 500 credits and I use them at once to boost my IG image, it may be flagged. Back in 2016 when I was experimenting, I noticed that my images with hashtag showed up in the search but over time they disappeared, suggesting me to think the IG had their preventive measures improved and giving no benefit of low IG users floating on the IG surface among most popular IG influencers. Thus by using Grambrl this did more harm than good to IG users. This is also a reason why I implement helper function self.pickIGfromFile(fname-  to have a lot of random IG images and giving those likes with a random time interval thus reducing the rate of change of getting likes. Less suspicious for IG.