Jump to content

[TOPIC: topicViewTemplate]
[GLOBAL: userSmallPhoto]
Photo

General Mobile Game/App questions
Started by coronasdk66 Jan 22 2020 01:12 PM

- - - - -
33 replies to this topic
[TOPIC CONTROLS]
Page 1 of 2 1 2
[/TOPIC CONTROLS]
[modOptionsDropdown]
[/modOptionsDropdown]
[reputationFilter]
[TOPIC: post.html]
#1

coronasdk66

[GLOBAL: userInfoPane.html]
coronasdk66
  • Enthusiast

  • 33 posts
  • Corona SDK

As a new game publisher prepping to launch our first title, I have some questions for those who have been in the business for awhile. This set will be the first of a series, and my hope is that by asking here in this forum, it will help not only me, but also others who are looking to launch.

 

1) We have implemented storage of user data (like high score, whether a game has been purchased or if it is the free version with ads, badges won, etc.) in a file which also includes hashes (actually long checksums - see #3 below) for each value. This (theoretically) will tell us if a user has manually modified the data in an attempt to cheat, or to get the full game without purchasing it. My question: If I discover that the user data no longer matches the hashes, what is the best practice at that point? Should we stop the game and tell them that they must uninstall/reinstall? Should we re-initialize the data as though they have started fresh? Should we display a message stating that the data is 'corrupt'?

 

2) Are there any common/best practices in Corona-land for obfuscation or reverse engineering protection?

 

3) We purposely avoided using any encryption libraries so that we did not have to go through any additional 'hoops' when we're ready to release the app. Given that, how can we protect things like keys that are present in the code? (e.g. things like IDs and keys for GameAnalytics, etc.). Because they are in code as literal strings won't they be readily visible in the app code installed on the device (if someone is dumping/rooting)?

 

Thanks in advance for any insights.

 

 



[TOPIC: post.html]
#2

Quantumwave

[GLOBAL: userInfoPane.html]
Quantumwave
  • Contributor

  • 132 posts
  • Corona SDK

I'd answer #2 & 3:

 

This is something I've been working on, thought I'd test the market and see if it's something other developers would invest in:

 

https://ko-fi.com/luaprotect

 

Yes, unprotected Lua source code is basically open to anyone with a decompiler, the keys and IDs are visible as well. It is the reason why I started this project.

 

Dave


  • ldurniat, coronasdk66, sporkfin and 2 others like this

[TOPIC: post.html]
#3

coronasdk66

[GLOBAL: userInfoPane.html]
coronasdk66
  • Enthusiast

  • 33 posts
  • Corona SDK

Dave, thanks! This looks really cool and I will definitely be looking into it. One question (and please forgive if this is too basic but as mentioned - we are new to mobile app dev)... since your product mentions AES-256 encryption, won't that make it fall under Apple's requirements as they apply to distribution of apps with encryption? It seems to me that I read somewhere (early in this journey) that it can be sometimes difficult to get your app through that process. Thoughts?



[TOPIC: post.html]
#4

Quantumwave

[GLOBAL: userInfoPane.html]
Quantumwave
  • Contributor

  • 132 posts
  • Corona SDK

I did look into the topic of exporting encrypted information a few months ago and got the answer that protecting one's source code does not fall into the export laws. Since I'm not a lawyer, I suggest others do their own research in this regard.

 

As for whether Apple allows apps with encryption, there must be thousands of apps using password-protected zipped files or other proprietary file formats, my Encrypted Lua Bytecode (ELB) file bundle is no different.

 

Dave


  • coronasdk66 likes this

[TOPIC: post.html]
#5

XeduR @Spyric

[GLOBAL: userInfoPane.html]
XeduR @Spyric
  • Corona Geek

  • 1,176 posts
  • Corona SDK

Yeah, you can freely protect your own assets. For instance, encrypting your local save files is almost a necessity if you don't want players to have an easy access to cheat (or gain free IAPs if you store their associated data locally). The issues arise if and when you begin to create messaging apps with end-to-end encryption, apps that generally deal with encryption or encrypt external files, etc.

 

That being said, I look forward to seeing how Dave's project progresses. 


  • Quantumwave and coronasdk66 like this

[TOPIC: post.html]
#6

XeduR @Spyric

[GLOBAL: userInfoPane.html]
XeduR @Spyric
  • Corona Geek

  • 1,176 posts
  • Corona SDK

Also, to your #1 question. One simple option is to just let your app crash.

If you have a simple conditional check to see if a value has been altered, then simply load the value if it hasn't been tampered with, otherwise let the value be nil, at which point it'll crash the app. It's low cost, easy to implement and you don't risk introducing any bugs via all sorts of fancy checks.


  • coronasdk66 likes this

[TOPIC: post.html]
#7

sporkfin

[GLOBAL: userInfoPane.html]
sporkfin
  • Contributor

  • 780 posts
  • Corona SDK

Great question!  There have been some lively debates about this over the years.  Anyone with enough skill and enough time will eventually be able to mess around with your code.  Personally, I would try to make it annoying enough to decode and recode your work that someone with a skill level high enough to eventually succeed would likely have better things to do with their time.  

 

I think @roaminggamer had a similar philosophy and used to offer some code obfuscation for a fee.  Maybe he still does that?

 

Please share your app when you are finished. It is always inspiring to see what other people are working on!


  • coronasdk66 likes this

[TOPIC: post.html]
#8

sporkfin

[GLOBAL: userInfoPane.html]
sporkfin
  • Contributor

  • 780 posts
  • Corona SDK

@Dave, I love your project.  I sent you some coffee for now to keep you going and plan to buy Lua Protect when it is released.


  • coronasdk66 likes this

[TOPIC: post.html]
#9

coronasdk66

[GLOBAL: userInfoPane.html]
coronasdk66
  • Enthusiast

  • 33 posts
  • Corona SDK

@Dave, I love your project.  I sent you some coffee for now to keep you going and plan to buy Lua Protect when it is released.

 

I did, too! It looks very interesting.  :-)



[TOPIC: post.html]
#10

sporkfin

[GLOBAL: userInfoPane.html]
sporkfin
  • Contributor

  • 780 posts
  • Corona SDK

@coronasdk66 - How will you keep your checksums from being hacked?  Is the info going through your server?



[TOPIC: post.html]
#11

coronasdk66

[GLOBAL: userInfoPane.html]
coronasdk66
  • Enthusiast

  • 33 posts
  • Corona SDK

We are using a modified version of GGData (by Graham Ranson of Glitch Games) that generates a checksum for a value based on the value salted (prepended) with two GUIDs (72 bytes). Assuming that we can keep the two GUIDs secret (which is part of the reason behind my original question #3) this should be reasonably difficult to defeat.

 

[edit] Of course now that I've put this out there, someone will probably poke holes in the concept, which I happily and heartily welcome!  :-)



[TOPIC: post.html]
#12

XeduR @Spyric

[GLOBAL: userInfoPane.html]
XeduR @Spyric
  • Corona Geek

  • 1,176 posts
  • Corona SDK

Basic Lua obfuscation and bytecode encryption is relatively simple to do, but I'm personally not completely aware as to how easy or difficult they are to undo and whether or not they have a negative impact on performance (and if so, then how bad). When it really comes to any code that the user can get their hands on, there's no such thing as real security.

 

Still, I'm looking forward to testing out Dave's project, especially concerning asset encryption. One thing that I'd like to see in Corona, even without any real encryption or other significant protection measures, would be a resource file for Win32 and MacOS. It's both messy and uncomfortable to have all your sfx and image files freely available there without any sort of "containment". Most licensed music and sfx that I've run into have clauses that prohibit distributing the music and sfx files by themselves or in projects where they can just be copied by good old fashioned ctrl+c and ctrl+v.


  • coronasdk66 and pixec like this

[TOPIC: post.html]
#13

coronasdk66

[GLOBAL: userInfoPane.html]
coronasdk66
  • Enthusiast

  • 33 posts
  • Corona SDK

Thanks (all) for the input. As it pertains to my question #2, I agree. I'm not looking for (or expecting) complete protection, just looking for simple/easy things to do that increase the difficulty of snooping around.



[TOPIC: post.html]
#14

sporkfin

[GLOBAL: userInfoPane.html]
sporkfin
  • Contributor

  • 780 posts
  • Corona SDK

Here is one of the older posts on this topic you might find interesting


  • coronasdk66 likes this

[TOPIC: post.html]
#15

Quantumwave

[GLOBAL: userInfoPane.html]
Quantumwave
  • Contributor

  • 132 posts
  • Corona SDK

Thanks guys for the support so far!

 

I started a quick poll to see what's more important for you in protecting your app:

 

https://twitter.com/lua_protect/status/1220447701650890780?s=20


  • coronasdk66 likes this

[TOPIC: post.html]
#16

XeduR @Spyric

[GLOBAL: userInfoPane.html]
XeduR @Spyric
  • Corona Geek

  • 1,176 posts
  • Corona SDK

Oh, right. There was one additional thing I wanted to say about using hashes versus encryption to protect your local save data.

 

Let's consider that someone is trying to hack your system, but they have not yet gained access to your source code. In this case, both hashes and encrypted data files can be used to prevent tampering with the save data, however, if the save data isn't encrypted then the hacker can still read what information is being stored in the file(s).

 

Now, the moment that a hacker gains access to your source code, i.e. they know what they are doing, then both measures fall flat because they can simply read how you create your hashes or what encryption you are using and what your keys are. Accessing source code could be made somewhat more difficult via obfuscation and other measures, but there's some debate as to how effective they are.

 

While we wait for Dave's approach, the most secure way to prevent cheating or cloning of your apps is to create online games where certain critical functions are run on your server. For instance, if you had a base building game, you'd send your player's desired actions to the server, such as "build a house to grid {x,y}". Then your server side script would look at this specific player's base layout to see if 1) the area is free, 2) if the player has sufficient funds, 3) if all other requirements are met, and then the server side script would make the required changes to the player's data (which is all on the server) and it'd only return "okay, build it" command back. Even if a cheater could make some local changes, they'd all be blocked or ignored by the server and cleared up by the next start up and if someone tried to clone your game, they'd lack the logic behind executing player commands.


  • coronasdk66 and pixec like this

[TOPIC: post.html]
#17

coronasdk66

[GLOBAL: userInfoPane.html]
coronasdk66
  • Enthusiast

  • 33 posts
  • Corona SDK

Thanks.

 

I would agree with this, and our game does use the server for multi-player games. I've struggled with this for a few months now... in order to do the record-keeping on our server (which I am very comfortable with) that means that even a casual user would have to establish an account (register) and I feel that is a significant barrier for some who just wants to get in and play single-player games for fun and then get out.

 

Maybe I should adjust my thinking. These are the sorts of conversations I've been needing. I can do the programming with no problem, but there are so many non-programming aspects to launching a mobile game/app that I sometimes feel like I am flying blind, especially since this is our first major attempt.

 

Perhaps I will adjust our game so that no server involvement and no registration is required unless the player wants to appear on leaderboards (and of course if you want to play multi-player).

 

Thoughts? Is that maybe a better approach?



[TOPIC: post.html]
#18

XeduR @Spyric

[GLOBAL: userInfoPane.html]
XeduR @Spyric
  • Corona Geek

  • 1,176 posts
  • Corona SDK

While were on this topic, I thought to google a few Lua obfuscation scripts and see how they do and how much difficulty I would have in "breaking" them.

 

The first obfuscator that I found simply converted the text into bytecode, i.e.

local thing = [[
function secret()
  print( "no one will ever find me" )
end
]]

-- Obfuscation function source: https://glot.io/snippets/f1tt9okm5w
local encoded = thing:gsub(".", function(bb) return "\\" .. bb:byte() end) or thing .. "\""
print( encoded )

-- Output: \102\117\110\99\116\105\111\110\32\115\101\99\114\101\116\40\41\10\9\112\114\105\110\116\40\32\34\110\111\32\111\110\101\32\119\105\108\108\32\101\118\101\114\32\102\105\110\100\32\109\101\34\32\41\10\101\110\100\10

Then I thought, "Alright, so how to revert it?" and here's what I came up with in around 5 minutes.

local function bytecodeToString( code )
  local t, a = {}, 1
  while true do
    if code:find( "\\", a ) then
      local b = code:find( "\\", a+1 )

      if b then
        t[#t+1] = code:sub(a+1,b-1):char()
        a = b
      else
        t[#t+1] = code:sub(a+1):char()
        break
      end
    else
      break
    end
  end

  return table.concat( t )
end

local decoded = bytecodeToString( encoded )
print( decoded )

Well, granted, that obfuscation function wasn't all that difficult to break, so I found another one to try as well.

-- LuaSeel, source: https://github.com/Direnta/LuaSeel
local a=[[
function superSecret()
  print( "this time no one will ever find me" )
end
]]

a="--// Decompiled Code. \n"..a;function Obfuscate(b)local c="function IllIlllIllIlllIlllIlllIll(IllIlllIllIllIll) if (IllIlllIllIllIll==(((((919 + 636)-636)*3147)/3147)+919)) then return not true end if (IllIlllIllIllIll==(((((968 + 670)-670)*3315)/3315)+968)) then return not false end end; "local d=c;local e=""local f={"IllIllIllIllI","IIlllIIlllIIlllIIlllII","IIllllIIllll"}local g=[[local IlIlIlIlIlIlIlIlII = {]]local h=[[local IllIIllIIllIII = loadstring]]local i=[[local IllIIIllIIIIllI = table.concat]]local j=[[local IIIIIIIIllllllllIIIIIIII = "''"]]local k="local "..f[math.random(1,#f)].." = (7*3-9/9+3*2/0+3*3);"local l="local "..f[math.random(1,#f)].." = (3*4-7/7+6*4/3+9*9);"local m="--// Obfuscated with LuaSeel 1.1 \n\n"for n=1,string.len(b)do e=e.."'\\"..string.byte(b,n).."',"end;local o="function IllIIIIllIIIIIl("..f[math.random(1,#f)]..")"local p="function "..f[math.random(1,#f)].."("..f[math.random(1,#f)]..")"local q="local "..f[math.random(1,#f)].." = (5*3-2/8+9*2/9+8*3)"local r="end"local s="IllIIIIllIIIIIl(900283)"local t="function IllIlllIllIlllIlllIlllIllIlllIIIlll("..f[math.random(1,#f)]..")"local q="function "..f[math.random(1,#f)].."("..f[math.random(1,#f)]..")"local u="local "..f[math.random(1,#f)].." = (9*0-7/5+3*1/3+8*2)"local v="end"local w="IllIlllIllIlllIlllIlllIllIlllIIIlll(9083)"local x=m..d..k..l..i..";"..o.." "..p.." "..q.." "..r.." "..r.." "..r..";"..s..";"..t.." "..q.." "..u.." "..v.." "..v..";"..w..";"..h..";"..g..e.."}".."IllIIllIIllIII(IllIIIllIIIIllI(IlIlIlIlIlIlIlIlII,IIIIIIIIllllllllIIIIIIII))()"print(x)end;do Obfuscate(a)end

Well, now we've got something interesting! The GitHub page lists its features as: "Math value algorithms, Variable obfuscator/changer, Bytecode encryption, Randomized strings - credit to SadBoy22", so now we are probably dealing with some serious shite... but let's give it a go!

 

The obfuscated/encrypted code is as follows:

--// Obfuscated with LuaSeel 1.1 

function IllIlllIllIlllIlllIlllIll(IllIlllIllIllIll) if (IllIlllIllIllIll==(((((919 + 636)-636)*3147)/3147)+919)) then return not true end if (IllIlllIllIllIll==(((((968 + 670)-670)*3315)/3315)+968)) then return not false end end; local IIlllIIlllIIlllIIlllII = (7*3-9/9+3*2/0+3*3);local IIlllIIlllIIlllIIlllII = (3*4-7/7+6*4/3+9*9);local IllIIIllIIIIllI = table.concat;function IllIIIIllIIIIIl(IIllllIIllll) function IIlllIIlllIIlllIIlllII(IIlllIIlllIIlllIIlllII) function IllIllIllIllI(IIlllIIlllIIlllIIlllII) end end end;IllIIIIllIIIIIl(900283);function IllIlllIllIlllIlllIlllIllIlllIIIlll(IllIllIllIllI) function IllIllIllIllI(IIlllIIlllIIlllIIlllII) local IIllllIIllll = (9*0-7/5+3*1/3+8*2) end end;IllIlllIllIlllIlllIlllIllIlllIIIlll(9083);local IllIIllIIllIII = loadstring;local IlIlIlIlIlIlIlIlII = {'\45','\45','\47','\47','\32','\68','\101','\99','\111','\109','\112','\105','\108','\101','\100','\32','\67','\111','\100','\101','\46','\32','\10','\102','\117','\110','\99','\116','\105','\111','\110','\32','\115','\117','\112','\101','\114','\83','\101','\99','\114','\101','\116','\40','\41','\10','\32','\32','\112','\114','\105','\110','\116','\40','\32','\34','\116','\104','\105','\115','\32','\116','\105','\109','\101','\32','\110','\111','\32','\111','\110','\101','\32','\119','\105','\108','\108','\32','\101','\118','\101','\114','\32','\102','\105','\110','\100','\32','\109','\101','\34','\32','\41','\10','\101','\110','\100','\10',}IllIIllIIllIII(IllIIIllIIIIllI(IlIlIlIlIlIlIlIlII,IIIIIIIIllllllllIIIIIIII))()

-- Well, that's all fine and good, but all we are interested in are the bytes, so let's just copy and paste them into print()

print( "'\45','\45','\47','\47','\32','\68','\101','\99','\111','\109','\112','\105','\108','\101','\100','\32','\67','\111','\100','\101','\46','\32','\10','\102','\117','\110','\99','\116','\105','\111','\110','\32','\115','\117','\112','\101','\114','\83','\101','\99','\114','\101','\116','\40','\41','\10','\32','\32','\112','\114','\105','\110','\116','\40','\32','\34','\116','\104','\105','\115','\32','\116','\105','\109','\101','\32','\110','\111','\32','\111','\110','\101','\32','\119','\105','\108','\108','\32','\101','\118','\101','\114','\32','\102','\105','\110','\100','\32','\109','\101','\34','\32','\41','\10','\101','\110','\100','\10'" )

-- Output:
'-','-','/','/',' ','D','e','c','o','m','p','i','l','e','d',' ','C','o','d','e','.',' ','
','f','u','n','c','t','i','o','n',' ','s','u','p','e','r','S','e','c','r','e','t','(',')','
',' ',' ','p','r','i','n','t','(',' ','"','t','h','i','s',' ','t','i','m','e',' ','n','o',' ','o','n','e',' ','w','i','l','l',' ','e','v','e','r',' ','f','i','n','d',' ','m','e','"',' ',')','
','e','n','d','
'

Well, that doesn't look all that obfuscated or encrypted after all. All that we need to do now is get every fourth character, starting from the second character and we'll get:

-- This function took between 1 and 2 minutes to write.
local function bytecodeToStringWithAwesomeSauce( code )
  local t = {}
  for i = 2, code:len(), 4 do
    t[#t+1] = code:sub(i,i)
  end
  return table.concat( t )
end

local decoded = bytecodeToStringWithAwesomeSauce( "'\45','\45','\47','\47','\32','\68','\101','\99','\111','\109','\112','\105','\108','\101','\100','\32','\67','\111','\100','\101','\46','\32','\10','\102','\117','\110','\99','\116','\105','\111','\110','\32','\115','\117','\112','\101','\114','\83','\101','\99','\114','\101','\116','\40','\41','\10','\9','\112','\114','\105','\110','\116','\40','\32','\34','\116','\104','\105','\115','\32','\116','\105','\109','\101','\32','\110','\111','\32','\111','\110','\101','\32','\119','\105','\108','\108','\32','\101','\118','\101','\114','\32','\102','\105','\110','\100','\32','\109','\101','\34','\32','\41','\10','\101','\110','\100','\10'" )
print( decoded )

-- Output:
--// Decompiled Code. 
function superSecret()
 print( "this time no one will ever find me" )
end

The point is that I've spent a lot longer to write this lengthy post than I did to create two different functions to undo obfuscation. Obfuscation by itself isn't worth it, which is why I am interested in seeing what Dave has planned.


  • sporkfin and pixec like this

[TOPIC: post.html]
#19

SGS

[GLOBAL: userInfoPane.html]
SGS
  • Corona Geek

  • 2,205 posts
  • Corona SDK

This is exactly what I do.... encrypt local save (AES-256) and mirror to my servers.

TBH it is not even worth trying to protect the game data.  Players that want to "hack" will simply max out on fake purchases.

 

I just let them do it and ban them from all internet features so no-one can see what they have done.

 

The other hack that is so easy to do is in memory value changing.  Say player has 199 coins, they simple search all memory addresses containing 199 and change to 9999999999999.

 

In my experience this is very much less than 1% and certainly from players you will never monetize. So don't waste your time.


  • coronasdk66, sporkfin and XeduR @Spyric like this

[TOPIC: post.html]
#20

pixec

[GLOBAL: userInfoPane.html]
pixec
  • Contributor

  • 170 posts
  • Corona SDK

lol :D I was just looking how to obfuscste my code and then I find a post where the exact function I was using was broken in minutes! I guess it is pointless :D

[TOPIC: post.html]
#21

coronasdk66

[GLOBAL: userInfoPane.html]
coronasdk66
  • Enthusiast

  • 33 posts
  • Corona SDK

BTW, this is a great discussion. Thanks to all who are participating.

 

To be clear, I am not that worried or interested in obfuscating the SOURCE code, since I'm not distributing the source. Wait... that IS correct, isn't it? If I do a build in Corona it's actually compiling to something binary or machine code, right? Right?

 

Holy Schnikes... if that assumption is incorrect then I must take a big step back to reevaluate.   :blink: 



[TOPIC: post.html]
#22

pixec

[GLOBAL: userInfoPane.html]
pixec
  • Contributor

  • 170 posts
  • Corona SDK

Lua is compiled into bytecode when built and that can be easily extracted from apks.

[TOPIC: post.html]
#23

Quantumwave

[GLOBAL: userInfoPane.html]
Quantumwave
  • Contributor

  • 132 posts
  • Corona SDK

To be clear, I am not that worried or interested in obfuscating the SOURCE code, since I'm not distributing the source. Wait... that IS correct, isn't it? If I do a build in Corona it's actually compiling to something binary or machine code, right? Right?

 

Holy Schnikes... if that assumption is incorrect then I must take a big step back to reevaluate.   :blink: 

 

Corona compiles your Lua source code into standard Lua bytecode. There are decompilers and programs that revert the bytecode back to Lua. With Lua Protect, the bytecode is encrypted along with a few other security measures, so it is much harder to revert it.

 

Dave



[TOPIC: post.html]
#24

SGS

[GLOBAL: userInfoPane.html]
SGS
  • Corona Geek

  • 2,205 posts
  • Corona SDK

I've had to DMCA for ripped assets but never for code.  Concentrate on what needs protecting.



[TOPIC: post.html]
#25

coronasdk66

[GLOBAL: userInfoPane.html]
coronasdk66
  • Enthusiast

  • 33 posts
  • Corona SDK

Thanks! I will dig into this just a bit more, then. I am used to decompilers/disassemblers that revert back to 'a version of the source code that operates identically' but not the exact same source code (e.g. variables have generic names, comments are gone, some optimizations may have occurred, etc.)

 

Dave, let me (us?) know if you need beta testers when the time is right.  




[topic_controls]
Page 1 of 2 1 2
 
[/topic_controls]