Picture created with Image Creator from Microsoft Designer

This post is about a handy tool I developed called seads, which helps detecting malvertising on search engines. Here is the GitHub repository, if you have ideas or feedback related to this project, feel free to reach out!

The idea for seads came from the growing number of cases of malicious ads displayed in search engine results and my need for a tool to detect them automatically. Recently, various incidents shows how malvertising has been used for redirecting users to phishing websites and for malware distribution.

Malvertising is a particularly sneaky technique, as it exploits the trust users place in the top search results of by their favorite search engines. A suggestion: to increase your online safety, consider installing tools like uBlock Origin or other reputable ad-blockers. These tools can help mitigate the risks associated with malvertising by blocking potentially harmful ads :)

Introducing seads

seads is written in GoLang and it uses headless browser pages to navigate to popular search engines, where it retrieves a list of ads displayed in response to user-submitted queries. At the time of this blog post, seads is able to detect ads on Google, Bing, DuckDuckGo and Yahoo.

In addition, seads can notify about detected ads via email, Slack or Telegram, it can provide a screenshot to support the evidence of malvertising campaigns, and can be executed using Docker.

Getting started

You can download the binary from the releases section on Github or compile it from source using the following Go installation command:

go install

Alternatively, you can use Docker to run seads without affecting your local setup:

docker build -t seads .
docker run -v "$(pwd)":/mnt seads -h

Once installed, you need to create a config file, here is an example that you can adjust to your needs:

  - query: "apple"
    expected-domains: [,]

  - query: "as roma"
    expected-domains: []

The config file above will query “ipad” and “as roma” in search engines, the field “expected-domains” is used to specify domains we are expecting to appear in the ads of search engines while searching for the specified keywords. Domains in “expected-domains” will still appear in the output of seads, but won’t be sent in the notification.

Finally, seads can be executed with the following flags:

  -config string (REQUIRED)
    	path to config file (default "config.yaml").
  -concurrency int
    	number of concurrent headless browsers (default 4).
    	print clear links in output (links will remain defanged in notifications).
    	notify if unexpected domains are found.
  -screenshot string
    	path to store screenshots (if empty, the screenshot feature will be disabled).

In order to receive notifications via email, Slack, or Telegram, you need to configure the config.yaml file with your credentials and preferences:

  host: SMTP server hostname or IP address (string)
  port: SMTP server port, common ones are 25, 465, 587 or 2525 (int)
  username: SMTP server username (string)
  password: SMTP server password (string)
  from: E-mail address that the mail are sent from (string)
  recipients: List of recipient e-mails (list of strings)

  token: API Bot token (string)
  channels: Channels to send messages to in Cxxxxxxxxxx format(list of strings)

  token: API Bot token (string)
  chatid: Chat IDs or Channel names (list of strings)

Testing it

Here is a real-life example of detecting ads with seads using the config file pasted above. The tool is executed with:

seads -config config.yaml -screenshot scr -notify

and it produces the following output:

seads output

Output recorded with asciinema

If ads were found, a new folder src will be created, containing screenshots like the one below:

seads screenshot

One of the screenshot produced by seads

A screenshot will be taken only if at least one ad is detected, ensuring that screenshots are relevant. Screenshots are named following this format: searchengine-query-timestamp.png. For example, the screenshot above will be named yahoo-apple-1710278888941790000.png.

And here is the notification which will be sent to the specified channels:

Here are the "unexpected domains" found during the last execution of seads:

Message creation date: 2024-03-12 22:28:14

* Search engine: Yahoo
 Search term: apple
 Domain: reparaturpc[.]ch
 Full link: www[.]https://reparaturpc[.]ch/de/?msclkid=75c3ce8f8942156ac179ab7f41a03704

* Search engine: Yahoo
 Search term: apple
 Domain: fust[.]ch
 Full link: https://www[.]fust[.]ch/de/marken/apple[.]html?&msclkid=a836011a07061ba4052864eacfe7d0fd&utm_source=bing&utm_medium=cpc&utm_campaign=Bing%20-%20NBrand%20-%20S%20-%20D%20-%20MM%20PC%20Marke%20Apple&utm_term=apple&utm_content=1_Apple%3D2_undefined%C2%A63_Nbrand&gclid=a836011a07061ba4052864eacfe7d0fd&gclsrc=3p[.]ds

* Search engine: Yahoo
 Search term: apple
 Domain: jobs[.]ch
 Full link: https://www[.]jobs[.]ch/en/vacancies/?term=apple&utm_source=bing&utm_medium=search&utm_campaign=wb:jobs|tg:b2c|cn:ww|lg:en|ct:search,nonbrand,company|cd:company|mg:job-application|pd:y|tt:cpc|gt:keyword,nonbrand,company|gd:company&msclkid=17ac4d7d0b0616628f40288dc3e79a46&utm_term=apple&utm_content=gt%3Akeyword,nonbrand,company%7Cgd%3Acompany

* Search engine: Yahoo
 Search term: apple
 Domain: amazon[.]com
 Full link: https://www[.]amazon[.]com/s?k=applwe&adgrpid=1344703557775981&hvadid=84044278562817&hvbmt=be&hvdev=c&hvlocphy=3322&hvnetw=o&hvqmt=e&hvtargid=kwd-84044521042995%3Aloc-175&hydadcr=29387_14610683&tag=mh0b-20&ref=pd_sl_7xha1yy51_e

This message was automatically sent by seads (

Automating executions

It is possible to further leverage the notification feature by automating exectution of seads. In Linux, we can use cron to do it, for example, to run seads daily at 9:00 AM, you can add the following entry to your crontab:

0 9 * * * /path/to/seads -config /path/to/config.yaml -screenshot /path/to/screenshots -notify

For Windows and macOS users, similar scheduling options are available using Task Scheduler and launchd, respectively.

By setting up a well-configured file, we can automate the execution of seads and receive notifications whenever it detects an ad from an unexpected domain. For instance, if we’re interested in potential malvertising targeting our company, we simply list our company’s domains in the expected-domains field. This way, seads will continuously monitor for any ads that don’t match our specified domains, alerting us immediately if it finds any.

Libraries used

For automating the headless browser, I relied on Rod, a powerful library that works with the DevTools Protocol. Rod is very popular for tasks like web scraping and automation, and it is capable of mimicking manual interactions with the browser. You can find additional information about Rod on its GitHub repository and documentation site.

As for notifications, I used the Shoutrrr Notification library, which provides seamless integration for notifications in Go applications. You can find more details about Shoutrrr on its GitHub repository and documentation site.


Due to the nature of search engine ads work, a single search might not reveal all the ads. However, by using the concurrency flag to increase the number of headless browsers working simultaneously - although it may slightly slow down detection - it will ensures a more comprehensive collection of ads.

It’s also worth mentioning that notifications sent have character limits, so messages exceeding this limit won’t be sent. I have encounter this issue while testing seads, as many search engine ad links may be quite long due to tracking elements. To address this, one approach could be setting up separate config files for different campaigns and running seads separately for each.

Closing remarks

In conclusion, seads can detect malvertising campaigns in Google, Bing, DuckDuckGo and Yahoo ads by relying on concurrent headless browser pages, which will navigate to the search engines, and display the ads which were found. The tool can be automatically executed with cron, Windows Scheduler or launchd, and it can take screenwhots of the ads and send notifications via email, Slack or Telegram.