By default, trial version of Charles Web Debugging Proxy ends in 30 minutes. But you can use it for more than 30 minutes, even you can use it for days without restarting the program. These are the simple steps to reproduce this issue.
  • Set your system time to a future date. For example, if today is 31st Jan 2023 then, change your system date to 2nd Feb 2023
  • Now Open Charles Proxy.
  • Once it is opened, reset your System time back to the actual date.
  • That's all! You can run your Charles Proxy until 2nd Feb 2023
Tested with Latest version of Charles Proxy: 4.6.3 (as of 30th Jan 2023).

This is for educational purpose only! Learn from the mistakes!

What causes this issue?

Let's check Charles Proxy's Source code by Decompiling Charles.jar file. I have used a decompiler from JD Project but you can use any decompiler. Purpose of this post is not about decompiling.

Once you get the source code by decompiling, Now we need a starting point. It's difficult to start from the main method, because there are bunch of code and it would take days to find the actual code. So let's start by searching the error message. This is the error message you would get after 30 minutes trial period.

Therefore, we need to search for the following string:

"This unlicensed copy of Charles will only run for 30 minutes. You may restart Charles and use it again.
Charles has taken many years of development. If you continue to use Charles please support that effort and purchase a license."

But it's not a good practice to search using whole string. Because there might be some invisible characters such as newline/carriage returns. Therefore, you have to search for part of the string and I did search using "unlicensed copy" and found the source code. As I expected, found a new line character.


Class e has been implemented from Runnable Interface. Therefore run method will be executed automatically when the class e is passed as a parameter of a Thread object.

After showing the error message, program will exit. By clicking this.a.a.exit(0, true) function, you can check it.


Now we need to find the code, where this class is passed as a parameter. This is not a static class. Therefore we can search it using the string "new e(". Some decompilers may provide a feature to get the invocations of specific class.

This is the code segment where that class is being passed as parameter.


As you can see It is invoking the method p.a() and then if that returns false, then program will exit. It is checking if valid license presents. But we don't need to inspect that function since we are going to identify the root cause of this time issue. But that p class contains the information about verifying  license key. Maybe I will cover about license in upcoming post.

This class d implements TimerTask abstract class. A task that can be scheduled for one-time or repeated execution by a Timer(class).

Makes sense right? Let's see who invokes this d class by using the same approach as we followed before by searching for the string "new d(". This is the code segment where class d is called.

As you can see from the above code, Timer class is used to invoke above d class after 1800301 milli seconds. Which is 30 minutes and 301 milli seconds.

Okay, Now we came to the conclusion. After 30 minutes from the Program launch, it is showing the error message and exiting the program with the help of Java Timer class(A facility for threads to schedule tasks for future execution in a background thread. Tasks may be scheduled for one-time execution, or for repeated execution at regular intervals.)

Let's build a sample application and try to reproduce the same issue with our Sample application.

Sample Vulnerable Application

import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

import java.text.SimpleDateFormat;

public class Main {
    public static void main(String[] args) {
        long delay = 30000L; // Delay in Milliseconds
        Timer timer = new Timer();
        TimerTask task = new MyTask();

        System.out.println("Program Started Time is : " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date()));

        timer.schedule(task, delay); // Scheduling our task
        System.out.println("Scheduled Execution Time is : " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date(task.scheduledExecutionTime())));
    }
}

class MyTask extends TimerTask {
    public void run() {
        System.out.println("My Task Executed Time is : " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date()));
        System.out.println("Exiting Program....");
        System.exit(0);
    }
}
As expected, above application also vulnerable. This is how you can reproduce it(As mentioned earlier).
  • Change the date to future date
  • Start the application
  • Once application is started, change the date to actual date(If you can't quickly change the date within 30 seconds, then try increasing the delay).
  • Observe the output

How to fix

In order to fix this issue, we need a function which does not depend on the System Date and Time.
java.util.concurrent.ScheduledExecutorService is a good alternative for that function.

Here is the sample application.

import java.util.concurrent.TimeUnit;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;

import java.util.Date;
import java.util.TimerTask;

import java.text.SimpleDateFormat;

public class Main {
    public static void main(String[] args) {
        long delay = 30000L; // Delay in Milliseconds
        TimerTask task = new MyTask();

        System.out.println("Program Started Time is : " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date()));

        ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
        executor.schedule(task, delay, TimeUnit.MILLISECONDS); // Scheduling our task
        System.out.println("Scheduled Execution Time is : " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date(task.scheduledExecutionTime())));
    }
}

class MyTask extends TimerTask {
    public void run() {
        System.out.println("My Task Executed Time is : " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date()));
        System.out.println("Exiting Program....");
        System.exit(0);
    }
}
This is copy of previous program but this time we make use of java.util.concurrent.ScheduledExecutorService class. Scheduled Execution Time would print 1970-01-01 00:00:00.000(That is epoch start time). Because this time nextExecutionTime variable inside TimerTask class won't be set. And we don't even need TimerTask class at all. We could just implement a class from Runnable Interface. Here is the implementation without TimerTask class.

import java.util.concurrent.TimeUnit;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;

import java.util.Date;

import java.text.SimpleDateFormat;

public class Test {
    public static void main(String[] args) {
        long delay = 30000L; // Delay in Milliseconds
        MyTask task = new MyTask();

        System.out.println("Program Started Time is : " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date()));

        ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
        executor.schedule(task, delay, TimeUnit.MILLISECONDS); // Scheduling our task
    }
}

class MyTask implements Runnable {
    public void run() {
        System.out.println("My Task Executed Time is : " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date()));
        System.out.println("Exiting Program....");
        System.exit(0);
    }
}
Hope this helps. I'm not familiar with all of the Java Classes. Please let me know in the comments, if you have any issues or if I made any mistakes. Thank you!