Announcement

Collapse
No announcement yet.

Protection and Conditional Statements

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

  • Gary Akins
    Guest replied
    <<shrug>> I'm just trying to give you a users' perspective on
    the matter, Mike... you may not care what happens to your
    users if you lose interest in the program, get hit by a bus, or
    just decide to go "find yourself" in the Australian outback for a
    few years - but I, as a user, damn sure would care what
    happens to me; thus, as a user, I would not even consider
    purchasing a program that used a protection scheme of this type,
    no matter how useful it might otherwise be.

    In other words: it's quite likely that instead of getting
    more registrations because people can't pirate your
    program, you may instead find yourself getting fewer
    registrations because your would-be customer base will consider
    the risk unacceptable and will simply not use your program at all.

    You might also want to consider that you could be
    opening yourself up to some legal liability issues here, if your
    program should suddenly decide that it's no longer allowed to
    work and you can't be gotten hold of for an updated serial
    number... forget the car crash; let's just say you decide to
    take a couple of weeks' vacation on a Carnival Cruise ship. Can
    you afford the lawyer to defend yourself against the civil suit
    from the guy who lost a week's worth of productivity because his
    IT manager swapped his network card while you were away? (Oh,
    sure, you can write a clause into your license that says
    you're not liable, and you might actually win the case in
    court, but that won't prevent the suit from being filed.
    Remember, in today's society, it isn't who's right or who's
    wrong, it's whether or not their lawyer can beat up your lawyer.
    )




    [This message has been edited by Gary Akins (edited May 02, 2001).]

    Leave a comment:


  • Semen Matusovski
    replied
    Mike --
    When you click Ctrl-F5, PbEdit calls PbDll1.Exe instead of PbDll.Exe (if you corrected a path for compiler).
    PbDll1.Exe analyzes source code, remembers a command '%C &H12345678 and calls a compiler (for compiler '%C is a comment).
    When compilation is finished, PbDll1.Exe (before returning control to PbEdit.Exe) opens compiled exe and writes CRC after EOF.
    That's why, when program starts, it's already has CRC


    ------------------
    E-MAIL: [email protected]

    Leave a comment:


  • Mike Trader
    replied
    Gary and Mark,
    This app is going to very few people. If I get hit by a Car I probably wont care if they change hardware and it no longer works! (is that bad Karma?)

    I just want to make sure that it does not end up on a d/l site for the world and their mother to use for free!

    Semen,
    I got all the files OK. I followed the instrutions, and got the example to work! This is amazing! I think this another level of programming from where I am at. I have studied it all for a while. I love the trick:
    dAddr = Crc1 - Crc2 + CODEPTR(Continue)
    GOTO DWORD dAddr
    Thats exactly what I started the thread for. Cool.
    as for the CRC, I see how it is calculated but I dont understand how you append it to the end of the file and then read it later.

    I have tried many ways and I just cant seem to get it to work. Calculating it seems to be no problem. I open the file for writing as binary then what?

    ------------------
    Kind Regards
    Mike

    Leave a comment:


  • Mark Newman
    replied
    Allow me to chime in with another Yikes! My biggest problem with tying
    the app to the hardware is that it treats the customer as a potential criminal.

    At my previous job one of our sister divisions had a new app framework for sale
    that used the PC hardware as the security key. They had drop the scheme as the
    feedback from customers and potential customers was overwhelmingly negative.

    If you're that concerned about security, use a dongle.


    ------------------
    Mark Newman



    [This message has been edited by Mark Newman (edited May 02, 2001).]

    Leave a comment:


  • Gary Akins
    Guest replied
    Each customer will get a unique number that will not work on another machine. As somone said earlier, when they reformat or upgrade hardware and re-install, I have to send them a new password, but thats fine.

    Yikes - you're going to tie the serial number to the user's hardware configuration??? Bad, bad, bad idea.

    Not only are most people not going to want to put up with the inconvenience of having to re-register every time they do something to their system, but they're going to be very nervous about what happens to them come the day that you lose interest in the product, get hit by a bus, or whatever, and they can no longer get new serial numbers for the program they purchased.

    ------------------

    Leave a comment:


  • Semen Matusovski
    replied
    mike ---
    i uploaded all files to http://ltr.perevozki.ru/pb/pbdll1.zip

    1) put crc32.inc into c:\pbdll60\winapi (together with winapi.inc)
    2) put pbdll1.bas and pbdll1.exe into c:\pbdll60\bin (together with pbdll.exe)
    3) in pb editor click menu window - options - compiler . change a path for compiler. should be pbdll1.exe instead of pbdll.exe.
    4) then you can start small sample, which i posted yesterday.

    Code:
       #compile exe
       #dim all
       #register none
       #include "win32api.inc"
       #include "crc32.inc" ' <-- two assembler subroutines, posted in [url="http://www.powerbasic.com/support/pbforums/showthread.php?t=23005"]http://www.powerbasic.com/support/pbforums/showthread.php?t=23005[/url] 
       
       '%c &h12345678
                   
       function pbmain
          dim tmpasciiz as asciiz * 255, buffer as string
          dim daddr as dword, crc1 as long, crc2 as long, scrctable as string * 1024
                
          if isfalse(getmodulefilename(byval 0&, tmpasciiz, sizeof(tmpasciiz))) then exit function
          open tmpasciiz for binary shared as #1 len = 32768
          get$ #1, lof(1), buffer
          close #1
          
          crc2 = cvl(buffer, len(buffer) - 3)
          crc32_buildtable scrctable, &h12345678
          crc1 = crc32_calculate (strptr(buffer), len(buffer) - 4, scrctable, len(buffer) - 4, 1)
          
          daddr = crc1 - crc2 + codeptr(continue)
          goto dword daddr
          
      continue:
          msgbox "ok"
          
       end function
    btw, ide - i beleive - is integrated development environment (here pbedit).

    [this message has been edited by semen matusovski (edited may 02, 2001).]

    Leave a comment:


  • Mike Trader
    replied
    Semen,

    I have spent all afternoon working on your examples. I dont get the Wrapper stuff tho I am lost with that code. I also dont know what IDE is.

    But I did get the MakeCRC to work in a modified form (see below). I can append the CRC i think but only to file that are 10k or less. I have an EXE that is 26k and it does not get any longer when it append the CRC to it!?

    Here is the code I put together:
    #COMPILE EXE
    #REGISTER NONE
    #DIM ALL
    #INCLUDE "WIN32API.INC" ' Win API definitions
    #INCLUDE "Comdlg32.INC" ' For Open/Save Dialog

    '-----------------------------------------------------------------------------------------
    FUNCTION OpenSaveDialog(BYVAL Caption AS STRING, _ ' caption
    BYVAL NameofFile AS STRING, _ ' filename
    BYVAL InitialDir AS STRING, _ ' start directory
    BYVAL Filter AS STRING, _ ' filename filter
    BYVAL DefExtension AS STRING, _ ' default extension
    Flag AS LONG) AS STRING ' 1 = Save Dlg, 0 = Open Dlg
    LOCAL Ofn AS OPENFILENAME
    LOCAL zFileName AS ASCIIZ * 256
    LOCAL zFileTitle AS ASCIIZ * 256
    LOCAL zFilter AS ASCIIZ * 256
    LOCAL zInitialDir AS ASCIIZ * 256
    LOCAL zTitle AS ASCIIZ * 256
    LOCAL zDefExt AS ASCIIZ * 10
    REPLACE "|" WITH CHR$(0) IN Filter
    zFilter = Filter + CHR$(0)
    zInitialDir = InitialDir + CHR$(0)
    zFileName = NameofFile + CHR$(0)
    zDefExt = DefExtension + CHR$(0)
    zTitle = Caption + CHR$(0)
    ofn.lStructSize = SIZEOF(ofn)
    ofn.hWndOwner = 0
    ofn.lpstrFilter = VARPTR(zFilter)
    ofn.nFilterIndex = 1
    ofn.lpstrFile = VARPTR(zFileName)
    ofn.nMaxFile = SIZEOF(zFileName)
    ofn.lpstrFileTitle = VARPTR(zFileTitle)
    ofn.nMaxFileTitle = SIZEOF(zFileTitle)
    ofn.lpstrInitialDir = VARPTR(zInitialDir)
    IF LEN(zTitle) THEN
    ofn.lpstrTitle = VARPTR(zTitle)
    END IF
    ofn.Flags = Flag
    IF Flag = 1 THEN
    CALL GetSaveFilename(ofn)
    ELSE
    CALL GetOpenFilename(ofn)
    END IF
    ofn.lpstrDefExt = VARPTR(zDefExt)
    FUNCTION = zFileName
    END FUNCTION
    '-------------------------------------------------------------------------
    SUB Crc32_BuildTable(sCrcTable AS STRING * 1024, BYVAL MagWord AS LONG)

    ! MOV EDI, sCrcTable
    ! MOV EDX, 0

    Crc32_BuildTable_Lb1:
    ! MOV EBX, EDX
    ! MOV ECX, 8

    Crc32_BuildTable_Lb2:
    ! MOV EAX, EBX
    ! AND EAX, 1
    ! JE Crc32_BuildTable_Lb3
    ! SHR EBX, 1
    ! MOV EAX, MagWord
    ! XOR EBX, EAX
    ! JMP Crc32_BuildTable_Lb4

    Crc32_BuildTable_Lb3:
    ! SHR EBX, 1

    Crc32_BuildTable_Lb4:
    ! DEC ECX
    ! JNZ Crc32_BuildTable_Lb2
    ! MOV [EDI], EBX
    ! ADD EDI, 4
    ! INC EDX
    ! CMP EDX, 255
    ! JLE Crc32_BuildTable_Lb1
    END SUB

    '-------------------------------------------------------------------------------------
    FUNCTION Crc32_Calculate (BYVAL Address AS DWORD, BYVAL Length AS LONG, _
    sCrcTable AS STRING * 1024, BYVAL InitCrc AS LONG, BYVAL Inverse AS LONG) AS LONG

    ! MOV EBX, InitCrc
    ! MOV EDI, Address
    ! MOV ESI, EDI
    ! ADD ESI, Length

    Crc32_Calculate_Lb1:
    ! CMP EDI, ESI
    ! JGE Crc32_Calculate_Lb2

    ! MOV EAX, 0
    ! MOV AL, [EDI]


    ! XOR EAX, EBX
    ! AND EAX, &HFF

    ! SHL EAX, 2
    ! add EaX, sCrcTable
    ! MOV EAX, [EAX]
    ! SHR EBX, 8
    ! AND EBX, &H00FFFFFF
    ! XOR EBX, EAX
    ! INC EDI
    ! JMP Crc32_Calculate_Lb1

    Crc32_Calculate_Lb2:
    ! CMP Inverse, 0
    ! JE Crc32_Calculate_Lb3
    ! XOR EBX, &HFFFFFFFF

    Crc32_Calculate_Lb3:
    ! MOV Function, EBX
    END FUNCTION

    '----------------------------------------------------------------------------------
    FUNCTION PBMAIN() AS LONG
    LOCAL WZCrc AS LONG, Crc AS LONG, sCrcTable1 AS STRING * 1024, sCrcTable2 AS STRING * 1024
    LOCAL TargetFile AS STRING, BUFFER AS STRING

    TargetFile = OpenSaveDialog("Exe to append checksum to", "", "C:\PBDLL60\", "exe", "exe", 0)
    IF TargetFile = "" THEN EXIT FUNCTION

    OPEN TargetFile FOR BINARY SHARED AS #1: GET$ #1, LOF(1), Buffer: CLOSE #1
    MSGBOX TargetFile+ $CRLF +"fileLength= "+STR$(LEN(Buffer))
    Crc32_BuildTable sCrcTable1, &HEDB88320
    WZCrc = Crc32_Calculate (STRPTR(Buffer), LEN(Buffer), sCrcTable1, &HFFFFFFFF, 1)

    Crc32_BuildTable sCrcTable2, &H12345678
    Crc = Crc32_Calculate (STRPTR(Buffer), LEN(Buffer), sCrcTable2, -2&, 0)

    MSGBOX "WinZip Incompatible CRC= "+HEX$(Crc)+" Winzip compatible CRC= "+HEX$(WZCrc)

    'IF sCrc <> "" THEN
    OPEN TargetFile FOR BINARY AS #1 LEN = 32768 '
    GET$ #1, LOF(1), Buffer ' Tmp
    'Crc32_BuildTable sCrcTable, VAL(sCrc)
    'Crc = Crc32_Calculate (STRPTR(Tmp), LEN(Tmp), sCrcTable, LEN(Tmp), 1) 'why is this different
    SEEK #1, LOF(1) + 1: PUT$ #1, MKL$(Crc)
    CLOSE #1
    'END IF

    END FUNCTION
    '-------------------------------------------------------------------------------------


    ------------------
    Kind Regards
    Mike

    Leave a comment:


  • Steve Hutchesson
    replied
    Mike,

    the idea of interlocking numbers means things like window handles, instance
    handles and any other thing that you can make a mess of if you end up with
    the wrong number. Make a window handle subject to the correct number being
    added in one place and being subtracted in another, someone plays with the
    number and the handle is no longer valid so a lot of code no longer works.

    I regularly use GLOBAL handles and the hInstance so you can modify it in
    one place and see the results in completely unrelated code elsewhere. This
    is the stuff that cracking nightmares are made of. Use a call to the
    FindFirstFile() API in code that does disk access so it fits in easily and
    fill a GLOBAL structure with the needed information that will include the
    length of the running EXE file.

    Somewhere else in the code that is completely unrelated, add or subtract it
    from the instance handle then somewhere else when a function uses the instance
    handle the program will crash for no apparent reason.

    Matthew,

    The problem is one of identification and distribution, code that moves a value
    into a register is spread all through a program so if you plug the test in one
    place, evaluate it in another and display information about it somewhere else,
    it becomes very hard to track down.

    Literal strings are a serious weakness in protection systems, place the text,

    "1 h4vê c4úGT y0ú, y0ú w1Ckêd Ï1itÏ5T H4CkêR"

    in your program and the cracker tracks its address and does a global search
    of the binary code to find what is using it. The best technique is to use
    basic dynamic string and construct it in different places through the EXE
    file so that there is no single place to identify the string from.

    Regards,

    [email protected]

    ------------------

    Leave a comment:


  • Matthew Berg
    replied
    TestRegKeyFunction and the like are a signpost to the location of
    the test code,
    Code:
        If key& = OKval&
          FUNCTION = 1
        Else
          FUNCTION = 0
        End If
    Go to the end of the function on a hex editor after the program is
    decompiled, NOP out the bulk of the test and type in the opcodes
    for MOV EAX, 1 and the protection is stone dead. The 5 minute crack.
    So remove that point of failure by not using a function to perform such checks. Put the code that does the check and takes appropriate action if a problem is found into an include file, then where ever you want to perform the check, just include the file containing the check code, e.g.
    Code:
    FUNCTION DoSomething(  )
       ...
       $INCLUDE "REGCHECK.BI"
       ...
    END FUNCTION
    instead of
    Code:
    FUNCTION DoSomething(  )
       ...
       CALL TestRegKey(  )
       ...
    END FUNCTION
    Sure, you may run into "bloatware" issues (depending on how complicated/large the code for the check is), but some developers may be willing to live with that.

    If you have a multi-part key, you could have several include files (checks), and arbitrarily select which one to use when you wanted to do a check. With 101 instances where the key is checked, it should (hopefully) take a while for any cracker to track them all down.

    Leave a comment:


  • Semen Matusovski
    replied
    mark --
    this a sample of program, which tests own crc. simply run it in ide.

    but you need to use my "wrapper" for pb/dll compiler. i posted it some minutes ago in source code section.

    Code:
       #compile exe
       #dim all
       #register none
       #include "win32api.inc"
       #include "crc32.inc" ' <-- two assembler subroutines, posted in [url="http://www.powerbasic.com/support/pbforums/showthread.php?t=23005"]http://www.powerbasic.com/support/pbforums/showthread.php?t=23005[/url] 
       
       '%c &h12345678
                   
       function pbmain
          dim tmpasciiz as asciiz * 255, buffer as string
          dim daddr as dword, crc1 as long, crc2 as long, scrctable as string * 1024
                
          if isfalse(getmodulefilename(byval 0&, tmpasciiz, sizeof(tmpasciiz))) then exit function
          open tmpasciiz for binary shared as #1 len = 32768
          get$ #1, lof(1), buffer
          close #1
          
          crc2 = cvl(buffer, len(buffer) - 3)
          crc32_buildtable scrctable, &h12345678
          crc1 = crc32_calculate (strptr(buffer), len(buffer) - 4, scrctable, len(buffer) - 4, 1)
          
          daddr = crc1 - crc2 + codeptr(continue)
          goto dword daddr
          
      continue:
          msgbox "ok"
          
       end function

    ------------------
    e-mail: [email protected]

    Leave a comment:


  • Mike Trader
    replied
    Gary,
    What do mean?
    Are you saying that if I provide a valid reg it can be uploaded for others to use? I intend to prevent this by having the protection scheme seed the random number generator when the installer runs and produce a UNIQUE crypted code that the user e-mails me (along with a check).

    Each customer will get a unique number that will not work on another machine. As somone said earlier, when they reformat or upgrade hardware and re-install, I have to send them a new password, but thats fine.

    In the communication I can also inform them of other programs / Dlls i will have written in the mean time!

    Mark,
    I agree with you, but I think your points are self evident. My situation is a little different in that I am not aiming at the mass market but a thin segment of the investing community. Traders are notoriously cheap when it comes to software becuase so many "gurus" have written Cr#p and sold it for $5000.

    If they can get around my protection scheme they will. If I make it hard enough they wont. As a few people have said, there is nothing that cannot be cracked, I know that, but that does not mean that I should not try to protect it, only that I should not be upset if it is cracked.

    If I am going to protect it I want to give it a good shot. I dont need the latest and greatest, just a good shot at it. The purpose of these threads is to determine the weak points in a protection scheme and at least avoid them.

    I have learnt many tricks including, hiding files, encrypting reg data, Installers, registry entrys, CRC32 (allthough I still cant get it to work), writing to the header, adding to an exe, reading/writing to the resource, avoiding easy jumps and now file compression.

    Anyway back to the thread.
    Steve,
    By "interlocked numbers" i assume you mean test the result of each function and if it is not whats expected assume that the program has been tampered with?

    Or do you mean send an extra variable to each function call and perform some operation on it and check the return value?



    ------------------
    Kind Regards
    Mike

    Leave a comment:


  • mark smit
    Guest replied
    Hello All,

    I have seen many kinds of protection schemes out there and to be honest, they are ALL crackable. The real key to prevent software piracy is to just write applications that people really like. Instead of reasearching protection schemes for months on end why not invest that time into your application. Here is just a small list of things that will most certainly get people to legally obtain your software.

    1. (un)Installs clean and simple.
    2. Documentation, Documentation, etc...
    3. Intuative work flow. (very important, dont be afraid to make custom controls)
    4. None of this "if you do that then you can't do this".
    5. Customer service. (VERY important)
    6. Realistic prices.
    7. Throw out "No Vapor Ware".

    The last item #7 is NOT a poke at PowerBasic. I think its only fair to your potental customer base that you inform them on "what's new". This allows them to decide if what they see in your product appeals to them. Some times we are put in a position "find the best software for the job". Having some insight as to were the company is going might allow us to make better choices. People just dont like waiting for the unknown...


    So, in a nut shell, take the time you spend on "protection" and put it into making your application "better". Keep in mind this is my own personal feeling. Thanks for letting me speak my mind.


    ------------------
    Cheers!

    Leave a comment:


  • Gary Akins
    Guest replied
    You do realize, though, that all it takes is one "3733T H4XOR D00D" to register their copy under a false identity and then upload it, along with the name and serial number, to every "warez site" on the web and this whole exercise is immediately rendered academic...

    ------------------

    Leave a comment:


  • Steve Hutchesson
    replied
    Eric,

    the problems apparently were related to non-standard PE files
    but I used UPX 0.84 on PowerBASIC PE format which has never been
    a problem.

    I personally no longer use UPX because I had a very public argument
    with the authors about the unusual GNU public licence attached to
    it and the later version that prohibit post compression modification
    of the UPX stub and copyright string.

    I often use that area to write data that an EXE file can use for
    things like settings so it became unviable for me to use it any
    longer.

    Regards,

    [email protected]

    ------------------

    Leave a comment:


  • Eric Pearson
    replied
    Hutch --

    > freeware UPX version 0.84

    The download page contains some pretty strong warnings about using versions below 1.00. What kinds of runtime problems is 0.84 likely to cause?

    -- Eric



    ------------------
    Perfect Sync Development Tools
    Perfect Sync Web Site
    Contact Us: mailto:[email protected][email protected]</A>

    Leave a comment:


  • Steve Hutchesson
    replied
    Scott,

    The answer to a list of debugger footprints is what you call the
    dead listing approach, do a straight memory dump of the PE file
    to a text file, decompile it and start having PHUN.

    This will tell you where th action is in how the protection system
    works, what the problem is in knowing how and where to patch the
    existing file and it is here where a compressed EXE/DLL becomes
    a real pain.

    If you have a look at the structure of a compressed EXE/DLL, you
    cannot patch it in its compressed state so it must be decompressed,
    patched then recompressed which is not a simple task, especially
    when the compressor seriously messes up the original section data.

    To be comprehensive, there is another approach to protect against,
    it is the "in memory" patcher which does modification of the loaded
    image of the PE file. This type is a lot harder to beat and your best
    weapon against it is complexity and interlocked numbers to component
    parts of the program.

    I think if Mike builds his DLL with the tricks he has in hand, it will
    slow down all but the highest of skill crackers and they usually do not
    bother with dedicated DLLs for an application that few would use.

    A few basic rules, NEVER, NEVER ,NEVER put a test in an EXPORT
    function from a DLL and also don't name it anything understandable.

    TestRegKeyFunction and the like are a signpost to the location of
    the test code,
    Code:
        If key& = OKval&
          FUNCTION = 1
        Else
          FUNCTION = 0
        End If
    Go to the end of the function on a hex editor after the program is
    decompiled, NOP out the bulk of the test and type in the opcodes
    for MOV EAX, 1 and the protection is stone dead. The 5 minute crack.

    Tests that involve jumps take an extra 5 minutes. Your only chance is
    to spread out the information and lock it into the critical numbers
    within the program.

    Regards,

    [email protected]

    ------------------

    Leave a comment:


  • Semen Matusovski
    replied
    1) If a program changes itself, it's very easy to detect by simple comparasion and to restore previous release before start.
    The same, if program writes to external file or registry.
    2) If a program tests "expirience" date, it's very easy to write simple program, which changes a date before start and restores it.

    It's not necessary to be a professional cracker to do such simple things ...

    Also note that ckrackers are crazy boys. If you will use good protection and will inform that a program works in demo-mode, they really will be interesting to broke similar program.
    For examole, well-known file manager FAR. In russian licence text there is a comment - freeware for xUSSR users and explanation, how to enter a password (russian text, current day of week).
    And what do you think ? After two weeks appears a patch, which allows to enter any password.

    Maybe there are reasons to do hidden "hooks". For example, if "CRC" is wrong, you do not inform "demo-release".
    Instead of this you simply use somewhere in calculations "CRC" de-facto - CRC expected instead of 0.

    ------------------
    E-MAIL: [email protected]

    Leave a comment:


  • Mike Trader
    replied
    I d/l the File compressor at: http://wildsau.idv.uni-linz.ac.at/mf...IONS/obsolete/
    It works great. In fact i can still add a LONG to the header at address &H 20 after the BEST compression. So that works great

    I wondered what the advantages of compressed exe's were.

    ------------------
    Kind Regards
    Mike

    Leave a comment:


  • Scott Turchin
    replied
    Wayne, he needs help, can ya hook him up with the CRC Writing function?


    Basically, crack my program, go for it:

    If IsFalse CRC32Verify Then Exit Function


    I've tried......it ain't cracking...


    The EXe must be manually CRC encoded AFTER compiling...
    The CRC is stored somewhere in the EXE....

    It's about as good as I can get besides the 50+ known debuggers that I scan for but we won't go there, it's 4 pages of code and Wayne and i worked a LOT of hours on that, very very complex....

    Wayne, getting an idea now? ($$)...


    ------------------
    Scott

    Leave a comment:


  • Scott Turchin
    replied
    Nice try, honestly!
    It will only slow them down however!

    I put 5 function calls in whiel testing my app with a tester, sure it locked up his machine the first 3 times, 2 hours later he cracked it and it ws pretty complex..

    There is yet another flag you can put in, decrypt something and take a value..
    If that Value is not equal to the decrypted value (Now they have to decrypt your encryption scheme) then blow up or exit....


    Start thinking along those lines too, but keep in mind, they can JMP right over that too..


    Best thing, remove that code, cant' crack what ain't there!


    Scott

    ------------------
    Scott

    Leave a comment:

Working...
X