bart1e answered your question right
this is just an extension to his answer with a different tool ghidra and reversing purely statically without running the binary.
golang uses a different approach to calling functions it provides a memory slot in stack for arguments to the functions as well as multiple values it can return from a function
for example a function can return a sum and product of two integers in the same function like
func doMagic (x,y) { return x+y ,x*y )
so roughly the disassembly will look like
( it is just a simplification of details not absolute syntax or exact types are used )
it just tries to reiterate how golang uses stack
instead of traditional x64 abi registers or x86 abi memory / stack for arguments and conventional
returns in rax/eax
mov [stack] , x
mov [stack] , y
mov [stack] , &fret1
mov [stack] , &fret2
call doMagic
and inside doMagic
t1 = x
t2 = y
t3 = t1 + t2
t4 = t1 * t2
fret1 = t3
fret2 = t4
ret
so reading around I found golang embeds the function names and its address in .gopclntab section and was fooling around with renaming the functions and successfully renamed the functions with this hack of script below but the decompilation was still looking a horrible mess
from ghidra import *
mem = currentProgram.getMemory()
start = mem.getBlock(".gopclntab").getStart()
provider = ghidra.app.util.bin.MemoryByteProvider(mem,start)
bread = ghidra.app.util.bin.BinaryReader(provider,True)
numfun = bread.readInt(8)
for i in range(0x10,numfun*0x10,0x10):
faddr = bread.readInt(bread.readInt(i+8))
fname = bread.readAsciiString(bread.readInt(bread.readInt(i+8) + 8))
print( "creating function at " + hex(faddr) + "\t" + fname )
removeFunctionAt(toAddr(faddr))
createFunction(toAddr(faddr),fname)
so i scoured around and hit upon a GitHub entry by felberj a ghidra script for golang
downloaded the script for 9.04 version copied it to ghidra/extension dir as instructed
in the project window did File->installExtension and restarted ghidra
now i imported the binary again this time making sure i used the newly added golang language entry instead of default gcc x64 ghidra proposes
and apart from renaming functions this extension also decoded the return types and changed the storage type of arguments to custom storage and assigned proper stack address to all the arguments and return values.
now the decompilation of main.main was looking a bit more readable
void main.main(void)
{
byte Wrong [17];
byte EPas [20];
byte good [30];
byte Ans [38];
byte retry [40];
ppbVar1 = *(in_FS_OFFSET + 0xfffffff8) + 0x10;
if (good + 4 < *ppbVar1 || good + 4 == *ppbVar1) {
runtime.morestack_noctxt();
main.main();
return;
}
EPas._0_8_ = 0xc5d38ad8cfdec4ef;EPas._8_4_ = 0xda8ad8df;EPas._12_4_ = 0xddd9d9cb;
EPas._16_4_ = 0x90ced8c5;
Wrong[0] = 0xf9; Wrong._1_4_ = 0xc2dec7c5;Wrong._5_4_ = 0x8acdc4c3;Wrong._9_4_ = 0xdd8ad9c3;
Wrong._13_4_ = 0xcdc4c5d8;
Ans._0_6_ = 0xd9cfcec3f9e8; Ans._6_2_ = 0xe6fe; Ans._8_2_ = 0xd1fc; Ans._10_4_ = 0xcfdccfd8;
Ans._14_4_ = 0x8acfcdc4; Ans._18_4_ = 0xc88ad9c3; Ans._22_4_ = 0x8aded9cf; Ans._26_4_ = 0xdcd8cfd9;
Ans._30_4_ = 0xc98acecf; Ans._34_4_ = 0xd7cec6c5;
good._0_4_ = 0x8adfc5f3; good._4_4_ = 0xc9cbd8e9; good._8_4_ = 0x8acecfc1; good._12_2_ = 0xdec3;
good._14_2_ = 0x8a86; good._16_2_ = 0x8aeb; good._18_4_ = 0xc5d8cfe2; good._22_4_ = 0x8ad9c38a;
good._26_4_ = 0xc4d8c5c8;
retry._0_8_ = 0xc5fd8ade8dc4c5ee; retry._8_4_ = 0x86d3d8d8; retry._12_4_ = 0xc6cff88a;
retry._16_4_ = 0x8a86d2cb; retry._20_4_ = 0xc6c3c2e9; retry._24_4_ = 0xc4cb8ac6;
retry._28_4_ = 0xd8fe8ace; retry._32_4_ = 0xcbc28ad3; retry._36_4_ = 0xd8cfced8;
uStack224 = 0x14;
rVar3 = main.ObfStr(EPas,0x14,0x14);
uStack0000000000000020 = SUB168(rVar3,0);
uStack0000000000000028 = SUB168(rVar3 >> 0x40,0);
uStack0000000000000018 = runtime.convTstring(in_stack_fffffffffffffe10,in_stack_fffffffffffffe18);
pdStack232 = &DAT_0049a600;
puVar7 = 0x1;
lVar9 = 1;
fmt.Fprint(&PTR_DAT_004d3a60,DAT_0055b7f0,&pdStack232,1,1);
uStack152 = DAT_0055b7e8;
apuStack96[0] = 0x0;
FUN_00451d15();
lVar5 = 0x1000;
lVar6 = 0x1000;
runtime.makeslice(&DAT_0049a740,0x1000,0x1000);
puStack184 = 0x0;
puVar8 = puVar7;
FUN_00451d15();
uStack176 = 0x1000;
uStack168 = 0x1000;
ppuStack160 = &PTR_DAT_004d3a40;
uStack112 = 0xffffffffffffffff;
uStack104 = 0xffffffffffffffff;
puStack184 = puVar7;
apuStack96[0] = puVar7;
FUN_0045207a();
rVar2 = bufio.(*Reader).ReadLine(apuStack96);
uStack0000000000000010 = SUB488(rVar2,0);
uStack0000000000000018 = SUB488(rVar2 >> 0x40,0);
uStack0000000000000020 = SUB488(rVar2 >> 0x80,0);
uStack0000000000000028 = SUB488(rVar2 >> 0xc0,0);
if (lStack480 != 0) {
uStack208 = 0x11;
rVar3 = main.ObfStr(Wrong,0x11,0x11);
uStack0000000000000020 = SUB168(rVar3,0);
uStack0000000000000028 = SUB168(rVar3 >> 0x40,0);
uStack0000000000000018 = runtime.convTstring(puVar8,lVar9);
if (lStack480 != 0) {
lStack480 = *(lStack480 + 8);
}
pdStack216 = &DAT_0049a600;
puVar8 = 0x2;
lVar9 = 2;
lStack200 = lStack480;
rVar4 = fmt.Fprintln(&PTR_DAT_004d3a60,DAT_0055b7f0,&pdStack216,2,2);
uStack0000000000000040 = SUB248(rVar4 >> 0x80,0);
}
rVar3 = main.ObfStr(Ans,0x26,0x26);
uStack0000000000000020 = SUB168(rVar3,0);
uStack0000000000000028 = SUB168(rVar3 >> 0x40,0);
if ((lVar9 == lVar6) &&
(uStack0000000000000020 = runtime.memequal(lVar5,puVar8,lVar6), puVar8 != '\0')) {
uStack240 = 0x1e;
rVar3 = main.ObfStr(good,0x1e,0x1e);
uStack0000000000000020 = SUB168(rVar3,0);
uStack0000000000000028 = SUB168(rVar3 >> 0x40,0);
uStack0000000000000018 = runtime.convTstring(puVar8,lVar9);
pdStack248 = &DAT_0049a600;
fmt.Fprintln(&PTR_DAT_004d3a60,DAT_0055b7f0,&pdStack248,1,1);
return;
}
uStack256 = 0x28;
rVar3 = main.ObfStr(retry,0x28,0x28);
uStack0000000000000020 = SUB168(rVar3,0);
uStack0000000000000028 = SUB168(rVar3 >> 0x40,0);
uStack0000000000000018 = runtime.convTstring(puVar8,lVar9);
pdStack264 = &DAT_0049a600;
fmt.Fprintln(&PTR_DAT_004d3a60,DAT_0055b7f0,&pdStack264,1,1);
return;
}
so as you can see it prints a string Enter password
reads a line
compares the entered pass with a obfuscated (simple xor with byte 0xaa ) actual password
and prints good , you are a hero
or prints wrong retry
the deobfuscation routine is main.obfstr() it takes a bytearray and length to xor
and returns a xorred string back
based on these observations we can write a simple xor script
that takes an address the byte to xor and length and xor the results to get the pass without running the binary
here is naive xor script (naïve because it ran for me the one time i tested in my machine and can have innumerable unanticipated corner case bugs)
#Desc xors a memory block of len unsigned bytes with a single unsigend byte like (0xff ^ 0xaa)
#@author blabb
#@category _NEW_
#@keybinding
#@menupath none
#@toolbar
import ghidra
def hexdump( a ):
for j in range(0,len(a),16):
for i in range(j,j+16,1):
if( i < len(a)):
print ( "%02x " % a[i]),
else:
print ( "%02x " % 0 ),
for i in range(j,j+16,1):
if( i < len(a)):
print ( "%c" % chr( a[i] ) ),
else:
print ( " " ),
print("\n")
baseaddr = askAddress( "XOR MEMORY","Enter Base Addreess")
xorby = askInt ( "XOR MEMORY","Enter Byte to xor with")
xorlen = askInt ( "XOR MEMORY","enter length of xor block")
res = []
provider = ghidra.app.util.bin.MemoryByteProvider(currentProgram.getMemory(),baseaddr)
br = ghidra.app.util.bin.BinaryReader(provider,True)
for i in range(0,xorlen,1):
res.append((br.readUnsignedByte(i) ^ xorby))
hexdump(res)
running this and providing the memory address for password 0x4d3ae0,xorbyte 0xaa,len 0x26
we can easily get the password
xormem.py> Finished!
xormem.py> Running...
42 53 69 64 65 73 54 4c 56 7b 72 65 76 65 6e 67 B S i d e s T L V { r e v e n g
65 20 69 73 20 62 65 73 74 20 73 65 72 76 65 64 e i s b e s t s e r v e d
20 63 6f 6c 64 7d 00 00 00 00 00 00 00 00 00 00 c o l d }
xormem.py> Finished!