• There is NO official Otland's Discord server and NO official Otland's server list. The Otland's Staff does not manage any Discord server or server list. Moderators or administrator of any Discord server or server lists have NO connection to the Otland's Staff. Do not get scammed!
  • 2026 staff recruitment is open! Check it out and consider applying!

C++ Real Tibia BUG (Vocation on ROOKGAARD)

TibiCAM

Veteran OT User
Joined
Feb 3, 2020
Messages
214
Reaction score
295
Someone was able to get a vocation in Rookgaard on real Tibia by supposedly creating an application that invokes a function on the address that handles the vocation window. In other words he could make a character and go to Rookgaard, and then call the function that shows the "Select vocation" dialog window, and pick any vocation.

Video:

On Linux, so far I have used Cutter to analyze the game client. It found 493377 strings. When I filter by "vocation" I see many that could potentially be connected to that "Select vocation" dialog window. It finds 183 strings. The first one in the list is this:

Address: 0x0182e640
String: tutorial/VocationSelectionPopup.qml

Further down we can see this:
Address: 0x01a99904
String: VocationCardWithBanner

And many, many more.
When I right-click on one, for example the first address, and select "Show X-Refs" it shows the following:

Code:
0x004aa82c      mov     rdi, qword [rbp - 0x1358]
0x004aa833      call    fcn.01512ec0 ; fcn.01512ec0
0x004aa838      mov     rdi, qword [r14 + 0x4f0]
0x004aa83f      movdqa  xmm4, xmmword [rbp - 0x70]
0x004aa844      movups  xmmword [r14 + 0x4e8], xmm4
0x004aa84c      test    rdi, rdi
0x004aa84f      je      0x4aa856
0x004aa851      call    fcn.00487730 ; fcn.00487730
0x004aa856      mov     rdi, qword [rbp - 0x48]
0x004aa85a      test    rdi, rdi
0x004aa85d      je      0x4aa864
0x004aa85f      call    fcn.00487730 ; fcn.00487730
0x004aa864      mov     rdi, qword [r14 + 0x248]
0x004aa86b      call    fcn.00604430 ; fcn.00604430
0x004aa870      mov     edi, 0x70  ; 'p'
0x004aa875      mov     r12, rax
0x004aa878      mov     rax, qword [r14 + 0x10]
0x004aa87c      mov     qword [rbp - 0x13a0], rax
0x004aa883      call    imp.operator new(unsigned long) ; sym.imp.operator_new_unsigned_long
0x004aa888      mov     rcx, qword [0x0226f108]
0x004aa88f      lea     rsi, [0x0182e640]
0x004aa896      mov     rdi, r13
0x004aa899      mov     rbx, rax
0x004aa89c      lea     rax, [rax + 0x10]
0x004aa8a0      mov     qword [rax - 8], rcx
0x004aa8a4      lea     rcx, [0x02795c58]
0x004aa8ab      mov     qword [rax - 0x10], rcx
0x004aa8af      mov     qword [rbp - 0x13b8], rax
0x004aa8b6      call    fcn.00486980 ; fcn.00486980
0x004aa8bb      mov     rdi, qword [rbp - 0x1328]
0x004aa8c2      xor     edx, edx
0x004aa8c4      mov     rsi, r13
0x004aa8c7      call    imp.QUrl::QUrl(QString const&, QUrl::ParsingMode) ; method.QUrl.QUrl_QString_const___QUrl::ParsingMode
0x004aa8cc      mov     rcx, qword [rbp - 0x1330]
0x004aa8d3      mov     rdx, qword [rbp - 0x13a0]
0x004aa8da      lea     rdi, [rbx + 0x10]
0x004aa8de      mov     rsi, qword [rbp - 0x1328]
0x004aa8e5      call    fcn.014f2a40 ; fcn.014f2a40
0x004aa8ea      mov     rdi, qword [rbp - 0x1328]
0x004aa8f1      call    imp.QUrl::~QUrl() ; method.QUrl._QUrl

Am I now supposed to lookup each function (assuming it's the ones beginning with "fcn.xxxxxxxx") and try invoke that somehow?
I have no idea how to try invoke a function on a specific address via C++, so naturally I used AI like the C++ noob I am, and made this (Sorry for the AI slop code):
C++:
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <sys/user.h>
#include <sys/mman.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <stdint.h>
// Shellcode to call function and trap (int3) to return control.
// Assembly: push rbp; mov rbp, rsp; call <addr>; int3; (simple, no args)
// Replace <addr> with runtime addr.
// Hex: \x55\x48\x89\xe5\xe8\x00\x00\x00\x00\xcc  (rel call placeholder)
unsigned char shellcode[] = {
    0x55,                   // push rbp
    0x48, 0x89, 0xe5,       // mov rbp, rsp
    0xe8, 0x00, 0x00, 0x00, 0x00,  // call rel32 (placeholder)
    0xcc                    // int3 (breakpoint to catch)
};
size_t shellcode_size = sizeof(shellcode);
// Function to write data to process memory
void poke_data(pid_t pid, uintptr_t addr, const void* data, size_t len) {
    const uint8_t* bytes = (const uint8_t*)data;
    size_t word_size = sizeof(long);
    for (size_t i = 0; i < len; i += word_size) {
        long word = 0;
        memcpy(&word, bytes + i, (len - i < word_size) ? (len - i) : word_size);
        if (ptrace(PTRACE_POKEDATA, pid, addr + i, word) == -1) {
            perror("ptrace(POKEDATA)");
            exit(1);
        }
    }
}
int main(int argc, char* argv[]) {
    if (argc != 3) {
        fprintf(stderr, "Usage: %s <pid> <function_address_hex>\n", argv[0]);
        return 1;
    }
    pid_t pid = atoi(argv[1]);
    uintptr_t func_addr = strtoull(argv[2], NULL, 0);
    // Attach to process
    if (ptrace(PTRACE_ATTACH, pid, NULL, NULL) == -1) {
        perror("ptrace(ATTACH)");
        return 1;
    }
    int status;
    waitpid(pid, &status, 0);
    // Get current registers
    struct user_regs_struct regs, orig_regs;
    if (ptrace(PTRACE_GETREGS, pid, NULL, &regs) == -1) {
        perror("ptrace(GETREGS)");
        return 1;
    }
    orig_regs = regs;
    // Find injectable memory (e.g., use /proc/<pid>/maps to find rw- region; simplify: assume near RIP or allocate via mmap injection)
    // For simplicity, overwrite at current RIP (dangerous! Backup first). Better: Inject into .text or alloc new.
    uintptr_t inject_addr = regs.rip;  // Overwrite current instruction pointer location (pause point)
    // Backup original code at inject_addr
    unsigned char orig_code[shellcode_size];
    for (size_t i = 0; i < shellcode_size; i += sizeof(long)) {
        errno = 0;
        long word = ptrace(PTRACE_PEEKDATA, pid, inject_addr + i, NULL);
        if (errno != 0) {
            perror("ptrace(PEEKDATA)");
            return 1;
        }
        memcpy(orig_code + i, &word, sizeof(long));
    }
    // Patch shellcode with call offset (relative to call instr + 5)
    int32_t call_offset = (int32_t)(func_addr - (inject_addr + 4 + 5));  // call is at offset 4, +5 for instr size
    memcpy(shellcode + 5, &call_offset, sizeof(int32_t));
    // Inject shellcode
    poke_data(pid, inject_addr, shellcode, shellcode_size);
    // Set RIP to inject_addr
    regs.rip = inject_addr;
    if (ptrace(PTRACE_SETREGS, pid, NULL, &regs) == -1) {
        perror("ptrace(SETREGS)");
        return 1;
    }
    // Continue to execute shellcode (calls function, hits int3)
    if (ptrace(PTRACE_CONT, pid, NULL, NULL) == -1) {
        perror("ptrace(CONT)");
        return 1;
    }
    waitpid(pid, &status, 0);  // Wait for int3 (SIGTRAP)
    // Restore original code and registers
    poke_data(pid, inject_addr, orig_code, shellcode_size);
    if (ptrace(PTRACE_SETREGS, pid, NULL, &orig_regs) == -1) {
        perror("ptrace(SETREGS restore)");
        return 1;
    }
    // Detach
    if (ptrace(PTRACE_DETACH, pid, NULL, NULL) == -1) {
        perror("ptrace(DETACH)");
        return 1;
    }
    printf("Function called successfully.\n");
    return 0;
}

I then compile it like so:
g++ tibia_vocation.cpp -o tibia_vocation

And then I launch it by giving it the PID and the address:

For example:
./tibia_vocation 37776 0x01940230

I've tried hundreds of addresses related to functions, but it either crashes the game client or just prints to the console that it successfully called the function (but nothing happens). I've tried going through the C++ code but I'm unsure what I'm doing. I was looking around the forums to see if anyone has any example code that shows how to invoke a function in the real Tibia client using C++, but could not find any.

Would love to see if anyone here has a clue on how to do this "trick" (bug). Thanks!
I don't mind if CipSoft will patch it soon. But this could be a good learning experience for me and others.


Cutter using their AppImage from Github repo:
Bash:
wget https://github.com/rizinorg/cutter/releases/download/v2.4.1/Cutter-v2.4.1-Linux-x86_64.AppImage
sudo chmod +x Cutter-v2.4.1-Linux-x86_64.AppImage
./Cutter-v2.4.1-Linux-x86_64.AppImage

I then set it to analzye the Tibia client file (inside ~/.local/share/CipSoft GmbH/Tibia/packages/Tibia/bin/client)
It took 1-2 minutes and then it revealed all Strings in the game client.
 
Last edited:
can't you invoke the client through natural gameplay and then relog to another character while that window is still on? prevent the shutdown call it should work like magic then?
 
I've concluded I will not be able to get this working. I know nothing about this low-level programming stuff and I've wasted almost 24 hours using ChatGPT and Grok to get it working. Unless @jo3bingham or someone else more skilled can make a script that sets a vocation then this will remain unfinished. It's just too much for me to understand.

I've been trying for hours just to get the "TibiaAPI" working by hex editing the "loginWebService" and RSA key but it will not login (but I see my character list). I assume it's because Tibia 15+ has introduced a lot of changes in the login functionality or something else. Using TibiaAPI would probably be the easiest way to get this working, but I have no idea how to even get it running.

Is there anyone here who has any idea how to get this running? Jo3 let us know the packet. But I don't know how to send that packet. Does anyone here know?? :D I've read his guide 100 times but I can't even get TibiaAPI working:

 
Last edited:
i've been trying here too, the thing can you share the filter you've been using in wireshark to find that packet? i've been looking into those small datas, usually less than length 80, thinkin about expanding, but there's so many its been overwhelming for me. Any suggestions?
 
i've been trying here too, the thing can you share the filter you've been using in wireshark to find that packet? i've been looking into those small datas, usually less than length 80, thinkin about expanding, but there's so many its been overwhelming for me. Any suggestions?

I couldnt get Wireshark running because all the network traffic is encrypted there. I don't know if there's a way to decrypt it. Wireshark does have a Tibia filter and then I would filter on the port 7171. I could see some traffic, but it was all encrypted.
Instead, I think using TibiaAPI by Jo3bingham is the best way to do it.
But I struggle to get TibiaAPI running.

I even made the changes he mentioned here:

I don't know if I'm doing something wrong with my Tibia client (Hex editing).
I need to change the "loginWebService" address from:
loginWebService=https://www.tibia.com/clientservices/loginservice.php

To:
loginWebService=http://127.0.0.1:7171/

And also fill the rest of the space after "loginWebService=http://127.0.0.1:7171/" with "0A" 31 times to fill the empty space.

And also the RSA key from:
BC27F992A96B8E2A43F4DFBE1CEF8FD51CF43D2803EE34FBBD8634D8B4FA32F7D9D9E159978DD29156D62F4153E9C5914263FC4986797E12245C1A6C4531EFE48A6F7C2EFFFFF18F2C9E1C504031F3E4A2C788EE96618FFFCEC2C3E5BFAFAF743B3FC7A872EE60A52C29AA688BDAF8692305312882F1F66EE9D8AEB7F84B1949

To:
9B646903B45B07AC956568D87353BD7165139DD7940703B03E6DD079399661B4A837AA60561D7CCB9452FA0080594909882AB5BCA58A1A1B35F8B1059B72B1212611C6152AD3DBB3CFBEE7ADC142A75D3D75971509C321C5C24A5BD51FD460F01B4E15BEB0DE1930528A5D3F15C1E3CBF5C401D6777E10ACAAB33DBE8D5B7FF5

I'm certain I did everything correctly, both in TibiaAPI and the client binary file with a hex editor.
I can launch the Tibia client and I can see my character list. The TibiaAPI proxy is running too.
But when I try login to the game, nothing happens. It just tries to connect forever.

TibiaAPI works by implementing a proxy in between the user and CipSoft, to intercept and modify packets.

If we can get TibiaAPI working on the latest Tibia client, we can definitely send this packet easily. Unless someone else knows how to send a packet.
Post automatically merged:


This is how far I am able to get with TibiaAPI.
 
Last edited:
I think it got patched, cateroide's new video talks to the guy who did it, he said he develop a software to work on Tibia. I guess is something like tibia api
 
This guy said that there's still things that doesnt get checked by the server, he said something about costing thousands of tibiacoins, i think is outfit/mount things. Maybe that still works
 
This guy said that there's still things that doesnt get checked by the server, he said something about costing thousands of tibiacoins, i think is outfit/mount things. Maybe that still works
When I was developing for FiveM a few years ago (GTA5), the amount of things controlled by the client (which should have had server checks) was an eye-opener... hence why Rockstar had the crazy money spawning glitch and insane amount of teleport hacking back in the day. They simply did not want to overload the server with thousands of checks.

They even still have a bunch of methods that are still unknown or have been deprecated etc.
0x023ACAB2DC9DC4A4 (https://docs.fivem.net/natives/?_0x023ACAB2DC9DC4A4)

Cipsoft seems to have gone down the same path... there are most likely, a ton of things not being checked by the server...
 
Last edited:
Although this is patched, I'd love to see someone's code about how to run functions in tibia client (and how to investigate the packet number). Maybe there are other things that could be explored in Tibia, such as other codes that are only checked in client-side.

If someone could make a script (any language) that does something easy in the client (just as a tutorial / example), please share it here.
 
Completely unrelated, but I wonder if there is a way to skip waiting lists. Ever since December when CipSoft reduced the limit of players online, the popular game worlds end up having 2-8 hour long waiting lists. Secura is impossible to login to in the afternoon, until midnight (German time zone).

I'm sure that everything that is related to the waiting list is made entirely on the server. But perhaps there is a way to spoof your position in the waiting list or send a packet to get signed in. It would be hilarious if there was a way to do that.

I'll look into TibiaAPI and try learn more low-level stuff. This stuff is interesting and it is something I must learn. Seems like a very deep rabbit hole that never ends, but I need some new hobby projects anyway. Thank you for making TibiaAPI, I will try to understand how it works and how to update it to work with Tibia 15+
 
Back
Top