# Segunda senha (PIN de 6 digitos) -- o que ja se sabe

Levantado em 01/09/2026 com o Ghidra headless sobre o AMo.exe (o nosso, md5 647e44db...).

## O client tem tudo

Quatro pacotes e uma janela, todos presentes no binario:

    CLoginSecondPasswdRegisterPacket    criar
    CLoginSecondPasswdRevisionPacket    alterar
    CLoginSecondPasswdDeletePacket      apagar
    CLoginSecondPasswdCheckPacket       verificar   <- o ecra "Verify second password"
    CGui2ndPassword                     a janela do teclado numerico

## Os opcodes, decifrados

Cada pacote tem um parser que comeca por confirmar os dois bytes do opcode. Achados pela
cadeia RTTI: string ".?AV<classe>@AMOPacket@@" -> TypeDescriptor (8 bytes antes) ->
CompleteObjectLocator -> vtable -> metodo [1], que e o parser.

    pacote     opcode   senha comeca em   funcao do parser
    Register   02 08    byte 12           FUN_0064810f
    Revision   02 09    byte 16           FUN_006481d2
    Delete     02 0A    byte 16           FUN_00648295
    Check      02 0B    byte 20           FUN_00648358

A senha e uma string terminada em NUL, com menos de 0x26 (38) caracteres.

## O 02 0B JA E NOSSO -- e era o "formato nao entendido"

O `build_0x020b_frame` manda 23 bytes copiados de uma captura do oficial, com o comentario
"formato ainda nao decodificado". E o CLoginSecondPasswdCheckPacket:

    02 0b | 00000003 | 00000000 00000000 | 01 | ba | 00000000 | 45 6e 00
                                                                "En"  <- byte 20

Vai antes da lista de personagens (02 01), uma vez so. Ou seja: mandamos o pacote da segunda
senha desde sempre, com os valores de "desativada" que vieram da captura.

## O que falta para a ligar

1. Saber que campo diz "esta conta tem segunda senha". Os candidatos sao o byte 14 (01) e o
   15 (0xba); o byte 20 leva a string "En", que pode ser idioma e nao estado.
2. Decompilar quem CONSOME o objeto depois do parser -- e quem decide abrir a janela.
3. Do nosso lado: guardar a senha na conta, contar tentativas e implementar o bloqueio de
   30 minutos que o proprio ecra promete.

NAO SE ADIVINHA O PASSO 1. Uma captura do oficial com a segunda senha ACTIVA resolve-o de
imediato, comparada com a que ja temos (desactivada). E o mesmo metodo que deu o byte 17 do
06 00: duas capturas que diferem so nisso.

## Mais duas pecas (01/09, mesma sessao)

A JANELA e criada por INDICE numa fabrica com um caso por janela, em 0x004382bd:

    004382a2  CALL 0x00757b90        aloca 0x35c bytes
    004382af  MOV [EBP-0x4], 0x8c    <- 140 e o indice desta janela
    004382bd  CALL 0x005a2a36        construtor do CGui2ndPassword

Ou seja: quem quer o ecra pede a janela 140 a fabrica. Nao ha condicao nenhuma nesse sitio,
o que confirma que a decisao vem de fora -- do campo do pacote que ainda falta identificar.

Procurar 0x8c como constante nao ajuda: e um deslocamento comum de estrutura e da centenas
de ocorrencias sem relacao nenhuma com isto.

## O caminho que resta, e o seu preco

Nao ha captura possivel: o jogador confirmou que a segunda senha NAO esta activa no oficial,
portanto o metodo das duas capturas (o que deu o byte 17 do 06 00) esta fora.

Sobra o teste directo, e neste caso e barato: o 02 0B e um pacote de DADOS que ja mandamos,
com 23 bytes conhecidos. Variar um campo de cada vez e ver se o ecra aparece nao arrisca
travar o client -- ao contrario dos modelos `._OB`, onde o mesmo tipo de tentativa custou
dois travamentos em 01/09. Candidatos, por ordem:

    byte 14 = 0x01     o mais provavel: um estado/bandeira isolado
    byte 15 = 0xba     valor estranho para uma bandeira, mas nao esta explicado
    bytes 20-22 "En"   string; pode ser idioma e nao estado

Reverter e trocar uma constante em world_server.build_0x020b_frame.

## Tentativas falhadas (01/09) -- nao repetir

Tres campos do 02 0B experimentados a vivo, com o jogador a entrar de cada vez. NENHUM fez
o ecra aparecer, e o pacote foi reposto ao original do oficial:

    byte 14   0x01 -> 0x00        nada
    byte 20   "En" -> "123456"    nada (o pacote foi de 23 para 31 bytes)
    byte 15   0xba -> 0x00        nada

No Ghidra, mais quatro caminhos que nao deram:

    quem cria a janela 140    e uma fabrica por INDICE (0x004382bd), sem condicao nenhuma
    constante 0x8c            25 ocorrencias, todas deslocamentos de estrutura ou tamanhos
                              de alocacao (o 0x8c e o tamanho do objeto, nao o indice)
    0x006c93d5                PUSH 0x3, PUSH 0x8c -- e registo de janelas, nao abertura
    opcode como constante     0xb02 / 0x20b / 0x80200 nao aparecem: os dois bytes sao
                              escritos separados, nao como um u16

## O que ainda resta

Definir no Ghidra a funcao que contem a fabrica em 0x004382bd -- hoje esta "fora de funcao",
e por isso o Ghidra nao mostra quem a chama. Com a funcao definida e reanalisada, as chamadas
aparecem, e a condicao da segunda senha esta imediatamente antes da que passa o indice 140.
E o unico caminho que sobra sem captura, e nao e curto.

## O que a captura mostrou (01/09, 20:37) -- e o que fecha

Com o hook armado e os cinco campos do 02 0B ja experimentados, gravou-se a entrada
completa. Sequencia dos pacotes 02 xx do login ate a lista de personagens:

    SEND 02 00 (360)   pedido de login
    RECV 02 06 ( 32)   CChannelWaitLoginPacket -- lista de canais, ja identificado
    RECV 02 00 (290)   resposta do login
    RECV 02 07 ( 24)   canal
    SEND 02 07 ( 24)   escolha do canal
    RECV 02 0B ( 23)   <- o nosso, com os campos alterados
    RECV 02 01 x4      a lista de personagens

DUAS COISAS FICAM PROVADAS:

1. O client RECEBE o 02 0B (esta la, com os bytes que mandamos) e NAO REAGE.
2. O client NUNCA MANDA nada da familia -- zero SEND de 02 08, 02 09, 02 0A ou 02 0B. Ele
   nao pergunta pela segunda senha, portanto nao ha pergunta nossa por responder.

Campos do 02 0B todos experimentados, nenhum fez aparecer o botao nem a janela:

    byte 14   0x01 -> 0x00
    byte 15   0xba -> 0x00
    byte 20   "En" -> "123456"
    u32 @ 6   0 -> 1
    u32 @ 10 e @ 16   0 -> 1 (ao mesmo tempo)

O pacote foi reposto ao original do oficial, byte a byte.

## Onde isto fica

O client TEM tudo: os quatro pacotes com opcode conhecido, a janela (ID 0xe14c, nao
desactivada -- aparece como as vizinhas que funcionam) e o botao definido no script
Contents_Enter.Sct nos tres layouts, com o controlo 1260 presente no Ctrl_Button.Sct.

O que liga isso NAO esta no 02 0B nem em nenhum pacote que o client mande. Fica por
descobrir, e as pistas por seguir sao:

  - o que o CGuiManager faz com o botao indice 4 do GUI 30027 quando monta a tela;
  - se ha uma opcao de configuracao local (registo, ficheiro de opcoes) que o client leia;
  - decompilar quem monta a tela de personagem e ve porque esconde esse controlo.

NAO CONTINUAR A VARIAR BYTES: cinco tentativas, cinco entradas do jogador, zero sinal.

## Segunda ronda (01/09, noite) -- o mecanismo da visibilidade

Achado o par que liga e desliga qualquer controlo. O byte +0x5f do objeto e a flag:

    FUN_00455617(obj)   obj[0x5f] = 1    MOSTRAR
    FUN_00455620(obj)   obj[0x5f] = 0    ESCONDER

E o caminho ate a tela de personagem, que tinha lido mal a primeira vez:

    PUSH 0x7562 (GUI 30050 do script) -> FUN_00651331, o handler do 02 01
    FUN_00440885(4, 0)   o 4 e o numero do ECRA, nao o indice do controlo
    ecra 4 = tela de personagem:  abre em FUN_00446866, fecha em FUN_004469c5

NENHUMA das chamadas de esconder que existem no codigo da tela mexe em subcontrolos -- todas
escondem GUIs inteiros (0x7553, 0x7555, 0x7556, 0x7557).

## Dois testes no script, e um deles partiu o client

1. MOVER o botao para o centro do ecra (420,380) nos tres layouts: NAO apareceu. Logo nao
   estava escondido atras de outra coisa nem fora de vista -- e escondido a serio.

2. MUDAR o indice do botao de 4 para 26, com NumSubCtrl de 26 para 27: O CLIENT FECHOU-SE
   ao entrar no canal. Ou seja o numero de controlos por GUI nao e livre -- ha um array de
   tamanho fixo e os indices sao validados. Revertido para Contents_Enter.Sct.antes_2ndpass.

   (A "anomalia" do indice 21 duplicado que se viu a caminho e falsa: a segunda linha esta
   comentada com //.)

## Estado

O botao esta no script, o controlo existe, a janela existe e nao esta desactivada, os quatro
opcodes sao conhecidos, e o client nao reage a nada que se mande no 02 0B nem pergunta nada.
Falta achar QUEM poe o +0x5f do Btn_2ndPass a zero -- e nao esta no codigo da tela.

## Terceira ronda -- onde a coisa fica, e porque

Descartado, um a um:

    as duas escritas directas em +0x5f      sao CONSTRUTORES (zeram tudo ao criar o controlo)
    mostrar/esconder na tela de personagem  todas actuam em GUIs INTEIROS, nunca em subcontrolos
    a definicao do controlo (1389/1260)     completa, com Normal/Click/Over/Disable e texto
    as 13 imagens dos botoes                TODAS presentes, incluindo as quatro do nosso

E dois testes no script, sem tocar em indices nem contadores:

    botao movido para o centro do ecra          nao aparece
    botao com a APARENCIA de um que funciona     nao aparece
    (usou-se o controlo 1252, o do Btn_Prev, no mesmo indice 4)

O SEGUNDO E O QUE FECHA: com a aparencia de um botao que o jogo desenha todos os dias, no
mesmo sitio, o slot continua vazio. Logo o problema NAO e o controlo, nem a imagem, nem a
posicao, nem a visibilidade: O CLIENT NAO DESENHA O CONTROLO DE INDICE 4 desta tela. Ignora-o
ao montar, seja o que for que la esteja.

## Conclusao

O client tem os quatro pacotes, a janela (0xe14c, viva como as vizinhas) e o botao no script
com tudo a que tem direito -- mas foi compilado sem ligar o controlo 4 da tela de personagem.
Nao ha nada do lado do servidor nem dos dados que o faca aparecer: isto so se resolve
mexendo no binario, e o sitio exacto ainda nao apareceu.

O que falta olhar, e e o unico caminho que sobra: o PARSER do .Sct no binario -- como le o
#.UseSubCtrl e porque salta este. Nao se olhou la ainda.

CUSTO ATE AQUI: uma sessao inteira, sete entradas do jogador, um crash do client (indice 26
com NumSubCtrl 27 -- o client valida os indices). Ganhou-se: os 4 opcodes, a identificacao do
02 0B que era "formato desconhecido" no nosso codigo, o ID da janela, o mecanismo da
visibilidade (+0x5f) e a certeza de que o caminho e o binario.

## O parser do .Sct -- o que se achou (01/09, fim da sessao)

As palavras-chave dos scripts ESTAO no binario, em MAIUSCULAS: o parser passa cada linha por
__strupr antes de comparar. A tabela inteira esta a volta de 0x3fe8c8 no ficheiro, e o
tradutor palavra-chave -> codigo e o FUN_0044abb9:

    #.HIDE         -> 0x3c      (existe uma directiva de esconder no formato!)
    #.NUMSUBCTRL   -> 0x61
    #.USESUBCTRL   -> 0x62
    #.GUI          -> 8

E O LEITOR DE TOPO, FUN_0044a827, TEM UMA LISTA DE EXCLUSAO -- le o bloco e IGNORA-O se o id
estiver nela:

    if (id != 0x36b1 && id != 0x36b6 && id != 15000 && id != 0x3a99) { ... processa ... }
       (14001)      (14006)         (15000)        (15001)

Nao e a nossa (a tela de personagem e a 30050), mas PROVA QUE O MECANISMO EXISTE: ha ids que
o client le do script e deita fora de proposito. A constante 0x36b1 so aparece em dois
sitios do binario, este e o FUN_007a4450.

## O que falta, agora com alvo certo

Achar o metodo que trata o codigo 0x62 (#.USESUBCTRL) -- e ele que le cada `#.UseSubCtrl` e
cria (ou nao) o controlo. Se houver uma lista de exclusao la dentro, e onde o indice 4 desta
tela e saltado, e um patch nessa comparacao resolve tudo.

O caminho: FUN_0044a827 chama `(**(piVar3 + 4))(ficheiro)` -- um metodo virtual do objeto GUI
acabado de criar, que le o CORPO do bloco. E esse metodo que tem o switch dos codigos.
A vtable do CGuiWindow nao resolveu pelo script AcharVTable (os COL candidatos dao lixo);
resolver isso a mao no Ghidra e o passo seguinte.

## Quarta ronda (02/09) -- a cadeia inteira, e a conclusao

Seguiu-se a cadeia toda, do pacote ate quem devia agir:

    a classe do pacote EXISTE e e instanciada
        vtable em 0x00812534 (e uma copia em 0x00823990); o objeto vive em [jogo + 0x7738],
        criado e destruido em FUN_00422bca / FUN_004231df, que e a inicializacao do client.

    o HANDLER e o metodo [27] dessa vtable -- FUN_00648897, em 0x008125a0.
    Tem tres irmaos SEQUENCIAIS ao lado (FUN_00648756, 006487c1, 0064882c), que sao com
    certeza os outros tres pacotes da familia (02 08, 02 09, 02 0A).

    O QUE O HANDLER FAZ:
        MOV ESI,0x838053     <- ponteiro para uma string VAZIA, em .rdata
        CALL strlen          <- 0
        CALL strncpy         <- copia zero bytes para o destino
    Ou seja: corre o parser (que le e guarda a senha) e devolve SEMPRE vazio.

TESTADO: patch de um byte, 0x838053 -> 0x838054 (que ja tem ":" la), para o handler devolver
uma string nao vazia. NAO MUDOU NADA. Revertido; o original esta em Axn.exe.bak_2asenha.

Logo o resultado do handler nao e usado por ninguem, ou o metodo [27] nunca e invocado --
procurou-se `CALL [reg+0x6c]` e os 9 sitios que aparecem sao codigo de desenho.

## Conclusao, ao fim de duas sessoes

TUDO existe neste client e NADA o liga:

    os 4 pacotes, com opcode conhecido e parser a funcionar
    a janela (ID 0xe14c), viva como as vizinhas
    o botao no script, com controlo e imagens completos
    a classe do gestor, instanciada na inicializacao

E nada o invoca: o client recebe o 02 0B e ignora-o, nunca manda nenhum dos quatro, nao
desenha o botao (o controlo de indice 4 e ignorado, mesmo com a aparencia de um que funciona)
e nao reage ao handler devolver outra coisa.

A leitura mais provavel e que a funcionalidade foi ACRESCENTADA numa versao POSTERIOR: este
binario e de 2019 e traz as pecas (herdadas ou preparadas), mas o fluxo que as usa nao esta
escrito. Ligar isso nao e um patch de bytes -- e escrever codigo novo no executavel.

O QUE JA NAO VALE A PENA TENTAR (feito, e sem efeito):
    os 5 campos do 02 0B; a ordem do pacote (antes e depois da lista); o botao no script
    (posicao, indice, aparencia de outro botao); o limite das imagens de fundo; o valor
    devolvido pelo handler.

CAMINHO ALTERNATIVO, se a funcionalidade importar: implementa-la no LOGIN, do nosso lado,
sem a janela nativa -- o servidor pede e valida um PIN por outra via. Nao e tao bonito, mas
nao depende de nada disto.
