Today we will take a deep dive into investigation of eleven members of MS-13 gang that were arrested at the beginning of this month for Sex Trafficking of a Minor, VICAR assault (Violent Crime in Aid of Racketeering Activity through assault with a dangerous weapon) and also drug dealing and possession of child pornography.

To show some OSINT for everyone, this post is public.

Last article about Intelligence gathering on critical infrastructure in US and Russian Federation is accessible below (for paid subscribers).

Season 2 is for paid subscribers, for the rest you just need to sign up via email.

Offensive OSINT s02e03 - Intelligence gathering on Internet facing critical infrastructure in United States of America and Russian Federation.
In this episode we will make an analysis of exposed Industrial Control System(ICS) devices in Russia and United States from military perspective. What is thebest way to spy on foreign exposed critical infrastructure? How to do espionageof strategic places? What exploit will be the most profitable…

We will follow steps that Law Enforcement Agency took to gather evidence and connect individuals in popular social media platform - Facebook. As promised at the end of last season, I will show how to create a network of mutual friends, based on the mentioned Mara Salvatrucha members.

Introduction

This is next part of human trafficking investigation on the blog. First research was an introduction to the online scene of classified ads websites, which can help criminals by providing easy access to advertise sexual services.

Offensive OSINT s01e08 - Human trafficking investigation part 1
Welcome in last episode of first season of Offensive OSINT. Today’s topic isvery important from ethical perspective and super interesting from technicalpoint of view. This is the field where Open Source Intelligence is used themost. We will go through most common methods of human trafficking onli…

Big news is that next website, after Backpage, has been seized on counts of promoting prostitution and money laundering, among others. CityXGuide has been replaced by known LEA seized banner. I bet Bedpage will be next and fall down next year.

In part 2 I presented real world scenarios where monitoring such websites provided necessary intelligence to take action and make arrests. Moreover, article presents a little bit of inside view how this process might look like by showing proof of concept solution to monitor Bedpage to catch out potentially "Bad Ads" that could ring a bell in terms of prostitution or sexual trafficking.

For paid subscribers only.

Offensive OSINT - s02e02 - Human trafficking investigation part 2. Monitoring Bedpage.
In this episode we will focus on widely known website which offers escortservices around the world and how it allegedly helps human traffickers.Moreover, I will present application that was mentioned in part 1, i.e. solutionthat Thorn[https://www.thorn.org/about-our-fight-against-sexual-exploitation-of-children/…

So this time we are diving into network of friends of arrested MS-13 members. It's very important step in any SOCMINT (Social Media Intelligence) investigation and can give many new leads by tracking associates of the subjects. The technique does not depend on the target you investigate, it does not matter if you track drug dealers, missing persons or organized crime. Visualizing mutual friends is must do.

Indictment

On 5th of August, Department of Justice in Eastern District of Virginia announced, on press release, arrest of eleven members of MS-13 gang on counts of sexual exploitation and physical abuse of a minor.

MS-13 Members and Associates Arrested for Sex Trafficking a Minor
Eleven members or close associates of the MS-13 gang were arrested this week relating to the sexual exploitation and physical abuse of a minor in northern Virginia and Maryland.

The Mara Salvatrucha gang is a criminal organization originally coming from Los Angeles, California and was set up in the 70'. The gang consists mostly of Salvadorian immigrants, operate in Central America and counts up to 70k members according MS-13 in Americas report made by InSight Crime. They are known from making profits out of human trafficking

Human trafficking is another source of revenue for some gangs. Victims—typically women and children—are often forced, coerced, or led with fraudulent pretense into prostitution and forced labor.16 The Bloods, MS-13, Sureños, and Somali gangs have been reportedly involved in human trafficking, according to multiple law enforcement and NGIC reporting  - https://www.fbi.gov/stats-services/publications/2011-national-gang-threat-assessment

In addition, they are defined by their cruelty and violence

Violence is at the heart of the MS13 and is what has made it a target of law enforcement in the United States, Central America and beyond. - https://www.justice.gov/eoir/page/file/1043576/download (Key findings - number 5.

Mixing these two things gives power for the gang but nightmare for the victims, what is presented in details in indictment.

Story starts on 4th of September, 2018 when Prince William County Police recovered the victim, known as MINOR-3 (16 y.o.), during stealing at Target shop. She was not alone, and her friend MINOR-2 (13 y.o.), was recovered by FBI Child Exploitation and Human Trafficking Child Force on 11th of October in Mount Rainier, Maryland.

You can read full indictment form link below

https://www.justice.gov/usao-edva/press-release/file/1301606/download

Based on theirs testimonies and multiple interviews, law enforcement was able to target specific people and places in time when they were missing. They were beaten up with a metal baseball bat, accused of stealing, sexually abused and trafficked. However, we won't focus on recreating events but rather take a closer look on the criminals.

The reason I share screenshots from indictment and not the names from press release is very important. Indictment describes multiple monikers used by actors which help to find social media account and confirm affiliation.

From OSINT perspective, indictment shows they all have a Facebook account(s) and it was also their main communication channel. MINORS recognized each of them based on photos from FB and then LE obtained enough evidences of criminal activity to make arrest. With a warrant, Facebook shared all private messages, photos, stories and location, so investigators had to recreate timeline based on the obtained evidence and interviews with MINORS. That more or less sums up indictment but I recommend you to read the whole story. There are also couple pure OSINT tricks to connect some dots which I will present later.

The important background character in this story is Facebook - as a platform, used by criminals on their own name, and also as an organization - which gave all necessary data to LE. Without this information, making charges would be more challenging.

Private messages were most helpful to make connections and relationships between known and not known members of the gang. I decided to take an OSINT approach and recreate their mutual friends and network of associations.

Research

First step was to find any person mentioned in indictment. I had to look for each person and his/her monikers with paying attention to the details like - "MOISES ZELAYA" and "MOIZES ZELAYA". I only discovered first account that belongs to MEMBER1 and started looking around into his friends trying to find more MEMBERS. Then after quick research, I discovered 8 people that are connected to each other, 1 that set his friends to private and 2 outside of the network. Some of them have more than 1000 friends, so to make network and visualization successful I had to scrape them all.

I will call each person of arrested MS-13 by MEMBER to not interfere with ongoing investigation.

Facebook Scraping

There are no good tools for scraping Facebook and API can't be used to get friends of a person. I tried Ultimate-Facebook-Scraper but it does not work for me. Maybe it was not updated or there is something wrong with my Facebook GUI. Anyway, I had to figure out a way to make a database of all friends of these 8 people.

So I fired up Burp Suite as a proxy, scrolled through friends (to the bottom) and retrieve every request to "api/graphql" which contains needed data - name, profile picture and link to the profile.

Burp Suite allows to save many requests to one big file which next can be parsed with below Python script

import json

arr = {'member1':[]}
end_json = {"name":"","info":"","profile_pic":""}

with open ('member1.txt', 'r') as f:
    splitted = f.read().split("\n\n")
    for line in splitted:
        if line.startswith('{"data":'):
            next = line.split("]]><")
            try:
                a = json.loads(next[0])
                for i in a['data']['node']['pageItems']['edges']:
                    arr['MEMBER1'].append({"name":i['node']['title']['text'],"link":i['node']['url'],"profile_pic":i['node']['image']['uri']})
            except Exception as e:
                pass

with open('member1.json', 'w') as f:
    json.dump(arr, f, indent=4)

to make a JSON file of friends of each MEMBER in following format

{
    "MEMBER1": [
        {
            "name": "Friend of MEMBER1",
            "link": "https://www.facebook.com/[REDACTED]",
            "profile_pic": "https://scontent-frx5-1.xx.fbcdn.net/[REDACTED]"
        },
        {
            "name": "Friend of MEMBER1",
            "link": "https://www.facebook.com/[REDACTED]",
            "profile_pic": "https://scontent-frt3-2.xx.fbcdn.net[REDACTED]"
        },
        {
            "name": "Friend of MEMBER1",
            "link": "https://www.facebook.com/[REDACTED]",
            "profile_pic": "https://scontent-frt3-1.xx.fbcdn.net/[REDACTED]"
        },

I repeated this for each MEMBER. Some of them have multiple accounts and indictment does not point which one was investigated by LE, so I took ones with the biggest amount of friends, hoping it covers also friends from other accounts.

Multiple accounts used by LUIS ALBERTO GONZALES a/k/a LUIS FIGO

If I would investigate more people, I will have to make this process fully automated. I was thinking to set up selenium to go through friends and proxy in background which would save and parse the 'graphql' responses to extract friends.

Comparing friends

Having separate files for each MEMBER with theirs friends, now I had to compare it and leave only ones that exist in both lists. As a unique key I took link to the profile and run above script to get only repetitive entries.

import json

arr1 = []
arr2 = []

with open('member1.json','r') as f:
    member = json.loads(f.read())
    for i in member['MEMBER1']:
        arr1.append(i['link'])
    f.close()
with open('member2.json','r') as f:
    member = json.loads(f.read())
    for i in member['MEMBER2']:
        arr2.append(i['link'])
    f.close()
mutual_friends = set(arr1).intersection(arr2)

It makes a two lists of MEMBERS' friends and then compare them by creating sets and running intersection on both of them. Now, variable 'mutual_friends" contain links to the profiles that exists for both MEMBERS i.e. mutual friends.

After that I had to think about proper input to make a visualization. I already prepared one for research about offshore organizations

Offensive OSINT s01e06 - Analysis of offshore organizations of Polish Steamship Company.
This time, we are diving into researching offshore organizations from bunch ofleaks like Panama Papers, Bahamas Leaks or Paradise Papers. In this episode I will present: * Structure of offshore organizations of Polish Steamship Company (POLSTEAM) * Methods to research offshore leaks * Network…

and the format is as follow

{
    "nodes": [
        {
            "name": "MEMBER1",
            "group": 1,
            "id": 1,
            "photo": "https://scontent-frx5-1.xx.fbcdn.net/[REDACTED]",
            "size": 120,
            "info": "https://www.facebook.com/[REDACTED]"
        },
        {
            "name": "MEMBER2",
            "group": 2,
            "id": 2,
            "photo": "https://scontent-frx5-1.xx.fbcdn.net/[REDACTED]",
            "size": 55,
            "info": "https://www.facebook.com/[REDACTED]"
        }
        "links": [
        {
            "source": 1,
            "target": 2,
            "value": 2
        },
        {
            "source": 2,
            "target": 1,
            "value": 2
        }
}

It will show connection between MEMBER1 and MEMBER2 and also from MEMBER2 to MEMBER1. It is indicated by 'source' and 'target' keys based on the node id.

Creating visualization

We have already set of links of mutual friends between two MEMBERS, so now we have to add each of them to the graph as nodes and include previously collected information. (profile pic and name)

mutual_friends_json = {"name":"","group":1,"id":0,"photo":"","size":55,"info":""}

only_mutual_friends = []

with open ('graph.json','rb') as graph:
    json_graph = json.loads(graph.read())
    id = json_graph['nodes'][-1]['id']
    with open('member1.json','r') as f:
        member1 = json.loads(f.read())
        for mutual_friend in mutual_friends:
            id = id + 1
            for i in member1['member1']:
                if mutual_friend == i['info']:
                    mutual_friends_json['name'] = i['name']
                    mutual_friends_json['photo'] = i['profile_pic']
                    mutual_friends_json['info'] = i['link']
                    mutual_friends_json['id'] = id
                    only_mutual_friends.append(mutual_friends_json)
                    mutual_friends_json = {"name": "", "group": 1, "id": 0, "photo": "", "size": 55, "info": ""}

It just checks for last ID in the graph, retrieve rest of information about friend based on the links from 'mutual_friends' and append each of them to the next list (mutual_friends_json) which will be used to save the results.

After that, we have two possibilities, the friend exists already in nodes or is the unique friend. I had to create another key "exists" only as a 'guard' to check if it already appeared in the database and mitigate possible repetitive entries.

exists = False

help = []

for j in only_mutual_friends:
    for i in json_graph['nodes']:
        if j['info'] == i['info']:
            json_graph['links'].append({'source':1,'target':i['id'], 'value':2})
            json_graph['links'].append({'source':2,'target':i['id'], 'value':2})
            j['exists'] = True
            j['id'] = i['id']
            help.append(j)
        else:
            try:
                if help == []:
                    j['exists'] = False
                    help.append(j)
                if help[-1]['info'] == j['info']:
                    pass
                else:
                    j['exists'] = False
                    help.append(j)
            except:
                pass

It iterates over 'mutual_friends' which is a list of dicts containing information about mutual friends and then add links to this particular person based on the retrieved ID from the graph. The 'else' condition is responsible for keeping previously non existent friends.

And the last stage is to add missing links and nodes for the friends that didn't show up in the database earlier.

for j in only_mutual_friends:
    if not j['exists']:
        json_graph['nodes'].append(j)
        json_graph['links'].append({'source': 1, 'target': j['id'], 'value': 2})
        json_graph['links'].append({'source': 2, 'target': j['id'], 'value': 2})
    

print(json.dumps(json_graph,indent=4))

To make it successful I had to do it 28 times (7+6+5+4+3+2+1) i.e. compare every person with other MEMBERS. I was running script and changing source id to the ids of currently checked MEMBERS, that's another manual part of the research that should be automated in case of broader investigation.

To make graph clearer, I customized nodes size based on the amount of connection to the person.

import json
from collections import Counter
help = []

with open('graph.json','r') as graph:
    g = json.loads(graph.read())
    for val in g['links']:
        help.append(val['target'])

counter = Counter(help)

for i in counter:
    for j in g['nodes']:
        if i == j['id']:
            j['size'] = counter[i] * 20

Script makes a list of the 'target' key, which indicates how many people are connected to the person and next counts occurrences. Next loop iterates over the counter and assign the number of occurrences to the 'size' field in the json file that will be used to create a network graph. I multiplied it by 20 to make the differences more visible.

Analyzing network

Before we dive into the relationship graph, I want to back to the case for a moment. Indictment also describes unidentified people (C.G. and K.O.) that were in contact with MEMBER(S) and MINOR-2.

Network of mutual friends can help to find such individuals and potential more connections to other gang members that use Facebook.

In addition, MINOR-2, if has more mutual friends with NELSON, should also appear in the network. It could be helpful for other members of the gang to track her down.

The graph shows every person in the network that has 2 or more MEMBERS as a friend on Facebook. Thanks to colorful links you can distinguish which friend is associated with each MEMBER and, in addition, thanks to different size of the nodes you can easily spot whom MEMBERS are connected to. Size of the nodes is established based on the numbers of 'target' (incoming) connections to the person rather than source connections (outgoing). It allows to present affiliated people outside of the MEMBERS network.

However, I still was not satisfied with the results and increased amount of target connections to 3. It will show closer associations in the network and present people that have 3 or more MEMBERS on their friends list. It requires another tampering with graph and below script helps with that

import json
from collections import Counter
help = []

with open('graph.json','r') as graph:
    g = json.loads(graph.read())
    for val in g['links']:
        help.append(val['target'])

counter = Counter(help)

help = []

for i in counter:
    if counter[i] > 2:
        help.append(i)

help2 = []
help3 = []


for j in g['links']:
    if j['target'] in help:
        help3.append(j)


for i in help3:
    for j in g['nodes']:
        if j['id'] == i['target'] or j['id'] == i['source']:
            if j in help2:
                pass
            else:
                help2.append(j)

g['nodes'] = help2
g['links'] = help3
print(json.dumps(g, indent=4))

It again creates a list of all occurrences of target connections and count it with help of Counter from Python collections. Next, scripts checks if there are more than 2 occurrences and append associated ID and to the next list. Based on this newly created list, it retrieves rest of the information from, main graph, about these IDs and put it in another list of dictionaries. Last iteration compares id of the node with existence as a target or source connection. At the end it prints new JSON with nodes and links associated with at least three people.

Now we have a clearer picture of all relationships. Graph shows that almost any of the MEMBERS are friends with each other and have multiple mutual friends. Having the names and profile picture make it easier to identify people but to show you some findings I highlighted couple friends by attaching a letter to them.

  • Friend D, F, G - another accounts of MOISES ORLANDO ZELAYA-VELIZ
  • Friend C, E - another accounts of LUIS ALBERTO GONZALES a/k/a LUIS FIGO
  • Friend B - another account of REINA ELIZABETH HERNANDEZ
  • Friend A - Another MEMBER which set his friends list to private - GILBERTO MORALES.
  • Friends H, I, J, K, L, M, N - have at least 4 connections to the MEMBERS

Further investigation

As previously mentioned, indictment describes couple OSINT evidences used by LE to connect the dots. Such example might be looking for common places in the Facebook photos of MEMBERS and associates.

They compared recovered photos from MINOR-2 to with K.O. public Facebook photos to establish a connection between them, being precise this same location.

Photos might reveal another indicators of relationship to the gang. Every photo they posted was carefully investigated from different angles.

Each photo shared in private messages will be compared to the publicly accessible photos of MEMBERS to establish further connections and recreate timeline and places they were during the crime.

What's more, pages their like are also important piece of the puzzle. They are fans of two football clubs and like pages related to theirs interests. These pages are quite unique and might be helpful to establish another common point.

Another indicator are even clothes. Recovered photo shows MINOR-2 in specific dress that REINA also wore. This connection has been established based on the public photo from REINA account.

I strongly believe, this is the dress. It matches description about different black and white patterns.

LE most probably made a network of mutual friends of the MEMBERS and investigated them in terms of affiliation to the gang or visited places during the crime. Keep in mind it was in second half of 2018.

Of course, investigation was also based on the evidence recovered from Facebook - images, videos, locations and private conversations, however from pure OSINT approach we can identify more potential members of the gang by tracking mutual friends of known MEMBERS.

Unfortunately, this is the point where our journey has to end due to real investigation work that is currently being done by professional law enforcement organizations. That was just one example how they might work and establish connections based on the network of mutual friends.

To quickly sum up the research:

  1. We gathered essential OSINT from indictment like monikers, events or basic associations.
  2. Manually take a quick journey over the accounts and discover as much as possible
  3. Scrap MEMBERS' Facebook friends with help of Burp Suite
  4. Compare MEMBERS' friends list with each other
  5. Prepare json input for the visualization
  6. Investigate relationship and potential new members based on the mutual connections
  7. Gather evidence (or lack thereof) of each suspected account

The research might look complex but it's due to data model I chose. I had to write multiple script to parse results and tamper with the graph. The research shows only one possible way to deal with this problem but you can keep all friends in Elasticsearch database and make a completely different graph, it's up to you.

Of course expect more investigations based on indictments in the future.

This time, money from subscribers goes to National Center for Missing & Exploited Children (NCMEC)

Conclusion

I was asked once how to prepare for missing persons CTF and I think this example can be perfect training exercise for people that want to practice their SOCMINT skills. It's kind of "reverse missing persons", you have all the evidence and must recreate timeline and crime scene. Indictments reveal many details that you might use in your own investigation and look for these specific indicators on social media, examples can be a dress or photo of a bathroom. Moreover, you will know the results of your research and be able to tell if your findings really matter.