Murga-Projects Forums
Steganography - Printable Version

+- Murga-Projects Forums (http://www.murga-projects.com/forum)
+-- Forum: Project Forums (/forumdisplay.php?fid=1)
+--- Forum: MurgaLua - General (/forumdisplay.php?fid=2)
+--- Thread: Steganography (/showthread.php?tid=376)


Steganography - mikshaw - 04-10-2010 08:26 AM

While watching Along Came a Spider last night on the telly, I noticed the school kids were playing with a "stego" program to embed and encrypt secret messages into image files. I'd done embedded zip files before, but thought this was a more interesting and secure alternative. After a bit of Google I found the commandline cross-platform program Steghide. So now here's a limited GUI for it.

The limitations are:
1) Embedding only (extraction will probably be added later)
2) JPEG container only (personal preference, unlikely to change)
3) No support for most options (will be improved later)
4) No way to change the image after it's loaded (will probably be fixed)

An image file can be supplied on the commandline (or, i assume, drag-and-drop onto the script's desktop icon). Otherwise a file chooser opens automatically.

I'm guessing this version will probably not work in Windows for now. Also obviously needs a lot of tweaks and fail-safe functions.

Code:
#!/usr/bin/env murgaLua

-- GUI for steghide <steghide.sourceforge.net>
-- function is limited to embedding data into jpg
-- 2010 mikshaw
-- version 0

if not arg[1] then img=fltk.fl_file_chooser("Choose a JPEG","*.jp{e,}g",nil,0) else img=arg[1] end
imgdata=fltk:Fl_JPEG_Image(img)
imgtest=imgdata:w() -- must be jpg
if imgtest==0 then os.exit() end

function write_file()
if passweird:value()=="" then
  fltk.fl_alert("No password specified!")
  return
end
local tmp=os.tmpname()..".txt"
if text:value()==1 then
  local out=io.open(tmp,"w")
  if out then
    out:write(textfield:value())
    out:close()
    embed=tmp
  end
else embed=filename:value()
end
local rtest=io.open(embed,"r")
if rtest then
  rtest:close()
else
  fltk.fl_alert("can't read file "..embed)
  return
end
if overw:value()==1 then ow=" -f " else ow="" end
os.execute("steghide embed -ef "..embed.." -cf "..img.." -sf "..outpoot:value().." -p "..passweird:value()..ow)
os.remove(tmp)
end

ww=500; wh=400; bh=30
w=fltk:Fl_Double_Window(ww,wh,"Steghide")

tabs=fltk:Fl_Tabs(0,0,ww,wh)

t1=fltk:Fl_Group(0,bh,ww,wh-bh,"image file")
t1:selection_color(46)
imgbox=fltk:Fl_Box(0,bh,ww,wh-bh)
imgbox:align(85)
imgbox:image(imgdata)
fltk:Fl_End() --t1

t2=fltk:Fl_Group(0,bh,ww,wh-bh,"hide data")
t2:selection_color(46)
file=fltk:Fl_Radio_Round_Button(10,bh*2,bh*3,bh,"file")
text=fltk:Fl_Radio_Round_Button(10,bh*3,bh*3,bh,"text")
filename=fltk:Fl_File_Input(10+bh*3,bh*2,ww-20-bh*4,bh)
choose=fltk:Fl_Button(ww-10-bh,bh*2,bh,bh,"@>")
choose:callback(function()
  newfile=fltk.fl_file_chooser("Embed a file","*.*",nil,0)
  if newfile then
    filename:value(newfile)
    file:value(1)
  end
end)
textfield=fltk:Fl_Multiline_Input(15,bh*4,ww-30,wh-bh*5)
fltk:Fl_End() --t2

t3=fltk:Fl_Group(0,bh,ww,wh-bh,"output")
t3:selection_color(46)
outpoot=fltk:Fl_File_Input(10+bh*2,bh*2,ww-20-bh*2,bh,"filename")
passweird=fltk:Fl_Secret_Input(10+bh*2,bh*4,ww-20-bh*2,bh,"password")
save=fltk:Fl_Button(10,bh*6,bh*3,bh,"save")
save:callback(write_file)
overw=fltk:Fl_Round_Button(bh*4,bh*6,bh*4,bh,"overwrite file")
slug=fltk:Fl_Box(ww-10,wh-10,10,10)
fltk:Fl_End() --t3
fltk:Fl_End() --tabs

file:value(1)
outname=string.gsub(img,"(.*)%..*","%1_stego.jpg")
outpoot:value(outname)

w:resizable(tabs)
tabs:resizable(t1)
tabs:resizable(t2)
tabs:resizable(t3)
t2:resizable(textfield)
t3:resizable(slug)
w:show()
Fl:run()




RE: Steganography - mikshaw - 04-13-2010 11:59 PM

Better, but still needs some dents worked out before I'd consider it stable.
Still might not work in Windows.

Changes:
*Image can be replaced.
*Allows both embedding and extracting (only one image open at a time, though).
*Encryption algorithm/mode can be specified.
*Shows info about the loaded image, including the amount of data that can be stored in it.
*Embed/extract as file have been removed, at least temporarily.

I took out the options to embed a file and to extract embedded data as a file, because their behavior is not as easy to handle in murgaLua as I'd hoped. The issue is mainly that steghide is a partially interactive program, and I haven't found a way to pass that interaction over to murgaLua. The extract-as-file is fine as long as you're ok with blindly forcing it every time, but I'm not ok with that. I'll probably be able to work something out with fl_file_chooser. Embed a file is more tricky. There is a variable limit to the amount of data that can be included in a JPEG, so the embed file has to be pretty small. If it's too big, steghide tells you it's too big, but that information does not get passed back to murgaLua for some reason. The save function has a similar issue, but since that is a vital part of the program I just have to accept it until i work out how to grab all necessary output from steghide.

The image info might need to be tweaked. Right now it uses "steghide info" to get the data capacity of the current image. It then shows that information as what I assume is the correct (approximate) number of available characters.
Is this right?:
If capacity is displayed in bytes, the number is rounded down to an integer.
If capacity is displayed in kb, the number is multiplied by 1024 and rounded down.
Should that be 1000 rather than 1024? Am I correct assuming 1byte=1character?
I may see if I can limit the size of the text buffer so the user doesn't have to guess.

Code:
#!/usr/bin/env murgaLua

-- limited GUI for steghide <steghide.sourceforge.net>
-- 2010 mikshaw
-- version 0.1

function apply_img(img)
image=img -- global image filename
local imgdata=fltk:Fl_JPEG_Image(img)
local w=imgdata:w()
local h=imgdata:h()
if w>0 and h>0 then imgbox:image(imgdata) end
  local outname=string.gsub(image,"(.*)%..*","%1_stego.jpg")
  outpoot:value(outname)
-- display image info/capacity in menu bar
local my_cmd=io.popen("steghide info "..image.." -p nothing")
local my_result=my_cmd:read("*a")
my_cmd:close()
local cap_str=string.gsub(my_result,".*capacity:%s*([%d%.]*%s*%a+).*","%1")
local cap_num=string.gsub(cap_str,"([%d%.]*).*","%1")
local cap_fmt=string.gsub(cap_str,"[%d%.%s]*(%a+)","%1")
if cap_fmt=="Byte" then cap_num=math.floor(cap_num); mult=1
elseif cap_fmt=="KB" then mult=1024 end
local chars=math.floor(cap_num*mult)
imgbar:menu(0):label(fltk.fl_filename_name(img)..
" | "..w.."x"..h.." | embed capacity: "..cap_str)
textfield:label("Your message ("..chars.." character limit for this image):")
textfield:redraw_label()
imgbox:size(w,h)
imgscr:position(0,0)
imgscr:redraw()
imgbar:redraw()
end

function choose_img()
local img=fltk.fl_file_chooser("Choose a JPEG","*.jp{e,}g",nil,0)
if img then apply_img(img) end
end

function choose_target()
local target=fltk.fl_file_chooser("Save As...","*.jp{e,}g",nil,0)
if target then outpoot:value(target) end
end

function write_file()
if not image then
  tabs:value(t1)
  fltk.fl_alert("You must choose an image file.")
  return
end
if outpoot:value()=="" then
  fltk.fl_alert("You must supply an output filename.")
  return
end
if embpass:value()=="" then
  fltk.fl_alert("You must set a password.")
  return
end
local tmp=os.tmpname()..".txt"
local out=io.open(tmp,"w")
if out then
  out:write(textfield:value())
  out:close()
else
  fltk.fl_alert("Can't read "..tmp.." for some reason")
end
if overw:value()==1 then ow=" -f " else ow="" end
os.execute("steghide embed -ef "..tmp.." -cf "..image.." -sf "..outpoot:value()..
" -p "..embpass:value()..ow.." -e "..enc_menu:text(enc_menu:value()).." "..mod_menu:text(mod_menu:value()))
os.remove(tmp)
end

function extract(cmd)
if not image then
  tabs:value(t1)
  fltk.fl_alert("You must choose an image file.")
  return
end
if extpass:value()=="" then
  fltk.fl_alert("You must supply a password.")
  return
end
local my_cmd=io.popen("steghide "..cmd..image.." -p "..extpass:value())
local my_result=my_cmd:read("*a")
my_cmd:close()
extbuff:append(my_result.."\n")
end


bh=30; ww=500; wh=bh*20
w=fltk:Fl_Double_Window(ww,wh,"Steghide")
tabs=fltk:Fl_Tabs(0,0,ww,wh)

-- IMAGE TAB --
t1=fltk:Fl_Group(0,bh,ww,wh-bh,"image file")
t1:selection_color(46)
imgbar=fltk:Fl_Menu_Bar(0,bh,ww,bh)
imgbar:add("click here to choose an image")
imgbar:menu(0):callback(choose_img)
imgscr=fltk:Fl_Scroll(0,bh*2,ww,wh-bh*2)
imgbox=fltk:Fl_Box(0,bh*2,ww,wh-bh*2)
fltk:Fl_End() --imgscr
imgbox:align(85)
fltk:Fl_End() --t1

-- EMBED TAB --
t2=fltk:Fl_Group(0,bh,ww,wh-bh,"embed")
t2:selection_color(46)
embpass=fltk:Fl_Secret_Input(10,bh*2,ww-20,bh,"password:")
textfield=fltk:Fl_Multiline_Input(10,bh*4,ww-20,bh*10)
textfield:align(5)
textfield:label("Type your secret message here")
enc_menu=fltk:Fl_Choice(10,bh*15+10,ww/2-10,bh,"encryption:")
mod_menu=fltk:Fl_Choice(ww/2,bh*15+10,ww/2-10,bh,"mode:")
enc_menu:align(5); mod_menu:align(5)
enc_menu:add("none")
-- get encinfo
enc_info=io.popen("steghide encinfo")
enc_data=enc_info:read("*a")
enc_info:close()
-- first 2 lines are useless
enc_data=string.gsub(enc_data,".*%.%.%.%s*(.*)%s*\n","%1")
count=0
-- create table for encryption modes
enc_tab={}
for line in string.gmatch(enc_data,".-\n") do
  count=count+1
  e=string.gsub(line,"(.-):(.*)","%1")
  m=string.gsub(line,".-:(.*)","%1")
  enc_tab[count]={}
  for i in string.gmatch(m,"%w+") do
    table.insert(enc_tab[count],i)
  end
  if e == "rijndael-128" then enc_def=count end
  enc_menu:add(e)
end
-- select the default encoding
enc_menu:value(enc_def)
outpoot=fltk:Fl_File_Input(10,bh*17,ww-20-bh,bh,"save as:")
ch_target=fltk:Fl_Button(ww-bh-10,bh*17,bh,bh,"@>")
ch_target:callback(choose_target)
outpoot:align(5); embpass:align(5)
save=fltk:Fl_Button(10,bh*18,bh*3,bh,"save")
save:callback(write_file)
overw=fltk:Fl_Round_Button(bh*4,bh*18,bh*4,bh,"overwrite an existing file?")
enc_menu:callback(function()
mod_menu:clear()
mod_menu:add(" ")
if enc_menu:value()==0 then mod_menu:value(0)
else
for k,v in pairs(enc_tab[enc_menu:value()]) do
  mod_menu:add(v)
end
mod_menu:value(1)
end
end)
enc_menu:do_callback()
fltk:Fl_End() --t2

-- EXTRACT TAB --
t3=fltk:Fl_Group(0,bh,ww,wh-bh,"extract")
t3:selection_color(46)
exttext=fltk:Fl_Text_Display(10,bh*4,ww-20,bh*15)
extbuff=fltk:Fl_Text_Buffer()
exttext:buffer(extbuff)
extpass=fltk:Fl_Secret_Input(10,bh*2,ww-20,bh,"password:")
extpass:align(5)
infobutt=fltk:Fl_Button(10,bh*3,bh*3,bh,"get info")
infobutt:callback(function() extract("info ") end)
astext=fltk:Fl_Button(10+bh*3,bh*3,bh*3,bh,"extract text")
astext:callback(function() extract("extract  -f -xf - -sf ") end)
-- The asfile feature doesn't work well with lua
--asfile=fltk:Fl_Button(10+bh*6,bh*3,bh*3,bh,"extract file")
--asfile:callback(function() extract("extract -sf ") end)
fltk:Fl_End() --t3
fltk:Fl_End() --tabs

if arg[1] then apply_img(arg[1]) end

w:resizable(tabs)
tabs:resizable(t1)
tabs:resizable(t2)
tabs:resizable(t3)
t1:resizable(imgscr)
t2:resizable(textfield)
t3:resizable(exttext)
w:show()
Fl:run()