How I Forensically Analyzed a macOS App That Ripped Off My Open-Source Project

by Konstantin Zaremski

I’m Konstantin Zaremski, the author of Apple Notes Exporter, a free and open-source macOS app for bulk exporting Apple Notes. I’ve been building and maintaining it since 2023, and in October 2025 I released version 1.0, a ground-up rewrite in Swift that directly queries the Apple Notes database for performance.

In early 2026, a fan of my project reached out to let me know that someone was selling what appeared to be a repackaged version of my app under the name “Notes Exporter Pro” on 1dot.ai. They’d slapped a $9.99 “Lifetime License” on it via PayPal, added a license key activation system, and were marketing it as their own product across at least six subreddits.

My project was MIT-licensed at the time. The MIT License lets anyone use, modify, and even sell my code, but there’s one non-negotiable requirement: you must include my copyright notice and the license text. They didn’t. They stripped every trace of my name, my copyright, and the MIT license from the binary. That means they had no valid license to distribute my code at all.

Here’s exactly how I confirmed it – from basic string extraction all the way to Ghidra decompilation.


Tools Used

I used five tools across the investigation. The first four come pre-installed on macOS:

  • strings: extracts printable strings from binary files
  • codesign: inspects code signatures
  • otool: object file display tool (Mach-O metadata, class dumps)
  • xcrun swift-demangle: demangles Swift symbol names
  • Ghidra (free, from NSA): reverse engineering framework with a decompiler – this is what produced the strongest evidence

Step 1: Inspect the App Bundle

A .app on macOS is just a directory. I started by downloading the DMG, hashing it, mounting it, and copying the app out for analysis.

curl -L -o ~/Desktop/Notes.Exporter.Pro.dmg \
  "https://github.com/1dotaidev/apple-notes-exporter-app-releases/releases/download/macapp/Notes.Exporter.Pro.dmg"
shasum -a 256 ~/Desktop/Notes.Exporter.Pro.dmg
hdiutil attach ~/Desktop/Notes.Exporter.Pro.dmg
cp -R "/Volumes/Notes Exporter Pro/Notes Exporter Pro.app" ~/Desktop/
hdiutil detach "/Volumes/Notes Exporter Pro"

Downloading, hashing, mounting, and copying the app bundle

Then I looked at what was inside:

ls -la "Notes Exporter Pro.app/Contents/"

Key directories to check:

  • Info.plist: app metadata, bundle ID, developer info, version
  • MacOS/: the actual executable binary
  • Frameworks/: bundled libraries and dependencies
  • Resources/: assets, localization files
  • _CodeSignature/: code signing data

Step 2: Read the Info.plist

cat "Notes Exporter Pro.app/Contents/Info.plist"

This gave me:

Key Value
CFBundleIdentifier onedotai.macosapp.notesexporter
CFBundleDisplayName Notes Exporter Pro
CFBundleShortVersionString 1.6
CFBundleGetInfoString Notes Exporter Pro 1.6, Copyright (c) 2026 Notes Exporter. All rights reserved.
SUFeedURL Sparkle update feed on GitHub (1dotaidev/apple-notes-exporter-app-releases)

Note the copyright string: “Copyright (c) 2026 Notes Exporter. All rights reserved.” – no mention of me or my license.

App bundle contents and Info.plist

Step 3: Check the Code Signature

codesign -dv --verbose=4 "Notes Exporter Pro.app" 2>&1

Result:

Authority=Developer ID Application: Ramachandran Arumugam Velmurugan (U84Y35UPTV)
Authority=Developer ID Certification Authority
Authority=Apple Root CA
TeamIdentifier=U84Y35UPTV
Timestamp=Jan 22, 2026 at 9:50:36 PM
Notarization Ticket=stapled

Now I had a real name tied to an Apple Developer account. The binary was signed by Ramachandran Arumugam Velmurugan with Team ID U84Y35UPTV, and Apple had notarized it.

codesign output showing developer identity and notarization

Step 4: Extract and Search Strings

The strings command extracts all readable text from a compiled binary: class names, string literals, SQL queries, error messages, URLs, everything.

strings "Notes Exporter Pro.app/Contents/MacOS/Notes Exporter Pro" > nep_strings.txt
wc -l nep_strings.txt  # 12,214 lines

Searching for my class names

grep -i "AppleNotesDatabaseParser\|TableParser\|NoteStoreProto" nep_strings.txt

AppleNotesDatabaseParser – my custom class for parsing the Apple Notes SQLite database (source, line 145) – was right there in the binary, as a Swift type metadata symbol: _TtC18Notes_Exporter_Pro24AppleNotesDatabaseParser. So was TableParser (source, line 72), my custom class for parsing embedded tables in notes.

These aren’t generic names. I wrote them. Finding them in someone else’s binary is a smoking gun.

Searching for my name or attribution

grep -i "zaremski\|konstantin\|kzaremski\|MIT\|GPL\|GNU" nep_strings.txt

Zero results. No mention of my name, my copyright, or any license text anywhere in the binary.

Class names found, attribution search returns nothing

Searching for my SQL queries

My app queries the Apple Notes SQLite database with specific query patterns. I searched for those:

grep -i "ZMEDIA\|ZIDENTIFIER\|ZFALLBACK\|Z_PRIMARYKEY\|ZICCLOUDSYNCINGOBJECT" nep_strings.txt

The same SQL queries from my AppleNotesDatabaseParser.swift were present:

  • Attachment query: SELECT att.ZMEDIA, att.Z_PK, att.ZIDENTIFIER, att.ZTYPEUTI, att.ZFILENAME ... (source, line 966)
  • Fallback image: SELECT ZFALLBACKIMAGEGENERATION FROM ZICCLOUDSYNCINGOBJECT WHERE ZIDENTIFIER = ? ... (source, line 1229)
  • Fallback PDF: SELECT ZFALLBACKPDFGENERATION FROM ZICCLOUDSYNCINGOBJECT WHERE ZIDENTIFIER = ? ... (source, line 1349)
  • Media filename: SELECT ZFILENAME FROM ZICCLOUDSYNCINGOBJECT WHERE Z_PK = ?; (source, line 1032)
  • Column detection: PRAGMA table_info(ZICCLOUDSYNCINGOBJECT) (source, line 214)

While the table and column names are Apple’s internal schema (any Notes app would reference them), the specific combination of queries, the Z_PRIMARYKEY subquery pattern for entity type lookups, and the ZMARKEDFORDELETION defensive NULL checks are specific implementation choices from my code.

SQL queries from my source code found in their binary

Searching for my debug strings

Error messages and debug logging are often left unchanged because they don’t face the user:

grep -i "ZMEDIA=NULL\|No media object\|No ZFILENAME\|Trying to find attachment" nep_strings.txt

Found exact matches for strings from my AppleNotesDatabaseParser.swift:

String in Their Binary My Source
Attachment %s ZMEDIA=NULL, type=%s, filename=%s Line 990
Trying to find attachment using ZFILENAME: %s Line 1005
Found attachment %s with ZMEDIA=%lld Line 1014
Failed to prepare attachment query Line 1022
No ZFILENAME found for media object %ld Line 1054
No media object row found with Z_PK=%ld Line 1057
Failed to prepare ZMEDIA query: %s Line 1061

The format strings use C-style %s/%lld (os_log formatting) vs my Swift string interpolation in source, but the message text is identical.

Debug log strings from my code found in their binary

Searching for their infrastructure

grep -i "license\|paypal\|payment\|cloudlogin\|1dot" nep_strings.txt

This revealed everything they’d bolted on:

  • License validation server: cloudloginsystem.pixelperfectdgm.workers.dev/api/verify-license
  • PayPal payment: https://www.paypal.com/ncp/payment/GF5RYTHZMDX6C
  • Support email: support@1dot.ai
  • Sparkle auto-update feed on GitHub

License infrastructure, PayPal, and support email in strings

Step 5: Inspect Linked Frameworks

ls "Notes Exporter Pro.app/Contents/Frameworks/"

They were bundling SwiftProtobuf.framework – the same protobuf dependency my project uses to parse Apple Notes’ protobuf-encoded note content. They also bundled Sparkle.framework (auto-updater, not in my project).

otool -L "Notes Exporter Pro.app/Contents/MacOS/Notes Exporter Pro"

The linked system libraries matched my project’s dependency chain: SQLite + Compression + SwiftProtobuf for database parsing, SwiftUI for the interface.

Step 6: Extract ObjC/Swift Class Metadata with otool

This is where it got more interesting. otool -oV dumps Objective-C and Swift runtime metadata, including class names and instance variables (ivars), even when method names are stripped.

otool -oV -arch arm64 "Notes Exporter Pro.app/Contents/MacOS/Notes Exporter Pro" > otool_dump.txt

Instance variable comparison

Their AppleNotesDatabaseParser has exactly 3 instance variables, in the same order, with the same names and sizes as mine (source, lines 146-148):

# Their Binary (otool) My Source
1 db (8 bytes) private var db: OpaquePointer?
2 dbPath (16 bytes) private let dbPath: String
3 version (1 byte) private var version: NotesVersion = .unknown

otool dump showing AppleNotesDatabaseParser ivars: db, dbPath, version

Their TableParser has a single ivar db – exact match to mine (source, line 73).

otool dump showing TableParser with single ivar: db

Full class list

The otool dump revealed all Swift classes in the binary. This showed clearly what they took and what they built themselves:

From my project (copied):

  • AppleNotesDatabaseParser – core database parser
  • TableParser – embedded table parsing
  • AttributeRun._StorageClass – protobuf generated types

Their own additions:

  • NotesExporterViewModel (43 ivars – substantially different from my ExportViewModel)
  • ExportManager, DatabaseNotesManager, NotesManager – their service layer
  • LicenseService – paywall
  • StoreManager – StoreKit integration
  • UpdaterManager – Sparkle auto-update
  • ZipExporter, NavigationHandler, ProgressTracker

Their main view model alone had 43 instance variables including auto-export features, iCloud warnings, and StoreKit state that don’t exist anywhere in my code. This confirmed: they took the core database parsing engine and built a new app shell around it.

Step 7: Ghidra Decompilation

This is where I got the strongest evidence. Ghidra is a free reverse engineering framework from the NSA that can decompile compiled binary code back into readable pseudocode.

I installed Ghidra via Homebrew and ran the headless analyzer:

brew install ghidra

# Extract the arm64 slice
lipo -thin arm64 "Notes Exporter Pro" -output nep_arm64

# Import and analyze
analyzeHeadless /tmp/ghidra_project NEP_Analysis -import nep_arm64

Ghidra headless analyzer running against the extracted arm64 binary

Then I wrote a custom Ghidra plugin to decompile all 4,188 functions and filter for ones that reference strings from my source code. 15 functions matched. Due to symbol stripping, function names appeared as FUN_XXXXXXXX, but the decompiled pseudocode revealed complete control flow, string constants, and API call sequences.

Attachment resolution: step-by-step control flow match

This is the key comparison. My fetchAttachmentData function (source, lines 963-1025) follows a specific 12-step sequence. Their decompiled function (FUN_100003e8c) follows the exact same sequence:

Step Their Function (Ghidra pseudocode) My Function (Swift source)
1 Prepare SQL with same attachment query Prepare SQL with same query
2 Bind attachment identifier as text Bind attachment identifier
3 Step through results Step through results
4 Read columns 3, 4, 5 (ZTYPEUTI, ZFILENAME, account) Read same columns
5 Check if column 0 (ZMEDIA) is SQLITE_NULL (type == 5) Check for SQLITE_NULL
6 If NULL: log "Attachment %s ZMEDIA=NULL..." If NULL: log same message
7 Check UTI for com.apple.paper, call fallback image Check UTI, call fetchFallbackImage
8 Check for PDF type, call fallback PDF Call fetchFallbackPDF
9 Try ZFILENAME: log "Trying to find attachment..." Log same, call findExternalAttachment
10 If ZMEDIA not null: log "Found attachment...ZMEDIA=%lld" Log same, call fetchMediaData
11 If no rows: log "No attachment found..." Log same message
12 If prepare fails: log "Failed to prepare..." Log same message

Every branch, every error path, every log message at every decision point – identical.

Media data fetch: 1:1 copy

Their FUN_1000048bc decompiles to the exact same logic as my fetchMediaData (source, lines 1030-1065):

  1. Same SQL: SELECT ZFILENAME FROM ZICCLOUDSYNCINGOBJECT WHERE Z_PK = ?;
  2. Same bind, same step, same column read
  3. Same log: "Media object %ld: filename=%s"
  4. Same three error paths with the same three messages

Fallback image/PDF resolution

Their fallback functions use the same SQL queries for ZFALLBACKIMAGEGENERATION (source, line 1229) and ZFALLBACKPDFGENERATION (source, line 1349), the same NSHomeDirectory() call, and construct the same candidate paths: group container + /Accounts/ + account ID + /FallbackImages/ (source, lines 1247-1248).

Column detection

Their decompiled code shows a function that calls PRAGMA table_info() and checks for columns named ZOWNER, ZACCOUNT, ZTITLE2, ZTITLE1, ZTITLE with fallbacks – the same approach as my getTableColumns function (source, lines 212-230).

The hex-encoded strings in the decompiled output (0x52454e574f5a = ZOWNER, 0x544e554f4343415a = ZACCOUNT) confirmed this.

One notable difference: their fetchAttachmentData query hardcodes note.ZACCOUNT as ZACCOUNT in a two-table join, which matches my code at the v1.0-3 tag. I have since replaced that with dynamic column detection and a three-table account join in commit 0626d94 (view). Their binary carries the older version, including a bug where the hardcoded column returns NULL on modern macOS versions. This suggests they forked my code at or around the v1.0-3 release.


What They Took vs. What They Built

The evidence paints a clear picture of what happened:

Taken from my project:

  • AppleNotesDatabaseParser – the entire SQLite query pipeline, attachment resolution, fallback handling, column detection
  • TableParser – embedded table parsing
  • The protobuf schema and Swift-generated types for decoding note content
  • All the trial-and-error knowledge encoded in the debug strings and edge-case handling

Built by them:

  • New UI/ViewModel layer (NotesExporterViewModel with 43 ivars)
  • License key paywall + PayPal payment
  • Auto-export/scheduled backup feature
  • Additional export formats (DOCX, EPUB, ZIP)
  • Sparkle auto-updater
  • AppleScript fallback export method
  • iCloud sync warning UI

They took the engine and built a new car body around it, then slapped a $9.99 price tag on it.


What I Investigated But Found NOT to Be Evidence

Honesty matters. I investigated several things that turned out not to be evidence of copying:

  • AppleScript delimiter patterns (:::, |||, ;;;, @@@, ~~~) – not in any version of my code; their own work
  • Description text (“export notes from Apple’s Notes.app to Markdown and HTML”) – similar but not verbatim from my README
  • UI strings (“Transform your Apple Notes…”, iCloud warning) – not in my source code
  • Some SQL differences (LEFT JOIN ... media ON att.ZMEDIA = media.Z_PK) – not in my code; their modifications
  • Class names DatabaseNotesManager, ExportManager, NavigationHandler, generatePDFWebKit – their own classes
  • NotesExporterViewModel (43 ivars) – substantially different from my ExportViewModel

I corrected these during the investigation to ensure every claim in this post is verified against actual source code.


The License Violation

At the time the code was taken, Apple Notes Exporter was licensed under the MIT License. The MIT License has one condition:

“The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.”

A grep for “zaremski”, “konstantin”, “MIT”, “GPL”, “license”, or “copyright” across their entire binary and app bundle returned zero results. They replaced my copyright with:

Copyright (c) 2026 Notes Exporter Pro. All rights reserved.

By stripping my copyright notice and license text, they violated the one condition of the MIT License, which means their right to distribute the code under those terms was never validly established.

I have since relicensed the project to GNU GPL v3, which requires derivative works to also be open source and provide corresponding source code. Going forward, anyone who takes code from my repository must keep it open source.


Their Marketing

The app was promoted by Reddit user /u/Ok-Organization5910 across at least six subreddits (r/macapps, r/AppleNotesGang, r/SideProject, r/MVPLaunch, r/founder, r/1dotaimacosapps) with claims like:

  • “After spending 6 hours trying to manually export my notes, I decided to build a solution”
  • “I spent the last few months building Apple Notes Exporter Pro”

The core of the “solution” they “built” is my database parser that I spent years developing.


Preserving Evidence

Before doing anything public, I preserved everything:

  1. Kept a copy of their .app bundle untouched, with SHA-256 hash recorded
  2. Saved the full strings dump (12,214 lines)
  3. Saved the full Ghidra decompilation output of matching functions
  4. Web-archived their website, payment pages, and GitHub releases repo
  5. Screenshotted Reddit posts with timestamps
  6. Archived my own git log as a timeline reference:
git log --all --format='%H %ai %an %s' > git-history-evidence.txt

What You Can Do If This Happens to You

  1. File a DMCA takedown on any platform hosting their content (GitHub, Reddit, web hosts)
  2. Report to Apple if they’re distributing via a Developer ID – include their Team ID
  3. Contact their payment processor (PayPal, Stripe, etc.) to report them profiting from infringing software
  4. Public disclosure with verified evidence
  5. Email them directly demanding compliance or removal

A Note on License Choice

If you’re publishing open-source software and don’t want someone to repackage it as a closed-source paid product, think carefully about your license:

  • MIT/BSD: Anyone can use, modify, sell. Only requirement is attribution. If they strip your name, they’re in violation, but enforcement is on you.
  • GPL v3: Derivative works must also be open source with source code provided. A paywall on a closed binary is inherently non-compliant.
  • AGPL: Same as GPL but also covers network/SaaS use.
  • Dual license: Free for open-source, paid license for commercial use.

I learned this the hard way. Going forward, my project is GPL v3.


Summary of Evidence

Evidence Type Tool Used Finding Source Reference
Class names strings AppleNotesDatabaseParser, TableParser in binary type metadata L145, L72
Instance variables otool -oV Identical ivar layout (names, order, sizes) for both classes L146-148, L73
SQL queries strings Same attachment, fallback, and column detection queries L966, L1229, L1349
Debug strings strings 7 identical error/log messages with matching format strings L990, L1005, L1014, L1054, L1057
Control flow Ghidra 12-step attachment resolution pipeline is structurally identical L963-1025
Media fetch Ghidra fetchMediaData equivalent is a 1:1 copy L1030-1065
Fallback handling Ghidra Same SQL, same NSHomeDirectory() path construction L1226-1270, L1247-1248
Column detection Ghidra Same PRAGMA table_info() approach, simpler (pre-v1.1) version L212-230
Attribution strings Zero matches for author name, copyright, or license text

Forensic analysis performed on February 24, 2026.

Independent verification: The DMG analyzed was downloaded from https://github.com/1dotaidev/apple-notes-exporter-app-releases/releases/download/macapp/Notes.Exporter.Pro.dmg. Anyone can download it and reproduce these findings using the tools described above.

Notes.Exporter.Pro.dmg
SHA256   49fb5cb19cda630c601f86b3453cdf4bcab837b8f217a523761da3cad18e6d44
Notes Exporter Pro (binary)
SHA256   149bada63a27a4ef1e1a1fefc6c0f219ff78f0157be947539d968cc11c636d88

The original project: github.com/kzaremski/apple-notes-exporter

I’ll update this post as things develop.

Copyright © 2020-2026 Konstantin Zaremski — All rights reserved. Styles by Neumorphism UI Bootstrap.