DSLBQ'S_BLOG

作者: dslbq

  • 《般若波罗蜜多心经》

    《般若波罗蜜多心经》

    般若波罗蜜多心经

    唐三藏法师玄奘奉 诏译

      观自在菩萨。行深般若波罗蜜多时。照见五蕴皆空。度一切苦厄。舍利子。色不异空。空不异色。色即是空。空即是色。受想行识亦复如是。舍利子。是诸法空相。不生不灭。不垢不净。不增不减。是故空中无色。无受想行识。无眼耳鼻舌身意。无色声香味触法。无眼界。乃至无意识界。无无明。亦无无明尽。乃至无老死。亦无老死尽。无苦集灭道。无智亦无得。以无所得故。菩提萨埵。依般若波罗蜜多故。心无挂碍。无挂碍故。无有恐怖。远离颠倒梦想。究竟涅槃。三世诸佛。依般若波罗蜜多故。得阿耨多罗三藐三菩提。故知般若波罗蜜多。是大神咒。是大明咒。是无上咒,是无等等咒。能除一切苦。真实不虚。故说般若波罗蜜多咒。即说咒曰:

      揭谛揭谛。波罗揭谛。波罗僧揭谛。菩提萨婆诃。

  • 《金刚般若波罗蜜经》

    《金刚般若波罗蜜经》

    香赞

    炉香乍爇,法界蒙熏,诸佛海会悉遥闻,随处结祥云,诚意方殷,诸佛现全身,
    南无香云盖菩萨摩诃萨(三称)

    奉请八金刚

    奉请青除灾金刚 奉请辟毒金刚
    奉请黄随求金刚 奉请白净水金刚
    奉请赤声火金刚 奉请定持灾金刚
    奉请紫贤金刚 奉请大神金刚

    奉请四菩萨

    奉请金刚眷菩萨 奉请金刚索菩萨
    奉请金刚爱菩萨 奉请金刚语菩萨

    发愿文

    稽首三界尊,皈命十方佛,
    我今发宏愿,持此金刚经,
    上报四重恩,下济三涂苦,
    若有见闻者,悉发菩提心,
    尽此一报身,同生极乐国。
    云何得长寿,金刚不坏身,
    复以何因缘,得大坚固力;
    云何于此经,究竟到彼岸,
    愿佛开微密,广为众生说。
    南无本师释迦牟尼佛(三称)

    开经偈

    无上甚深微妙法
    百千万劫难遭遇
    我今见闻得受持
    愿解如来真实义

    金刚般若波罗蜜经

    姚秦三藏法师鸠摩罗什译

      如是我闻,一时,佛在舍卫国祇树给孤独园。与大比丘众千二百五十人俱。尔时世尊食时,著衣持钵,入舍卫大城乞食。于其城中次第乞已。还至本处。饭食讫,收衣钵,洗足已,敷座而坐。

      时,长老须菩提,在大众中,即从座起,偏袒右肩,右膝著地,合掌恭敬而白佛言:希有,世尊!如来善护念诸菩萨。善付嘱诸菩萨。世尊,善男子、善女人发阿耨多罗三藐三菩提心,应云何住?云何降伏其心?佛言:善哉!善哉!须菩提,如汝所说,如来善护念诸菩萨,善付嘱诸菩萨。汝今谛听,当为汝说。善男子、善女人发阿耨多罗三藐三菩提心,应如是住,如是降伏其心。唯然,世尊。愿乐欲闻。

      佛告须菩提:诸菩萨摩诃萨应如是降伏其心:所有一切众生之类,若卵生、若胎生、若湿生、若化生,若有色、若无色,若有想、若无想,若非有想、非无想,我皆令入无余涅槃而灭度之。如是灭度无量无数无边众生,实无众生得灭度者。何以故?须菩提,若菩萨有我相、人相、众生相、寿者相,即非菩萨。

      复次,须菩提,菩萨于法,应无所住行于布施。所谓不住色布施,不住声、香、味、触、法布施。须菩提,菩萨应如是布施,不住于相。何以故?若菩萨不住相布施,其福德不可思量。须菩提,于意云何?东方虚空可思量不?不也,世尊。须菩提,南西北方、四维上下虚空,可思量不?不也,世尊。须菩提,菩萨无住相布施,福德亦复如是不可思量。须菩提。菩萨但应如所教住。

      须菩提,于意云何?可以身相见如来不?不也,世尊。不可以身相得见如来。何以故?如来所说身相,即非身相。佛告须菩提:凡所有相,皆是虚妄。若见诸相非相,则见如来。

      须菩提白佛言:世尊,颇有众生,得闻如是言说章句,生实信不?佛告须菩提:莫作是说,如来灭后,后五百岁,有持戒修福者,于此章句,能生信心,以此为实。当知是人,不于一佛二佛三四五佛而种善根,已于无量千万佛所种诸善根。闻是章句,乃至一念生净信者,须菩提,如来悉知悉见,是诸众生,得如是无量福德。何以故?是诸众生无复我相、人相、众生相、寿者相;无法相,亦无非法相。何以故?是诸众生,若心取相,则为著我、人、众生、寿者。若取法相,即著我、人、众生、寿者。何以故?若取非法相,即著我、人、众生、寿者。是故不应取法,不应取非法。以是义故,如来常说,汝等比丘。知我说法,如筏喻者,法尚应舍,何况非法?

      须菩提,于意云何?如来得阿耨多罗三藐三菩提耶?如来有所说法耶?须菩提言:如我解佛所说义,无有定法,名阿耨多罗三藐三菩提,亦无有定法如来可说。何以故?如来所说法,皆不可取、不可说非法,非非法。所以者何?一切贤圣,皆以无为法,而有差别。

      须菩提,于意云何?若人满三千大千世界七宝,以用布施,是人所得福德,宁为多不?须菩提言:甚多,世尊。何以故?是福德,即非福德性。是故如来说福德多。若复有人,于此经中,受持乃至四句偈等,为他人说,其福胜彼。何以故?须菩提。一切诸佛,及诸佛阿耨多罗三藐三菩提法,皆从此经出。须菩提,所谓佛法者,即非佛法。

      须菩提,于意云何?须陀洹能作是念:“我得须陀洹果”不?须菩提言:不也,世尊。何以故?须陀洹名为入流,而无所入。不入色声香味触法,是名须陀洹。须菩提,于意云何?斯陀含能作是念:“我得斯陀含果”不?须菩提言:不也,世尊。何以故?斯陀含名一往来,而实无往来,是名斯陀含。须菩提,于意云何?阿那含能作是念:“我得阿那含果”不?须菩提言:不也,世尊。何以故?阿那含名为不来,而实无不来。是故名阿那含。须菩提,于意云何?阿罗汉能作是念:“我得阿罗汉道”不?须菩提言:不也,世尊。何以故?实无有法名阿罗汉。世尊,若阿罗汉作是念,我得阿罗汉道,即为著我、人、众生、寿者。世尊,佛说我得无诤三昧,人中最为第一,是第一离欲阿罗汉。世尊,我不作是念,我是离欲阿罗汉。世尊。我若作是念,我得阿罗汉道。世尊则不说须菩提是乐阿兰那行者。以须菩提实无所行。而名须菩提是乐阿兰那行。

      佛告须菩提:于意云何?如来昔在然灯佛所,于法有所得不?不也,世尊。如来在然灯佛所,于法实无所得。须菩提,于意云何?菩萨庄严佛土不?不也,世尊。何以故?庄严佛土者,即非庄严,是名庄严。是故须菩提。诸菩萨摩诃萨,应如是生清净心。不应住色生心,不应住声、香、味、触、法生心,应无所住而生其心。须菩提,譬如有人,身如须弥山王,于意云何?是身为大不?须菩提言:甚大,世尊。何以故?佛说非身,是名大身。

      须菩提,如恒河中所有沙数,如是沙等恒河,于意云何?是诸恒河沙宁为多不?须菩提言:甚多,世尊。但诸恒河尚多无数,何况其沙?须菩提,我今实言告汝:若有善男子、善女人,以七宝满尔所恒河沙数三千大千世界,以用布施,得福多不?须菩提言:甚多,世尊。佛告须菩提:若善男子、善女人。于此经中,乃至受持四句偈等。为他人说。而此福德,胜前福德。

      复次,须菩提,随说是经,乃至四句偈等,当知此处,一切世间天、人、阿修罗,皆应供养如佛塔庙。何况有人,尽能受持读诵?须菩提,当知是人,成就最上第一希有之法。若是经典所在之处。则为有佛,若尊重弟子。

      尔时,须菩提白佛言:世尊,当何名此经?我等云何奉持?佛告须菩提:是经名为金刚般若波罗蜜,以是名字,汝当奉持。所以者何?须菩提,佛说般若波罗蜜,即非波若波罗蜜,是名般若波罗蜜。须菩提,于意云何?如来有所说法不?须菩提白佛言:世尊,如来无所说。须菩提,于意云何?三千大千世界所有微尘,是为多不?须菩提言:甚多,世尊。须菩提,诸微尘,如来说非微尘,是名微尘。如来说世界,非世界,是名世界。须菩提:于意云何?可以三十二相见如来不?不也,世尊。不可以三十二相得见如来。何以故?如来说三十二相,即是非相,是名三十二相。须菩提,若有善男子、善女人,以恒河沙等身命布施。若复有人,于此经中,乃至受持四句偈等为他人说,其福甚多。

      尔时,须菩提闻说是经,深解义趣,涕泪悲泣,而白佛言:希有世尊!佛说如是甚深经典,我从昔来所得慧眼,未曾得闻如是之经,世尊。若复有人得闻是经,信心清净,则生实相。当知是人,成就第一希有功德。世尊,是实相者,则是非相,是故如来说名实相。世尊,我今得闻如是经典,信解受持,不足为难。若当来世,后五百岁,其有众生,得闻是经,信解受持,是人则为第一希有。何以故?此人无我相、无人相、无众生相、无寿者相。所以者何?我相即是非相;人相、众生相、寿者相,即是非相。何以故?离一切诸相,则名诸佛。佛告须菩提:如是!如是!若复有人,得闻是经,不惊不怖不畏,当知是人,甚为希有。何以故?须菩提,如来说第一波罗蜜,即非第一波罗蜜,是名第一波罗蜜。须菩提,忍辱波罗蜜,如来说非忍辱波罗蜜,是名忍辱波罗蜜。何以故?须菩提,如我昔为歌利王割截身体,我于尔时,无我相、无人相、无众生相、无寿者相。何以故?我于往昔节节支解时,若有我相、人相、众生相、寿者相,应生瞋恨。须菩提,又念过去,于五百世作忍辱仙人,于尔所世,无我相、无人相、无众生相、无寿者相。是故,须菩提,菩萨应离一切相,发阿耨多罗三藐三菩提心,不应住色生心,不应住声、香、味、触、法生心,应生无所住心。若心有住,则为非住。是故,佛说菩萨心不应住色布施。须菩提,菩萨为利益一切众生故,应如是布施。如来说一切诸相即是非相,又说一切众生即非众生。须菩提,如来是真语者、实语者、如语者、不诳语者、不异语者。须菩提,如来所得法,此法无实无虚。须菩提。若菩萨心住于法而行布施,如人入闇,则无所见。若菩萨心不住法而行布施,如人有目,日光明照,见种种色。须菩提。当来之世,若有善男子、善女人,能于此经受持读诵,则为如来以佛智慧,悉知是人,悉见是人,皆得成就无量无边功德。

      须菩提,若有善男子、善女人,初日分以恒河沙等身布施,中日分复以恒河沙等身布施,后日分亦以恒河沙等身布施,如是无量百千万亿劫以身布施。若复有人,闻此经典,信心不逆,其福胜彼。何况书写、受持读诵、为人解说。须菩提,以要言之,是经有不可思议不可称量,无边功德。如来为发大乘者说,为发最上乘者说。若有人能受持读诵,广为人说。如来悉知是人,悉见是人,皆得成就不可量、不可称、无有边、不可思议功能。如是人等,则为荷担如来阿耨多罗三藐三菩提。何以故?须菩提,若乐小法者,著我见、人见、众生见、寿者见,则于此经不能听受读诵,为人解说。须菩提,在在处处,若有此经,一切世间天人阿修罗所应供养。当知此处,则为是塔,皆应恭敬,作礼围绕,以诸华香而散其处。

      复次,须菩提,若善男子、善女人受持读诵此经,若为人轻贱,是人先世罪业,应堕恶道,以今世人轻贱故,先世罪业则为消灭,当得阿耨多罗三藐三菩提。须菩提,我念过去无量阿僧祇劫,于然灯佛前,得值八百四千万亿那由他诸佛,悉皆供养承事,无空过者。若复有人,于后末世,能受持读诵此经,所得功能,于我所供养诸佛功德,百分不及一,千万亿分,乃至算数譬喻所不能及。须菩提,若善男子、善女人,于后末世,有受持读诵此经,所得功德,我若具说者,或有人闻,心则狂乱,狐疑不信。须菩提。当知是经义不可思议,果报亦不可思议。

      尔时,须菩提白佛言:世尊,善男子、善女人发阿耨多罗三藐三菩提心,云何应住?云何降伏其心?佛告须菩提:善男子、善女人发阿耨多罗三藐三菩提心者,当生如是心,我应灭度一切众生,灭度一切众生已,而无有一众生实灭度者。何以故?须菩提,若菩萨有我相、人相、众生相、寿者相、则非菩萨。所以者何?须菩提,实无有法,发阿耨多罗三藐三菩提心者。须菩提,于意云何?如来于然灯佛所,有法得阿耨多罗三藐三菩提不?不也,世尊。如我解佛所说义,佛于然灯佛所,无有法得阿耨多罗三藐三菩提。佛言:如是,如是。须菩提,实无有法,如来得阿耨多罗三藐三菩提。须菩提,若有法如来得阿耨多罗三藐三菩提者,然灯佛则不与我授记,汝于来世当得作佛,号释迦牟尼。以实无有法,得阿耨多罗三藐三菩提,是故然灯佛与我授记。作是言:汝于来世,当得作佛,号释迦牟尼。何以故?如来者,即诸法如义。若有人言:如来得阿耨多罗三藐三菩提。须菩提,实无有法,佛得阿耨多罗三藐三菩提。须菩提,如来所得阿耨多罗三藐三菩提,于是中无实无虚。是故,如来说一切法皆是佛法。须菩提,所言一切法者,即非一切法,是故名一切法。须菩提,譬如人身长大。须菩提言:世尊,如来说人身长大,则为非大身,是名大身。须菩提。菩萨亦如是。若作是言,我当灭度无量众生,则不名菩萨。何以故?须菩提,实无有法名为菩萨。是故,佛说一切法无我、无人、无众生、无寿者。须菩提,若菩萨作是言,我当庄严佛土,是不名菩萨。何以故?如来说庄严佛土者,即非庄严,是名庄严。须菩提,若菩萨通达无我法者,如来说名真是菩萨。

      须菩提,于意云何?如来有肉眼不?如是,世尊,如来有肉眼。须菩提,于意云何?如来有天眼不?如是,世尊,如来有天眼。须菩提,于意云何?如来有慧眼不?如是世尊,如来有慧眼。须菩提,于意云何?如来有法眼不?如是,世尊,如来有法眼。须菩提,于意云何?如来有佛眼不?如是,世尊。如来有佛眼。须菩提,于意云何?如恒河中所有沙,佛说是沙不?如是,世尊,如来说是沙,须菩提,于意云何?如一恒河中所有沙,有如是沙等恒河,是诸恒河所有沙数佛世界,如是,宁为多不?甚多,世尊。佛告须菩提。尔所国土中,所有众生,若干种心,如来悉知。何以故?如来说诸心皆为非心,是名为心。所以者何?须菩提,过去心不可得,现在心不可得,未来心不可得。

      须菩提,于意云何?若有人满三千大千世界七宝,以用布施,是人以是因缘,得福多不?如是,世尊,此人以是因缘,得福甚多。须菩提。若福德有实,如来不说得福德多。以福德无故,如来说得福德多。

      须菩提,于意云何?佛可以具足色身见不?不也,世尊!如来不应以具足色身见。何以故?如来说具足色身,即非具足色身,是名具足色身。须菩提。于意云何?如来可以具足诸相见不?不也,世尊,如来不应以具足诸相见。何以故?如来说诸相具足,即非具足,是名诸相具足。

      须菩提,汝勿谓如来作是念,我当有所说法。莫作是念,何以故?若人言,如来有所说法,即为谤佛,不能解我所说故。须菩提,说法者,无法可说,是名说法。尔时慧命须菩提白佛言:世尊,颇有众生,于未来世闻说是法,生信心不?佛言:须菩提,彼非众生,非不众生。何以故?须菩提,众生众生者,如来说非众生,是名众生。

      须菩提白佛言:世尊,佛得阿耨多罗三藐三菩提,为无所得耶?佛言:如是如是!须菩提,我于阿耨多罗三藐三菩提,乃至无有少法可得,是名阿耨多罗三藐三菩提。

      复次,须菩提,是法平等,无有高下,是名阿耨多罗三藐三菩提。以无我、无人、无众生、无寿者修一切善法,即得阿耨多罗三藐三菩提。须菩提,所言善法者,如来说即非善法,是名善法。

      须菩提,若三千大千世界中,所有诸须弥山王,如是等七宝聚,有人持用布施。若人以此般若波罗蜜经,乃至四句偈等,受持读诵,为他人说,于前福德。百分不及一,百千万亿分,乃至算数譬喻所不能及。

      须菩提,于意云何?汝等勿谓如来作是念,我当度众生。须菩提,莫作是念。何以故?实无有众生如来度者。若有众生如来度者,如来则有我人众生寿者。须菩提,如来说有我者,则非有我,而凡夫之人以为有我,须菩提,凡夫者,如来说即非凡夫,是名凡夫。

      须菩提,于意云何?可以三十二相观如来不?须菩提言:如是如是。以三十二相观如来。佛言:须菩提,若以三十二相观如来者,转轮圣王即是如来。须菩提白佛言:世尊。如我解佛所说义,不应以三十二相观如来。尔时,世尊而说偈言:

    若以色见我,以音声求我,
    是人行邪道,不能见如来。

      须菩提,汝若作是念,如来不以具足相故,得阿耨多罗三藐三菩提。须菩提,莫作是念,如来不以具足相故,得阿耨多罗三藐三菩提。须菩提,汝若作是念,发阿耨多罗三藐三菩提心者,说诸法断灭,莫作是念。何以故?发阿耨多罗三藐三菩提心者,于法不说断灭相。

      须菩提,若菩萨以满恒河沙等世界七宝,持用布施。若复有人,知一切法无我,得成无忍。此菩萨胜前菩萨所得功德,何以故?须菩提,以诸菩萨不受福德故。须菩提白佛言:世尊,云何菩萨不受福德?须菩提,菩萨所作福德,不应贪著。是故说不受福德

      须菩提,若有人言,如来若来若去、若坐若卧,是人不解我所说义。何以故?如来者,无所从来,亦无所去,故名如来。

      须菩提,若善男子,善女人,以三千大千世界碎为微尘,于意云何?是微尘众,宁为多不?须菩提言:甚多,世尊。何以故?若是微尘众实有者,佛则不说是微尘众。所以者何?佛说微尘众,即非微尘众,是名微尘众。世尊。如来所说三千大千世界,即非世界,是名世界。何以故?若世界实有者,则是一合相,如来说一合相,即非一合相,是名一合相。须菩提,一合相者,则是不可说,但凡夫之人,贪著其事。

      须菩提,若人言,佛说我见、人见、众生见、寿者见。须菩提,于意云何?是人解我所说义不?不也,世尊,是人不解如来所说义。何以故?世尊说我见、人见、众生见、寿者见,即非我见、人见、众生见、寿者见,是名我见、人见、众生见、寿者见。须菩提,发阿耨多罗三藐三菩提心者,于一切法,应如是知,如是见,如是信解,不生法相。须菩提,所言法相者,如来说即非法相,是名法相。

      须菩提,若有人以满无量阿僧祇世界七宝,持用布施。若有善男子、善女人发菩提心者,持于此经,乃至四句偈等,受持读诵,为人演说,其福胜彼。云何为人演说?不取于相,如如不动。何以故?

    一切有为法,如梦幻泡影,
    如露亦如电,应作如是观。

      佛说是经已,长老须菩提及诸比丘、比丘尼、优婆塞、优婆夷,一切世间,天人阿修罗,闻佛所说皆大欢喜,信受奉行。

    金刚般若波罗蜜经

    金刚赞

      断疑生信。绝相超宗。顿忘人法解真空。般若味重重。四句融通。福德叹无穷。南无祇园会上佛菩萨(三称)

    回向偈

    愿以此功能,普及于一切。
    我等与众生,皆共成佛道。

  • 汇编语言

    汇编语言

    概述

    汇编语言是直接在硬件之上工作的编程语言,首先要了解硬件系统的结构,才能有效的应用汇编语言对其编程。汇编的研究重点放在如何利用硬件系统的编程结构和指令集有效灵活的控制系统进行工作。

    基础知识

    • 汇编指令是机器指的助记符,同机器指令一一对应(用汇编编译器转换)。
    • 每一种CPU都有自己的汇编指令集。
    • CPU可以直接使用的信息在存储器中存放。
    • 在存储器中指和数据没有任何区别,都是二进制信息。
    • 每一个CPU芯片都有许多管脚,这些管脚和总线相连。也可以说,这些管脚引出总线。一个CPU可以引出三种总线的宽度标志了这个CPU的不同方面的性能
      • 地址总线的宽度决定了CPU的寻址能力
      • 数据总线的宽度决定了CPU与其它器件进行数据传送时的一次数据传送量
      • 控制总线宽度决定了CPU对系统中其它器件的控制能力

    CPU对存储器的读写

    CPU要想进行数据的读写,必须和外部器件(标准的说法是芯片)进行三类信息的交互:

    • 存储单元的地址(地址信息)
    • 器件的选择,读或写命令(控制信息)
    • 读或写的数据(数据信息)

    在计算机中专门有连接CPU和其他芯片的导线,通常称为总线。

    • 物理上:一根根导线的集合
    • 逻辑上划分为(存储器中的数字是指令还是数据根据总线进行区分,例如0x0011102可以是数据,也可以是指令):
      • 地址总线
      • 控制总线
      • 数据总线

    CPU读写流程:

    1. 寻找要读写的地址
    2. 发出控制指令
    3. 读写数据

    地址总线

    CPU是通过地址总线来指定存储单元的。地址总线上能传送多少个不同的信息,CPU就可以对多少个存储单元进行寻址。我们常说的64位、32位指的就是CPU的地址总线宽度。

    数据总线

    CPU与内存或其它器件之间的数据传送是通过数据总线来进行的。数据总线的宽度决定了CPU和外界的数据传送速度。

    控制总线

    CPU对外部器件控制是通过控制总线来进行的。在这里控制总线是个总称,控制总线是一些不同控制线的集合。

    有多少根控制总线,就意味着CPU提供了对外部器件的多少种控制。所以,控制总线的宽度决定了CPU对外部器件的控制能力。

    前面所讲的内存读或写命令是由几根控制线综合发出的:

    • 其中有一根名为读信号输出控制线负责由CPU向外传送读信号,CPU向该控制线上输出低电平表示将要读取数据。
    • 有一根名为写信号输出控制线负责由CPU后外传送写信号。

    内存地址空间

    一个CPU的地址线宽度为10,那么可以寻址1024个(210)内存单元,这1024个可寻到的内存单元就构成这个CPU的内存地址空间。

    存储器(网卡、显卡、内存)在物理上是独立的器件。但是它们在以下两点上相同:

    1. 都和CPU的总线相连。
    2. CPU对它们进行读或写的时候都通过控制线发出内存读写命令。

    将各类存储器看作一个逻辑存储器:

    • 所有的物理存储器被看作一个由若干存储单元组成的逻辑存储器。
    • 每个物理存储器在这个逻辑存储器中占有一个地址段,即一段地址空间。
    • CPU在这段地址空间中读写数据,实际上就是在相对应的物理存储器中读写数据。

    不同计算机系统的内存地址空间分配情况是不同的,下图为8086PC机的内存地址空间分配情况

    最终运行程序的是CPU,我们用汇编编程的时候,必须要从CPU角度考虑问题。
    对CPU来讲,系统中的所有存储器中的存储单元都处于一个统一的逻辑存储器中,它的容量受CPU寻址能力的限制。这个逻辑存储器即是我们所说的内存地址空间。

    寄存器(CPU工作原理)

    CPU概述

    一个典型的CPU由运算器控制器寄存器等器件组成,这些器件靠内部总线相连。

    区别:

    • 内部总线实现CPU内部各个器件之间的联系。
    • 外部总线实现CPU和主板上其它器件的联系。

    寄存器概述

    8086 CPU中寄存器总共为 14 个,且均为 16 位。即AX、BX、CX、DX、SP、BP、SI、DI、IP、PSW、CS、DS、SS、ES

    寄存器类型

    • 通用寄存器
      • AX(Accumulator): 累加寄存器,也称之为累加器
    • BX(Base): 基地址寄存器
    • CX(Count):计数器寄存器
    • DX(Data):数据寄存器
    • 指针寄存器
      • SP(Stack Pointer):堆栈指针寄存器
      • BP(Base Pointer): 基指针寄存器
    • 址寄存器
      • SI(Source Index):源变址寄存器
      • Dl(Destination Index):目的变址寄存器
    • 控制寄存器
      • IP(Instruction Pointer): 指令指针寄存器
      • FLAG:标志寄存器
    • 段寄存器
      • CS(Code Segment): 代码段寄存器
      • DS(Data Segment):数据段寄存器
      • SS(Stack Segment):堆栈段寄存器
      • ES(Extra Segment):附加段寄存器

    字在寄存器中的存储

    一个字可以存在区一个16位寄存器中,这个字的高位字芊和低位字节自然就存在这个寄存器的高8位寄存器和低8位寄存器中。

    物理地址

    CPU访问内存单元时要给出内存单元的地址。所有的内存单元构成的存储空间是一个一维的线性空间。我们将这个唯一的地址称为物理地址。

    CPU中的相关部件提供两个16位的地址,一个称为段地址,另一个称为偏移地址,段地址和偏移地址通过内部总线送入一个称为地址加法器的部件,地址加法器将两个16位地址合并成一个20位的地址。

    地址加法器合成物理地址的方法:物理地址=段地址 X 16 + 偏移地址

    “段地址 * 16″有一个更为常用的说法就是数据左移4位。(二进制位 )

    一个数据的十六进制形式左移1位,相当于乘以16
    一个数据的十进制形式左移1位,相当于乘以10
    一个数据的X进制形式左移1位,相当于以X

    段的概念

    错误认识 :内存被划分成了一个一个的段,每一个段有一个段地址。
    其实 :
    内存并没有分段,段的划分来自于CPU,由于8086 CPU用 “(段地址X16) +偏移地址=物理地址”的方式给出内存单元的物理地址,使得我们可以用分段的方式来管理内存。

    在编程时可以根据需要,将若干地址连续的内在单元看作一个段,用段地址 X 16定位段的起始地址(基础地址),用偏移地址定位段中的内存单元。

    两点需要注意

    • 段地址 X 16 必然是16的倍数,所以一个段的起始地址也一定是16的倍数。
    • 偏移地址为16位,16位地址的寻址能力为64K,所以一个段的长度最大为64K。

    “数据在21F60H内存单元中。”对于8086PC机的两种描述 :

    1. 数据存在内存2000:1F60单元中
    2. 数据存在内存的2000段中的IF60H单元中

    可根据需要,将地址连续、起始地址为16的倍数的一组内存单元定义为一个段。

    段寄存器

    段寄存器就是提供段地址的。
    8086 CPU有4个段寄存器 :CS、DS、SS、ES
    当8086CPU要访问内存时,由这4个段寄存器提供内存单元的段地址。

    CS和IP

    CS和IP是8086CPU中最关键的寄存器,它们指示了CPU当前要读取指令的地址。
    CS为代码段寄存器,IP为指令指针寄存器。

    工作流程

    1. 从CS:IP指向内存地址读取指令,读取的指令进入指令缓冲器。
    2. IP = IP + 所读取指的长度,从而指向下一条指令。
    3. 执行指令。 转到步骤1,重复这个过程。

    工作过程的简要概述

    • 在8086CPU加电启动或复位后(即CPU刚开始工作时)CS和IP被设置为CS=FFFFH,IP=O00OH。
    • 即在8086PC机刚启动时,CPU从内存FFFF0H单元中读取指令执行。
    • FFFF0H单元中的指合是8086PC机开机后执行的第一条指令。
    • 在任何时候,CPU将CS、IP中的内容当作指令的段地址和偏移地址,用它们合成指令的物理地址,到内存中读取指令码,执行。
    • 如果说,内存中的一段信息曾被CPU执行过的话,那么,它所在的内存单元必然被CS:IP指向过。

    修改CS:IP的指令

    • 在CPU中,程序员能够用指令读写的部件只有寄存器,程序员可以通过改变寄存器中的内容实现对CPU的控制。
    • CPU从何处执行指今是由CS:IP中的内容决定的,程序员可以通过改变CS:IP中的内容来控制CPU执行目标指令。

    代码段

    • 对于8086 PC机,在编程时,可以根据需要,将一组内存单元定义为一个段。
    • 可以将长度为N(N<=64KB)的一组代码,存在一组地址连续、起始地址为16的倍数的内存单元中,这段内存是用来存放代码的,从而定义了一个代码段

    这段长度为10字节的字节的指令,存在从123B0H~123B9H的一组内存单元中,我们就可以认为,123B0H~123B9H这段内存单元是用来存放代码的,是一个代码段,它的段地址为123BH,长度为10字节。

    如何使得代码段中的指令被执行呢?

    将一段内存当作代码段,仅仅是我们在编程时的一种安排,CPU 并不会由于这种安排,就自动地将我们定义得代码段中的指令当作指令来执行。

    CPU只认被CS:IP指向的内存单元中的内容为指令,所以要将CS:IP指向所定义的代码段中的第一条指令的首地址。

    Windows调试工具

    调用 cmd,输入debug

    R:查看、改变CPU寄存器的内容
    D:查看内存中的内容 -d 1000:0
    E:改写内存中的内容 -e 1000:0 23 11 22 66
    U:将内存中的机器指令翻译成汇编指令
    T:执行一条机器指令
    A:以汇编指令的格式在内存中写入一条机器指令(没有执行) -a 1386:10 mov ax 100...

    寄存器(内存访问)

    内存中字的存储

    任何两个地址连续的内存单元,N号单元和N+1号单元,可以将它们看成两个内存单元,也可以看成一个地址为N的字单元中的高位字节单元和低位字节单元。

    DS和[address]

    • CPU要读取一个内存单元的时候,必须先给出这个内存单元的地址
    • 在8086PC中,内存地址由段地址和偏移地址组成
    • 8086CPU中有一个DS寄存器,通常用来存放要访问的数据的段地址

    例如:我们要读取10000H单元的内容可以用如下程序段进行:

    mov bx, 1000H ;设置bx值为1000:0地址(会自动地址加法)
    mov ds, bx
    mov al, [0] ;偏移地址

    上面三条指令将10000H (1000:0) 中的数据读到al中。

    已知的mov指令可完成的三种传送功能

    • 将数据直接送入寄存器:mov ax, 2H
    • 将一个寄存器中的内容送入另一个寄存器中:mov bx, ax
    • 将一个内存单元中的内容送入一个寄存器:mov al, [0]
    • 将一个寄存器的内容送入内存单元中:mov [0], al
    • 将一个寄存器的内容送入段寄存器中:mov ds, ax

    执行指令时,8086CPU自动取DS中的数据为内存单元的段地址。
    如何用mov指令从10000H中读取数据?

    1. 10000H表示为1000:0 (段地址:偏移地址)
    2. 将段地址1000H放入ds
    3. 用mov al,[O]完成传送 (mov指令中的[]说明操作对象是一个内存单元,[]中的0说明这个内存单元的偏移地址是0,它的段地址默认放在ds中)

    8086CPU不支持将数据直接送入DS段寄存器,这是硬件设计的问题

    怎么样将al的数据从寄存器送入内存单元?

    mov bx, 1000H
    mov ds, bx
    mov [0], al

    字的传送

    因为8086CPU是16位结构,有16根数据线,所以可以一次性传送16位的数据,也就是一次性传送一个字

    mov bx, 1000H
    mov ds, bx ;指定段地些
    mov ax, [0] ;将1000:0处的字型数据送入ax,因为ax有16位,所以一次送传送一个字
    mov [0], cx ;将cx中的16位数据送到内存1000:0处

    数据段

    对于8086PC机,我们可以根据需要将一组内存单元定义为一个段(可以是代码段、数据段等)。

    我们可以将一组长度为N (N<=64K) 、地址连续、起始地址为16的倍数的内存单元当作专门存储数据的内存空间,从而定义了一个数据段。

    比如我们用123B0H~12339H这段空间来存放数据

    • 段地址:1233H
    • 长度 :10字节

    如何访问数据段中的数据?

    将一段内存当作数据段,是我们在编程时的一种安排,我们可以在具体操作的时候,用ds存放数据段的段地址,再根据需要,用相关指令访问数据段中的具体单元。

    • 栈用来临时存放东西
    • 栈有两个基本的操作 : 入栈和出栈
      • 入栈:将一个新的元素放到栈顶
      • 出栈:从栈顶取出一个元素
    • 栈顶的元素总是最后入栈,需要出栈时,又最先被从栈中取出
    • 栈的操作规则: LIFO(Last In First out,后进先出)
    • 现今的CPU中都有栈的设计
    • 8086CPU提供相关的指令来以栈的方式访问内存空间
    • 这意味着,我们在基于8086CPU编程的时候,可以将一段内存当作栈来使用
    • 8086 CPU提供入栈和出栈指令:
      • PUSH(入栈)
      • POP(出栈)
    • push ax:将寄存器ax中的数据送入栈
    • pop ax:从栈顶取出数据送入ax
    • 8086 CPU的入栈和出栈操作都是以字为单位进行的
    • 字型数据用两个单元(字节)存放,高地址单元放高8位 ,低地址单元放低8位

    CPU如何知道当前要执行的指令所在的位置

    寄存器CS和IP中存放着当前指令的段地址和偏移地址。8086CPU中,有两个寄存器:

    • 栈段寄存器SS:存放栈顶的段地址
    • 栈指针寄存器SP:存放栈顶的偏移地址

    任意时刻,SS:SP指向栈顶元素。

    执行push和pop的时候,如何知道哪个单元是栈顶单元?

    执行push ax时,CPU做了以下步骤:

    • SP=SP-2
    • 将ax中的容送入SS:SP指向的内存单元处,SS:SP此时指向新栈顶。

    如果我们将10000H~1000FH这段空间当作栈,初始状态栈是空的,此时,SS=1000H,SP=?

    SP = 0010H,指向栈空间最高地址单元的下一个单元

    POP之后,内存中的数据还是存在的,只有等下一次数据push时才会被覆盖

    栈顶超界的问题

    SS和SP只记录了栈顶的地址,依靠SS和SP可以保证在入栈和出栈时找到栈顶。可是如何能够保证在入栈、出栈时,栈顶不会超出栈空间?

    当栈满的时候再使用push指令入栈,栈空的时候再使用pop指令出栈,都将发生栈顶超界问题。栈顶超界是危险的,因为:我们既然将一段空间安排为栈,那么在栈空间之外的空间里很可能存放了具有其他用途的数据、代码等,这些数据、代码可能是我们自己的程序中的,也可能是别的程序中的。(毕竟一个计算机系统并不是只有我们自己的程序在运行)。

    但是由于我们在入栈出栈时的不小心而将这些数据、代码意外地改写,将会引发一连串的错误。当然也有可能是故意的改写。

    比如说在CPU中有记录栈顶上限和下限的寄存器,我们可以通过填写这些寄存器来指定栈空间的范围,然后,CPU 在执行push指令的时候靠检测栈顶上限寄存器,在执行pop指令的时候靠检测栈顶下限寄存器保证不会超界。实际情况 : 8086CPU中并没有这样的寄存器。

    8086 CPU不保证对栈的操作不会超界。这就是说,8086 CPU只知道栈顶在何处(由SS:SP指示),而不知道读者安排的栈空间有多大。这点就好像,CPU只知道当前要执行的指令在何处(由CS:SP指示)而不知道读者要执行的指令有多少。

    8086 CPU的工作机理,只考虑当前的情况:

    • 当前栈顶在何处
    • 当前要执行的指令是哪一条

    我们在编程的时候要自己注意栈顶超界的问题,要根据可能用到的最大栈空间,来安排栈的大小,防止入栈的数据太多而导致的超界,执行出栈操作的时候也要注意,以防栈空的时候继续出栈而导致的超界。

    push、pop指令

    push和pop指会的格式

    • push内存单元:将一个内存单元处的字入栈(栈操作都是以字为单位)
    • pop内存单元:出栈,用一个内存字单元接收出栈的数据例如:
      • push [0]
      • pop [2]

    指令执行时,CPU要知道内存单元的地址,可以在push、pop指令中给出内存单元的偏移地址,段地址在指令执行时,CPU从ds中取得。

    练习

    将10000H~1000FH这段空间当作栈,初始状态是空间,将AX、BX、DS中的数据入栈

    mov ax, 1000H
    mov ss, ax ;设置栈的段地址,SS=1000H,不能直接向段寄存器SS送入数据,所以用ax中转。
    mov sp, 0010H ;设置栈顶的偏移地址,国为栈为空,所以SP=0010H。以上三条指令设置栈顶地址。编程中要自己注意栈的大小。
    push ax
    push bx
    push ds ; push可以直接操作段寄存器
    

    C语言Main函数调用其他函数对临时数据的操作

    • 将10000H~1000FH这段空间当作栈,初始状态是空的
    • Main函数中有AX,BX局部变量
    • 开始调用A函数前,将AX、BX中的数据入栈
    • 然后将AX、BX清零
    • 调用A函数
    • 返回A函数结果
    • 从栈中恢复AX、BX原来的内容

    从上面的程序我们看到,用栈来暂存以后需要恢复的寄存器中的内容时,出栈的顺序要和入栈的顺序相反,因为最后入栈的寄存器的内容在栈顶,所以在恢复时,要最先出栈。

    push和pop指令同mov指令不同,CPU执行mov指令只需一步操作,就是传送,而执行push、 pop指令却需要两步操作。
    执行push时,先改变SP,后向SS:SP处传送。
    执行pop时,先读取SS:SP处的数据,后改变SP。

    栈段

    • 对于8086PC机,在编程时我们可以根据需要,将一组内存单元定义为一个段。
    • 我们可以将长度为N(N <=64K)的一组地址连续、起始地址为16的倍数的内存单元,当作栈来用,从而定义了一个栈段。
    • 比如我们将10010H~1001FH 这段长度为16字节的内存空间当作栈来用,以栈的方式进行访问。
    • 这段空间就可以成为栈段,段地址为1000H,大小为16字节。
    • 将一段内存当作栈段,仅仅是我们在编程时的一种安排,CPU并不会由于这种安排,就在执行push、pop 等栈操作指令时就自动地将我们定义的栈段当作栈空间来访问。
    • 如何使的如push、pop等栈操作指令访问我们定义的栈段呢?

    如果我们将10000H~IFFFFH这段空间当作栈段,初始状态是空的,此时,SS=1000H,SP=?

    我们将10000H~1FFFFH这段空间当作栈段,SS=1000H,栈空间大小为64KB,栈最底部的字单元地址为1000:FFFE(如果为空,指向下一个栈段)。任意时刻,SS:SP指向栈顶,当栈中只有一个元素的时候,SS=1000H,SP=FFFEH

    栈为空时,就相当于栈中唯一的元素出栈,出栈后,SP=SP+2。SP原来为FFFEH,加2后SP=0,所以,当栈为空的时候,SS=1000H,SP=0。

    换个角度看,任意时刻,SS:SP指向栈顶元素,当栈为空的时候,栈中没有元素,也就不存在栈顶元素,所以SS:SP只能指向栈的最底部单元下面的单元,该单元的偏移地址为栈最底部的字单元的偏移地址+2 ,栈最底部字单元的地址为1000:FFFE,所以栈空时,SP=0000H。

    段的综述

    我们可以将一段内存定义为一个段,用一个段地址指示段,用偏移地址访问段内的单元。这完全是我们自己的安排。

    • 我们可以用一个段存放数据,将它定义为“数据段”
    • 我们可以用一个段存放代码,将它定义为“代码段”
    • 我们可以用一个段当作栈,将它定义为“栈段”

    我们可以这样安排,但若要让CPU按照我们的安排来访问这些段,就要对于数据段,将它的段地址放在 DS中,用mov、add、sub等访问内存单元的指令时,CPU就将我们定义的数据段中的内容当作数据段来访问

    对于代码段,将它的段地址放在 CS中将段中第一条指令的偏移地址放在IP中,这样CPU就将执行我们定义的代码.
    对于栈段,将它的段地址放在SS中,将栈顶单元的偏移地置放在SP 中,这样CPU在需要进行栈操作的时候,比如执push、pop指令等,就将我们定义的栈段当作栈空间来用

    可见,不管我们如何安排,CPU将内存中的某段内存当作代码,是因为CS:IP指向了那里,CPU将某段内存当作栈,是因为SS:IP 指向了那里。

    我们一定要清楚,什么是我们的安排,以及如何让CPU按我们的安排行事。要非常的清楚CPU的工作机理,才能在控制CPU来按照我们的安排运行的时候做到游刀有余。

    一段内存,可以既是代码的存储空间又是数据的存储空间,还可以是栈空间,也可以什么也不是(也可以是漏洞点)。
    关键在于CPU中寄存器的设置,即 :CS、IP、SS、SP、DS的指向。

    汇编程序

    编写过程

    • 编写(文本编辑器:notepad++,sublime等)
    • 编译连接:使用汇编语言编译程序(MASM.EXE)对源程序文件中的源程序进行编译,产生目标文件。再用连接程序(LINK.EXE)对目标文件进行连接,生成可在操作系统中直接运行的可执行文件。
    • 可执行文件中包含两部分内容:
      • 程序(从原程序中的汇编指令翻译过来的机器码)和数据(源程序中定义的数据)
      • 相关的描述信息(比如: 程序有多大、要占多少内存空间等)
    • 执行可执行程序:操作系统依照可执行文件中的描述信息,将可执行文件中的机器码和数据加载入内存,并进行相关的初始化(比如:设置CS:IP指向第一条要执行的指令),然后由CPU执行程序。

    源程序

    • 汇编指令:有对应的机器码的指令,可以被编译为机器指令,最终为CPU所执行。
    • 伪指令:没有对应的机器码的指令,最终不被CPU所执行。伪指令是由编译器来执行的指令,编译器根据伪指令来进行相关的编译工作。
      • segment和ends的功能是定义一个段,segment说明一个段开始,ends说明一个段结束。
      • 一个段必须有一个名称来标识,使用格式为
        • 段名 segmen
        • 段名 ends
      • 一个汇编程序是由多个段组成的,这些段被用来存放代码、数据或当作栈空间来使用。
      • 一个有意义的汇编程序中至少要有一个段,这个段用来存放代码。没有代码段,就算有数据段也无法执行
      • End是一个汇编程序的结束标记,编译器在编译汇编程序的过程中,如果碰到了伪指令end,就结束对程序的编译
      • 如果程序写完了,要在结尾处加上伪指令End,否则,编译器在编译程序时,无法知道程序在何处结束。
      • 不要搞混了end和ends
      • assume:含义为“假设”
      • 它假设某一段寄存器和程序中的某一个用segment … ends 定义的段相关联。
      • 通过assume说明这种关联,在需要的情况下,编译程序可以将段寄存器和某一个具体的段相联系。

    我们可以将源程序文件中的所有内容称为源程序,将源程序中最终由计算机执行处理的指令或数据,称为程序
    程序最先以汇编指令的形式存在源程序中,经编译、连接后转变为机器码,存储在可执行文件中。

    标号

    • 一个标号指代了一个地址。
    • codesg:放在segment的前面,作为一个段的名称,这个段的名称最终将被编译、连接程序处理为一个段的段地址。

    DOS中的程序运行

    DOS是一个单任务操作系统:

    一个程序P2在可执行文件中,则必须有一个正在运行的程序P1,将P2从可执行文件中加载入内存后,将CPU的控制权交给P2,P2才能得以运行。P2开始运行后,P1暂停运行。
    而当P2运行完毕后,应该将CPU的控制权交还给使它得以运行的程序P1,此后,P1继续运行。

    程序返回

    我们的程序最先以汇编指令的形式存在源程序中,经编译、连接后转变为机器码,存储在可执行文件中,那么,它怎样得到运行呢?一个程序结束后,将CPU的控制权交还给使它得以运行的程序,我们称这个过程为 : 程序返回。

    程序如何返回?

    mov ax, 4c00H
    int 21H

    这两条指令所实现的功能就是程序返回。

    编译连接的作用

    • 当源程序很大时,可以将它分为多个源程序文件来编译,每个源程序编译成为目标文件后,再用连接程序将它们连接到一起,生成一个可执行文件。
    • 程序中调用了某个库文件中的子程序,需要将这个库文件和该程序生成的目标文件连接到一起,生成一个可执行文件。
    • 一个源程序编译后,得到了存有机器码的目标文件,目标文件中的有些内容还不能直接用来生成可执行文件,连接程序将这些内容处理为最终的可执行信息。所以,在只有一个源程序文件,而又不需要调用某个库中的子程序的情况下,也必须用连接程序对目标文件进行处理,生成可执行文件。
    • 对于连接的过程,可执行文件是我们想要得到的最终结果。

    汇编语言编程要用到 : 编辑器 (Edit) 、编译器 (masm)、连接器(link) 、调试工具 (debug) 等所有工具,而这些工具都是在操作系统之上运行的程序,所以学习的过程必须在操作系统的环境中进行。

    可执行文件中的程序装入内存并运行的原理

    在DOS中,可执行文件中的程序P1若要运行,必须有一个正在运行的程序P2,将P1从可执行文件中加载入内存,将CPU的控制权交给它,P1才能得以运行。
    当P1运行完毕后,应该将CPU的控制权交还给使它得以运行的程序P2。

    操作系统的外壳(Shell)

    操作系统是由多个功能模块组成的庞大、复杂的软件系统。任何通用的操作系统,都要提供一个称为shell(外壳)的程序,用户(操作人员)使用这个程序来操作计算机系统工作
    DOS中有一个程序command.com,这个程序在DOS中称为命令解释器,也就是DOS系统的shell

    我们在DOS中直接执行exe程序时,是正在运行的command将exe程序加载入内存。
    command设置CPU的CS:IP指向程序的第一条指令(即程序的入口),从而使程序得以运行。
    程序运行结束后,返回到command中,CPU继续运行command。

    DEBUG

    为了观察程序的运行过程 ,我们可以使用Debug。
    Debug可以将程序加载入内存,设置CS:IP指向程序的入口,但Debug并不放弃对CPU 的控制(中断),这样,我们就可以使用Debug的相关命令来单步执行程序查看每条指令指令的执行结果。

    在DOS系统中.exe文件中的程序的加载过程

    也就是CS寄存器的值比DS寄存器的值多10H的原因,数据从DS开始,代码从CS开始执行

    总而言之:

    1. 程序加载后,ds中存放着程序所在内存区的段地址,这个内存区的偏移地址为0,则程序所在的内存区的地址为: ds:0
    2. 这个内存区的前256个字节中存放的是PSP,dos用来和程序进行通信。
    3. 从256字节处向后的空间存放的是程序。
    4. 所以,我们从ds中可以得到PSP的段地址SA,PSP的偏移地址为0,则物理地址为SAX16+0。
    5. 因为PSP占256 (100H) 字节,所以程序的物理地址是:
      SA * 16 + 0 + 256 = SA * 16 + 16 * 16 = (SA + 16) * 16 + 0

    int 21执行后,显示“Program terminatednormally’,返回到Debug中。
    表示程序正常结束(注意,要使用P命令执行int 21)。

    -g 偏移地址:执行到偏移地址暂停
    -p:跳到下一个未循环的位置

    [BX]和loop指令

    [BX]是什么?

    和[0]有些类似,[0]表示内存单元,它的偏移地址是0。要完整的描述一个内存单元,需要两种信息:

    1. 内存单元的地址
    2. 内存单元的长度(类型)

    我们用[0]表示一个内存单元时,0表示单元的偏移地址,段地址默认在ds中,单元的长度(类型)可以由具体指令中的其他操作对象(比如说寄存器)指出.如AX,AL。

    [BX]同样也表示一个内存单元,它的偏移地址在BX中,比如下面的指令:

    mov ax,[BX]
    mov al,[BX]

    不能直接给其它寄存器设置偏移地址(会默认为值),而需要通过[BX]转换

    mov ax, [2] ; 假设ss:2的值为5555H,ax不会设置为5555H,而会设置为2

    Loop指令

    指令的格式是:loop 标号,CPU执行loop指令的时候,要进行两步操作:

    1. (cx) = (cx) – 1;
    2. 判断cx中的值,不为零则转至标号处执行程序,如果为零则向下执行。
    assume cs:codesg
    
    codesg segment
    
    dslbq:  mov ax, 2
            mov cx, 11
         s: add ax,ax
            loop s       
    
            mov ax, 4c00H
            int 21H
    codesg ends
    
    end dslbq

    在汇编语言中,标号代表一个地址,此代码中有一个标号s。它实际上标识了一个地址,这个地址处有多条指: add ax,ax

    而在汇编源程序中,数据不能以字母开头,所以要在前面加0。

    段前缀

    指令“mov ax,[bx]”中,内存单元的偏移地址由bx给出,而段地址默认在ds中。我们可以在访问内存单元的指令中显式地给出内存单元的段地址所在的段寄存器。这些出现在访问内存单元的指令中,用于显式地指明内存单元的段地址的“ds:”、“cs:”、“ss:”或“es:”,在汇编语言中称为段前缀。

    一段安全的空间

    在8086模式中,随意向一段内存空间写入内容是很危险的 ,因为这段空间中可能存放着重要的系统数据或代码。

    我们在不能确定一段内存空间中是否存放着重要的放据或代码的时候,不能随意向其中写入内容。
    不要忘记,我们是在操作系统的环境中工作,操作系统管理所有的资源,也包括内存。

    同样不能忘记,我们正在学习的是汇编语言,要通过它来获得底层的编程体验,理解计算机底层的基本工作机理。
    所以我们尽量直接对硬件编程,而不去理会操作系统。

    我们似乎面临一种选择,是在操作系统中安全、规矩地编程,还是自由、直接地用汇编语言去操作真实的硬件,了解那些早己被层层系统软件掩盖的真相?
    在大部分的情况下,我们选择后者,除非我们就是在学习操作系统本身的内容。

    我们在纯DOS方式(实模式)下,可以不理会DOS,直接用汇编语言去操作真实的硬件,因为运行在CPU实模式下的DOS,没有能力对硬件系统进行全面、严格地管理。

    但在Windows XP\2000、UNIX这些运行于CPU保护模式下的操作系统中,不理会操作系统,用汇编语言去操作真实的硬件,是根本不可能的。
    硬件已被这些操作系统利用CPU保护模式所提供的功能全面而严格地管理了

    在一般的PC机中,DOS方式下,DOS和其他合法的程序一般都不会使用0:200~0:2FF( 0:200h~0:2FFh)的256 个字节的空间。所以,我们使用这段空间是安全的。

    多个段

    程序第一行中的 “dw”的含义是定义字型数据。dw即define word。假设我们使用dw定义了8个字型数据(数据之间以逗号分隔),它们所占的内存空间的大小为16个字节。程序中的指令就要对这8个数据进行累加,可这8个数据在哪里呢?由于它们在代码段中,程序在运行的时候CS中存放代码段的段地址,所以我们可以从CS中得到它们的段地址。

    这8个数据的偏移地址是多少呢?因为用dw定义的数据处于代码段的最开始,所以偏移地址为0,这8 个数据就在代码段的偏移0、2、4、6、8、A、C、E处。程序运行时,它们的地址就是CS:0、CS:2、CS:4、CS:6、CS:8、CS:A、CS:C、CS:E。

    我们在程序中用到了数据和栈,我们将数据、栈和代码都放到了一个段里面。我们在编程的时候要注意何处是数据,何处是栈,何处是代码。
    这样做显然有两个问题:
    (1)把它们放到一个段中使程序显得混乱;
    (2)前面程序中处理的数据很少,用到的栈空间也小,加上没有多长的代码,放到一个段里面没有问题。

    但如果数据、栈和代码需要的空间超过64KB,就不能放在一个段中(一个段的容量不能大于64 KB,是我们在学习中所用的8086模式的限制,并不是所有的处理器都这样)。
    所以,我们应该考虑用多个段来存放数据、代码和栈。

    我们用和定义代码段一样的方法来定义多个段,然后在这些段里面定义需要的数据,或通过定义数据来取得栈空间。

    assume cs:code, ds:data, ss:stack
    
    code segment
    
    start:      mov ax, stack
                 mov ss, ax
                 mov sp, 16
                 mov ax, data
                 mov ds, ax
                 push ds:[0]
                 push ds:[2]
                 pop ds:[2]
                 pop ds:[0]
                 
                 mov ax, 4c00h
                 int 21h
    code ends
    
    data segment
          dw    0123H, 0456H
    data ends
    
    stack segment
          dw    0, 0
    stack ends     
    
    end start

    我们在源程序中为这三个段起了具有含义的名称,用来存放数据的段我们将其命名为“data”,用来放代码的段我们将其命名为“code”,用来作栈空间的命名为“stack”。
    但CPU看的懂这些名称吗?答案是否,若要CPU按照我们的安排行事,就要用机器指令控制它,源程序中的汇编指令是CPU要执行的内容。

    那么,CPU如何知道去执行它们?
    我们在源程序的最后用“end start”说明了程序的入口,这个入口将被写入可执行文件的描述信息,可执行文件中的程序被加载入内存后,CPU的CS:IP被设置指向这个入口,从而开始执行程序中的第一条指令。
    标号“start”在“code”段中,这样CPU就将code段中的内容当作指令来执行了。

    我们在code段中,使用指令:

    mov ax,stack
    mov ss,ax
    mov sp,16


    设置ss指向stack,设置ss:sp指向stack:16, CPU 执行这些指令后,将把stack段当做栈空间来用。
    CPU若要访问data段中的数据,则可用 ds 指向 data 段,用其他的寄存器(如:bx)来存放 data段中数据的偏移地址。

    总之,CPU到底如何处理我们定义的段中的内容,是当作指令执行,当作数据访问,还是当作栈空间,完全是靠程序中具体的汇编指令,和汇编指令对CS:IP、SS:SP、DS等寄存器的设置来决定的。

    定位内存地址

    前面,我们用[0]、[bx]的方法,在访问内存的指令中,定位内存单元的地址。现在我们主要讲解一些更灵活的定位内存地址的方法和相关的编程方法。我们的讲解将通过具体的问题来进行:

    首先我们介绍两条指令and和or,因为我们下面的例程中要用到它们。

    and 指令

    逻辑与指令,按位进行与运算。

    如 mov al, 01100011B
    and al, 00111011B
    执行后:al = 00100011B

    通过该指令可将操作对象的相应位设为0,其他位不变。

    例如:
    将al的第6位设为0:and al, 10111111B
    将al的第7位设为0:and al, 01111111B
    将al的第0位设为0:and al, 11111110B

    or指令

    逻辑或指令,按位进行或运算。

    如 mov al, 01100011B
       and al, 00111011B
    执行后:al = 01111011B

    通过该指令可将操作对象的相应位设为1,其他位不变。

    例如:
    将al的第6位设为1:or al, 01000000B
    将al的第7位设为1:or al, 10000000B
    将al的第0位设为1:or al, 00000001B
    

    [bx + idata]

    我们可以用[bx]的方式来指明一个内存单元, 我们还可以用一种更为灵活的方式来指明内存单元:[bx+idata]表示一个内存单元,它的偏移地址为(bx)+idata(bx中的数值加上idata)。

    我们看一下指令mov ax,[bx+200]的含义:
    将一个内存单元的内容送入ax,这个内存单元的长度为2字节(字单元),存放一个字,偏移地址为bx中的数值加上200,段地址在ds中。
    数学化的描述为: (ax)=((ds)*16+(bx)+200)

    指令mov ax,[bx+200]也可以写成如下格式(常用):

    • mov ax,[200+bx]
    • mov ax,200[bx]
    • mov ax,[bx].200

    用[bx+idata]的方式进行数组的处理

    有了[bx+idata]这种表示内存单元的方式,我们就可以用更高级的结构来看待所要处理的数据。

    SI和DI

    SI和DI是8086CPU中和bx功能相近的寄存器,但是SI和DI不能够分成两个8 位寄存器来使用。

    下面的三组指令实现了相同的功能:

    mov bx,0
    mov ax,[bx]
    
    mov si,0
    mov ax,[si]
    
    mov di,0
    mov ax,[di]

    下面的三组指令也实现了相同的功能:

    mov bx,0
    mov ax,[bx+123]
    
    mov si,0
    mov ax,[si+123]
    
    mov di,0
    mov ax,[di+123]

    我们用ds:si 指向要复制的源始字符串,用 ds:di 指向复制的目的空间,然后用一个循环来完成复制。

    assume cs:codesg,ds:datasg
        datasg segment 
          db 'welcome to masm!'
          db '................'
        datasg ends
    
    codesg segment
    start: mov ax,datasg
             mov ds,ax
             mov si,0
             mov di,16
             mov cx,8
        s:  mov ax,[si]
             mov [di],ax
             add si,2
             add di,2
             loop s
    
             mov ax,4c00h
             int 21h
    codesg ends
    end start

    注意,在程序中,我们用16位寄存器进行内存单元之间的数据传送,一次复制 2 个字节,一共循环8次。

    我们可以利用[bx(si或di)+idata]的方式,来使程序变得简洁,程序如下:

    codesg segment
    start: mov ax,datasg
             mov ds,ax
             mov si,0
             mov cx,8
        s:  mov ax,0[si]
             mov 16[si],ax
             add si,2
             loop s
             mov ax,4c00h
             int 21h
    codesg ends
    end start

    [bx+si]和[bx+di]

    前面,我们用[bx(si或di)]和[bx(si或di)+idata] 的方式来指明一个内存单元,我们还可以用更灵活的方式:

    [bx+si]
    [bx+di]

    [bx+si]表示一个内存单元,它的偏移地址为(bx)+(si)(即bx中的数值加上si中的数值)。

    不同的寻址方式的灵活应用

    将datasg段中每个单词的头一个字母改为大写字母

    assume cs:codesg,ds:datasg
    datasg segment
       db '1. file          '
       db '2. edit         '
       db '3. search     '
       db '4. view        '
       db '5. options    '
       db '6. help        '
    datasg ends
    
    codesg segment
     start:……
    codesg ends
    end start

    datasg中的数据的存储结构,如图:

    我们可以看到:在datasg中定义了6个字符串,每个长度为16字节。(注意,为了直观,每个字符串的后面都加上了空格符,以使它们的长度刚好为16字节)

    我们需要进行6次循环,用一个变量R定位行,用常量3 定位列。处理的过程如下:
    BX先存放第一行的地址 mov cx,6;因为总共有六行 s: 改变第BX行,第3列的字母为大写 改变BX的值是它指向下一行的地址 loop

    我们用bx作变量,定位每行的起始地址,用3定位要修改的列,用[bx+idata]的方式来对目标单元进行寻址

    assume cs:codesg,ds:datasg
    
    datasg segment
    	db '1. file         '
    	db '2. edit         '
    	db '3. search       '
    	db '4. view         '
    	db '5. options      '
    	db '6. help         '
    datasg ends
    
    codesg segment
    start: mov ax, datasg
           mov ds, ax
           mov bx, 0
    
           mov cx, 6
        s: mov al, [bx+3]     ;注意,单位是字节,所以是al
           and al, 11011111b  ;使第五位为零,这样呢,就确定了大写!
           mov [bx+3], al
           add bx, 16
           loop s
    
           mov ax, 4c00h
           int 21h
    codesg ends
    end start

    一般来说,在需要暂存数据的时候,我们都应该使用栈,回忆一下,栈空间在内存中,采用相关的指令,如:push、pop等,可对其进行特殊的操作。

    数据处理

    我们知道,计算机是进行数据处理、运算的机器,那么有两个基本的问题就包含在其中:

    1. 处理的数据在什么地方?
    2. 要处理的数据有多长?

    这两个问题,在机器指令中必须给以明确或隐含的说明,否则计算机就无法工作。

    我们就要针对8086CPU对这两个基本问题进行讨论。虽然讨论是在8086CPU的基础上进行的,但是这两个基本问题却是普遍的,对任何一个处理器都存在。

    在8086CPU 中,只有这4个寄存器(bx、bp、si、di)可以用在“[…]” 中来进行内存单元的寻址。
    在“[…]” 中,这4个寄存器(bx、bp、si、di)可以单个出现,或只能以四种组合出现:
    bx和si、bx和di、bp和si、bp和di

    错误的用法

    mov ax,[bx+bp]
    mov ax,[si+di]

    正确的用法:

    mov ax,[bx]
     mov ax,[si]
     mov ax,[di]
     mov ax,[bp]
     mov ax,[bx+si]
     mov ax,[bx+di]
     mov ax,[bp+si]
     mov ax,[bp+di]
     mov ax,[bx+si+idata]
     mov ax,[bx+di+idata]
     mov ax,[bp+si+idata]
     mov ax,[bp+di+idata]
    
    

    只要在[…]中使用寄存器bp,而指令中没有显性的给出段地址,段地址就默认在ss中。比如:

    mov ax,[bp]               含义: (ax)=((ss)*16+(bp))
    mov ax,[bp+idata]      含义:(ax)=((ss)*16+(bp)+idata)
    mov ax,[bp+si]           含义:(ax)=((ss)*16+(bp)+(si))
    mov ax,[bp+si+idata]  含义:(ax)=((ss)*16+(bp)+(si)+idata)

    绝大部分机器指令都是进行数据处理的指令,处理大致可分为三类:
    读取、写入、运算
    在机器指令这一层来讲,并不关心数据的值是多少,而关心指令执行前一刻,它将要处理的数据所在的位置。

    指令在执行前,所要处理的数据可以在三个地方:
    CPU内部、内存、端口

    汇编语言中用三个概念来表达数据的位置。

    • 立即数(idata)
    • 寄存器
    • 段地址(SA)和偏移地址(EA)

    立即数(idata)

    对于直接包含在机器指令中的数据(执行前在cPu 的指令缓冲器中),在汇编语言中称为:立即数(idata ) ,在汇编指令中直接给出。例如:
    mov ax,1
    对应机器码:B80100
    执行结果:(ax) = 1

    寄存器

    指令要处理的数据在寄存器中,在汇编指令中给出相应的寄存器名。例如:
    mov ax,bx
    对应机器码:89D8
    执行结果:(ax) = (bx)

    段地址(SA)和偏移地址(EA)

    指令要处理的数据在内存中,在汇编指令中可用[X]的格式给出EA,SA在某个段寄存器中。

    • 存放段地址的寄存器可以是默认的。
    • 存放段地址的寄存器也可以显性的给出。

    显性的给出存放段地址的寄存器

    mov ax,ds:[bp]          ;含义:(ax)=((ds)*16+(bp)) ,默认未指定时,bp跟ss
    mov ax,es:[bx]          ;含义:(ax)=((es)*16+(bx)),默认未指定时,bx跟ds
    mov ax,ss:[bx+si]      ;含义:(ax)=((ss)*16+(bx)+(si))
    mov ax,cs:[bx+si+8]  含义:(ax)=((cs)*16+(bx)+(si)+8)

    当数据存放在内存中的时候,我们可以用多种方式来给定这个内存单元的偏移地址,这种定位内存单元的方法一般被称为寻址方式。

    指令要处理的数据有多长?

    8086CPU的指令,可以处理两种尺寸的数据,byte和word。所以在机器指令中要指明,指令进行的是字操作还是字节操作。

    对于这个问题,汇编语言中用以下方法处理:

    • 通过寄存器名指明要处理的数据的尺寸,下面的指令中,寄存器指明了指令进行的是字操作:
      mov ax,1
      mov bx,ds:[0]
      mov ds,ax
      mov ds:[0],ax
      inc ax

      add ax,1000
    • 在没有寄存器名存在的情况下,用操作符X ptr指明内存单元的长度,X在汇编指令中可以为word或byte
      mov word ptr ds:[0],1
      inc word ptr [bx]
      inc word ptr ds:[0]
      add word ptr [bx],2
      mov byte ptr ds:[0],1
      inc byte ptr [bx]
      inc byte ptr ds:[0]
      add byte ptr [bx],2

      在没有寄存器参与的内存单元访问指令中,用word ptr或byte ptr显性地指明所要访问的内存单元的长度是很必要的。否则,CPU无法得知所要访问的单元是字单元,还是字节单元。
    • 有些指令默认了访问的是字单元还是字节单元,
      比如:push [1000H]就不用指明访问的是字单元还是字节单元,
      因为push指令只进行字操作

    DIV指令

    div是除法指令(division),使用div作除法的时候:

    • 除数:8位或16位,在寄存器或内存单元中
    • 被除数:(默认)放在AX 或 DX和AX中

    当除数为8位,被除数应该为16位(AX),当除数为16位时,被除数应该为 32位(DX+AX)。

    div指令格式:

    div reg
    div 内存单元

    div指令示例:

    div byte ptr ds:[0] ;含义为:(al) = (ax) / ((ds)*16+0)的商;(ah) = (ax) / ((ds)*16+0)的余数
    div word ptr es:[0] ;含义为:(ax) = [(dx)*10000H+(ax)]/((ds)*16+0)的商;(dx) = [(dx)*10000H+(ax)]/((ds)*16+0)的余数
    

    伪指令 dd

    dd是用来定义dword (double word双字)型数据的。

    dup

    dup是一个操作符,在汇编语言中同db、dw、dd 等一样,也是由编译器识别处理的符号。
    它是和db、dw、dd 等数据定义伪指令配合使用的,用来进行数据的重复。

    dup示例

    db 3 dup (0)
    定义了3个字节,它们的值都是0,
    相当于 db 0,0,0

    db 3 dup (0,1,2)
    定义了9个字节,它们是
    0、1、2、0、1、2、0、1、2,
    相当于 db 0,1,2,0,1,2,0,1,2

    db 3 dup (‘abc’,’ABC’)
    定义了18个字节,它们是
    ‘abcABCabcABCabcABC’,
    相当于db ‘abcABCabcABCabcABC’

    可见,dup的使用格式如下:

    • db 重复的次数 dup (重复的字节型数据)
    • dw 重复的次数 dup (重复的字型数据)
    • dd 重复的次数 dup (重复的双字数据)

    dup是一个十分有用的操作符
    比如我们要定义一个容量为 200 个字节的栈段,如果不用dup,则必须用这样的格式:

    stack segment
    dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
    dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
    dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
    dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
    dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
    stack ends

    有了dup就可以轻松解决。如下:

    stack segment
    db 200 dup (0)
    stack ends

    转移指令

    8086CPU的转移指令分为以下几类:

    • 无条件转移指令 (如:jmp)
    • 条件转移指令
    • 循环指令(如:loop)
    • 过程
    • 中断

    操作符offset

    操作符offset在汇编语言中是由编译器处理的符号,它的功能是取得标号的偏移地址。
    比如下面的程序:

    assume cs:codesg
    codeseg segment
    start:mov ax,offset start ; 相当于 mov ax,0 
    s:mov ax,offset s ; 相当于mov ax,3 
    codesg ends 
    end start

    jmp指令

    mp为无条件转移,可以只修改IP,也可以同时修改CS和IP。
    jmp指令要给出两种信息:

    • 转移的目的地址
    • 转移的距离(段间转移、段内短转移,段内近转移)

    jmp short 标号(转到标号处执行指令)
    这种格式的 jmp 指令实现的是段内短转移,它对IP的修改范围为 -128~127,也就是说,它向前转移时可以最多越过128个字节,向后转移可以最多越过127个字节。

    assume cs:codesg
    codesg segment
      start:mov ax,0
              jmp short s
              add ax,1
           s:inc ax
    codesg ends
    end start

    左面的程序执行后, ax中的值为 1 ,因为执行 jmp short s 后 ,越过了add ax,1 ,IP 指向了标号 s处的 inc ax。也就是说,程序只进行了一次ax加1操作。

    汇编指令与机器码

    汇编指令与机器码的对应示例

    可以看到,在一般的汇编指令中,汇编指令中的idata(立即数),不论它是表示一个数据还是内存单元的偏移地址,都会在对应的机器指令中出现,因为CPU执行的是机器指令,它必须要处理这些数据或地址。

    对照汇编源程序后我们可以看到,Debug 将 jmp short s 中的 s 表示为inc ax 指令的偏移地址 8 ,并将jmp short s 表示为 jmp 0008 ,表示转移到cs:0008处。
    但是我们观察对应的机器码,却意外地发现了一些问题:

    jmp 0008 ( Debug 中的表示)或jmp short s (汇编语言中的表示)所对应的机器码为EB 03,注意,这个机器码中竟不包含转移的目的地址。
    这意味着,CPU 在执行EB 03的时候,并不知道转移目的地址。

    这说明在机器指令中并不包含转移的目的地址。
    如果机器指令中不包含目的地址的话,那么,也就是说 CPU不需要这个目的地址就可以实现对IP的修改。

    jmp short s指令的读取和执行过程:

    1. (CS)=0BBDH,(IP)=0006,CS:IP指向EB 03(jmp short s的机器码);
    2. 读取指令码EB 03进入指令缓冲器;
    3. (IP)=(IP)+所读取指令的长度=(IP)+2=0008,CS:IP指向add ax,1;
    4. CPU指行指令缓冲器中的指令EB 03;
    5. 指令EB 03执行后,(IP)=000BH,CS:IP指向inc ax。

    实际上,指令“jmp short 标号”的功能为(IP)=(IP)+8位位移。

    • 8位位移=“标号”处的地址-jmp指令后的第一个字节的地址;
    • short指明此处的位移为8位位移;
    • 8位位移的范围为-128~127,用补码表示
    • 8位位移由编译程序在编译时算出。

    还有一种和指令“jmp short 标号”功能相近的指令格式:
    jmp near ptr 标号
    它实现的时段内近转移。
    指令“jmp near ptr 标号”的功能为:
    (IP)=(IP)+16位位移。

    指令“jmp near ptr 标号”的说明:

    1. 16位位移=“标号”处的地址-jmp指令后的第一个字节的地址;
    2. near ptr指明此处的位移为16位位移,进行的是段内近转移;
    3. 16位位移的范围为
    4. -32769~32767,用补码表示;
    5. 16位位移由编译程序在编译时算出。

    Call和ret指令

    想想程序之间的加载返回过程。call和ret 指令都是转移指令,它们都修改IP,或同时修改CS和IP。
    它们经常被共同用来实现自程序的设计。我们讲解call和ret 指令的原理。

    ret 和 retf

    ret指令用栈中的数据,修改IP的内容,从而实现近转移。
    CPU执行ret指令时,进行下面两步操作:

    (1)(IP)=((ss)*16+(sp))
    (2)(sp)=(sp)+2

    retf指令用栈中的数据,修改CS和IP的内容,从而实现远转移;
    CPU执行retf指令时,进行下面两步操作:

    (1)(IP)=((ss)*16+(sp))
    (2)(sp)=(sp)+2
    (3)(CS)=((ss)*16+(sp))
    (4)(sp)=(sp)+2

    可以看出,如果我们用汇编语法来解释ret和retf指令,则:
    CPU执行ret指令时,相当于进行:pop IP
    CPU执行retf指令时,相当于进行:pop IP pop CS

    call 指令

    call指令经常跟ret指令配合使用,因此CPU执行call指令,进行两步操作:

    • 将当前的 IP 或 CS和IP 压入栈中
    • 转移(jmp)

    call 指令不能实现短转移,除此之外,call指令实现转移的方法和 jmp 指令的原理相同。

    依据位移进行转移的call指令

    • call 标号(将当前的 IP 压栈后,转到标号处执行指令)
    • CPU执行此种格式的call指令时,进行如下的操作:
      • (sp) = (sp) – 2
        ((ss)*16+(sp)) = (IP)
      • (IP) = (IP) + 16位位移
    • call 标号
      • 16位位移=“标号”处的地址-call指令后的第一个字节的地址;
      • 16位位移的范围为 -32768~32767,用补码表示;
      • 16位位移由编译程序在编译时算出。

    从上面的描述中,可以看出,如果我们用汇编语法来解释此种格式的 call指令,则:CPU 执行指令“call 标号”时,相当于进行:

    push IP
    jmp near ptr 标号     

    转移的目的地址在指令中的call指令

    • 前面讲解的call指令,其对应的机器指令中并没有转移的目的地址 ,而是相对于当前IP的转移位移。
    • 指令“call far ptr 标号”实现的是段间转移。
    • CPU执行“call far ptr 标号”这种格式的call指令时的操作:
    (sp) = (sp) – 2
            ((ss) ×16+(sp)) = (CS)
            (sp) = (sp) – 2
            ((ss) ×16+(sp)) = (IP)
    (CS) = 标号所在的段地址
        (IP) = 标号所在的偏移地址
    

    从上面的描述中可以看出,如果我们用汇编语法来解释此种格式的 call 指令,则:
    CPU 执行指令 “call far ptr 标号” 时,相当于进行:

    push CS
    push IP
    jmp far ptr 标号

    指令格式:call 16位寄存器

    • 功能:
      • (sp) = (sp) – 2
      • ((ss)*16+(sp)) = (IP)
      • (IP) = (16位寄存器)
    • 汇编语法解释此种格式的 call 指令,CPU执行call 16位reg时,相当于进行:
    push IP 
    jmp 16位寄存器

    转移地址在内存中的call指令有两种格式:

    • call word ptr 内存单元地址
    • call dword ptr 内存单元地址

    比如下面的指令:

     mov sp,10h
     mov ax,0123h
     mov ds:[0],ax
     call word ptr ds:[0] ;执行后,(IP)=0123H,(sp)=0EH

    call dword ptr 内存单元地址

    汇编语法解释:

    push CS
    push IP
    jmp dword ptr 内存单元地址

    比如,下面的指令:

    mov sp,10h
    mov ax,0123h
    mov ds:[0],ax
    mov word ptr ds:[2],0
    call dword ptr ds:[0] ; 执行后,(CS)=0,(IP)=0123H,(sp)=0CH

    mul 指令

    因下面要用到,我们介绍一下mul指令,mul是乘法指令,使用 mul 做乘法的时候:

    • 相乘的两个数:要么都是8位,要么都是16位。
    • 8 位: AL中和 8位寄存器或内存字节单元中;
    • 16 位: AX中和 16 位寄存器或内存字单元中。

    结果:

    • 8位:AX中
    • 16位:DX(高位)和AX(低位)中

    格式如下:

    • mul reg
    • mul 内存单元

    内存单元可以用不同的寻址方式给出,比如:

    • mul byte ptr ds:[0]
      含义为: (ax)=(al)((ds)16+0);
    • mul word ptr [bx+si+8]
      含义为:
      (ax)=(ax) * ((ds)16 + (bx) + (si) + 8)结果的低16位;
      (dx)=(ax) * ((ds)16 + (bx) + (si) + 8)结果的高16位;

    例如:

    • 计算100*10
      100和10小于255,可以做8位乘法,程序如下:
    mov al,100
    mov bl,10
    mul bl
    结果: (ax)=1000(03E8H)
    • 计算100*10000
      100小于255,可10000大于255,所以必须做16位乘法,程序如下:
    mov ax,100
    mov bx,10000
    mul bx ;结果: (ax)=4240H,(dx)=000FH
                   ;(F4240H=1000000)

    模块化程序设计

    • 从上面我们看到 ,call 与 ret 指令共同支持了汇编语言编程中的模块化设计。在实际编程中,程序的模块化是必不可少的。
    • 因为现实的问题比较复杂,对现实问题进行分析时,把它转化成为相互联系、不同层次的子问题,是必须的解决方法。
    • 而call和ret 指令对这种分析方法提供了程序实现上的支持。利用 call和ret指令,我们可以用简洁的方法,实现多个互相联系、功能独立的子程序来解决一个复杂的问题。
    • 子程序一般都要根据提供的参数处理一定的事务,处理后,将结果(返回值)提供给调用者。
    • 其实,我们讨论参数和返回值传递的问题,实际上就是在探讨,应该如何存储子程序需要的参数和产生的返回值。

    标志寄存器

    8086CPU的标志寄存器有16位,其中存储的信息通常被称为程序状态字(PSW)。
    我们己经使用过8086CPU的ax、bx、cx、dx、si、di、bp、sp、ip、cs、ss、ds、es等13个寄存器了。
    本章中的标志寄存器(以下简称为flag)是我们要学习的最后一个寄存器。

    flag 和其他寄存器不一样,其他寄存器是用来存放数据的,都是整个寄存器具有一个含义。而flag寄存器是按位起作用的,也就是说,它的每一位都有专门的含义,记录特定的信息。

    8086CPU的flag寄存器的结构:

    flag的1、3、5、12、13、14、15位在8086CPU中没有使用,不具有任何含义。而0、2、4、6、7、8、9、10、11位都具有特殊的含义。

    ZF标志

    flag的第6位是ZF(Zero Flag),零标志位。它记录相关指令执行后:

    • 结果为0 ,ZF = 1
    • 结果不为0,ZF = 0

    例如:

    mov ax,1
    sub ax,1

    指令执行后,结果为0,则ZF = 1。

    mov ax,2 
    sub ax,1

    指令执行后,结果为1,则ZF = 0。对于ZF的值,我们可以这样来看,ZF标记相关指令的计算结果是否为0,如果为0,则在ZF要记录下“是0”这样的肯定信息。
    指令:

    mov ax,1
    and ax,0

    执行后,结果为0,则ZF=1,表示“结果是0”。
    指令:

    mov ax,1
    or ax,0

    执行后,结果不为0,则ZF=0,表示“结果非0”。

    注意:
    在8086CPU的指令集中,有的指令的执行是影响标志寄存器的,比如:addsubmuldivincorand等,它们大都是运算指令(进行逻辑或算术运算);有的指令的执行对标志寄存器没有影响,比如:movpushpop等,它们大都是传送指令。我们在使用一条指令的时候,要注意这条指令的全部功能,其中包括,执行结果对标记寄存器的哪些标志位造成影响。

    PF标志

    flag的第2位是PF(Parity Flag):奇偶标志位。它记录指令执行后,结果的所有二进制位中1的个数:

    • 为偶数,PF = 1
    • 为奇数,PF = 0

    示例
    指令:

    mov al,1
    add al,10

    执行后,结果为00001011B,其中有3(奇数)个1,则PF=0;
    指令:

    mov al,1
    or al,10

    执行后,结果为00000011B,其中有2(偶数)个1,则PF=1;

    SF标志

    flag的第7位是SF(Symbol Flag),符号标志位。
    它记录指令执行后,

    • 结果为负,SF = 1
    • 结果为正,SF = 0

    示例

    mov al,10000001B
    add al,1
    结果: (al)=10000010B

    有符号数与补码

    • 我们知道计算机中通常用补码来表示有符号数据。计算机中的一个数据可以看作是有符号数,也可以看成是无符号数。
    • 比如:
      • 00000001B ,可以看作为无符号数 1 ,或有符号数+1;
      • 10000001B ,可以看作为无符号数129,也可以看作有符号数-127。
    • 这也就是说,对于同一个二进制数据,计算机可以将它当作无符号数据来运算,也可以当作有符号数据来运算。
    • 我们可以将add指令进行的运算当作无符号数的运算,那么add指令相当于计算129+1,结果为130(10000010B);
    • 也可以将add指令进行的运算当作有符号数的运算,那么add指令相当于计算-127+1,结果为-126(10000010B)。
    • 不管我们如何看待,CPU 在执行add等指令的时候,就已经包含了两种含义,也将得到用同一种信息来记录的两种结果。
    • 关键在于我们的程序需要哪一种结果。
    • SF 标志,就是CPU对有符号数运算结果的一种记录 ,它记录数据的正负。在我们将数据当作有符号数来运算的时候,可以通过它来得知结果的正负。
    • 如果我们将数据当作无符号数来运算,SF的值则没有意义,虽然相关的指令影响了它的值。
    • 这也就是说,CPU在执行 add 等指令时,是必然要影响到SF标志位的值的。
    • 至于我们需不需要这种影响,那就看我们如何看待指令所进行的运算了。
      mov al,10000001B add al,1
      执行后,结果为10000010B,SF=1, 表示:如果指令进行的是有符号数运算,那么 结果为负;
    • 再例如:
      mov al,10000001B
      add al,01111111B
      执行后,结果为0,SF=0

      表示:如果指令进行的是有符号数运 算,那么结果为非负。
    • 某此指令将影响标志寄存器中的多个标志位,这些被影响的标记位比较全面地记录了指令的执行结果,为相关的处理提供了所需的依据。
    • 比如指令sub al,al执行后,ZF、PF、SF等标志位都要受到影响,它们分别为:1、1、0。

    CF标志

    • flag的第0位是CF(Carry Flag),进位标志位。
    • 一般情况下,在进行无符号数运算的时候,它记录了运算结果的最高有效位向更高位的进位值,或从更高位的借位值。
    • 对于位数为N的无符号数来说,其对应的二进制信息的最高位,即第N-1位,的最高有效位,而假想存在的第N位,就是相对于最高有效位的更高位。

    我们知道,当两个数据相加的时候,有可能产生从最高有效位向更高位的进位。

    比如,两个8 位数据:98H+98H,将产生进位。由于这个进位值在8位数中无法保存,我们在前面的课程中,就只是简单地说这个进位值丢失了。

    其实CPU在运算的时候,并不丢弃这个进位值,而是记录在一个特殊的寄存器的某一位上。 8086CPU 就用flag的CF位来记录这个进位值。

    在Debug中,我们可以看到类似下面的信息:

    比如,下面的指令:

    mov al,98H
    add al,al ;执行后: (al)=30H,CF=1,
              ;CF记录了最高有效位向更高位的进位值
    add al,al ;执行后: (al)=30H,CF=0,
    • 另外一种情况,而当两个数据做减法的时候,有可能向更高位借位。
    • 比如,两个 8 位数据:97H-98H,将产生借位,借位后,相当于计算197H-98H。
    • 而flag的CF位也可以用来记录这个借位值。
    • 比如,两个 8 位数据:97H-98H,将产生借位,借位后,相当于计算197H-98H。
    • 而flag的CF位也可以用来记录这个借位值。

    OF标志(Overflow Flag)

    我们先来谈谈溢出的问题。在进行有符号数运算的时候,如结果超过了机器所能表示的范围称为溢出。那么,什么是机器所能表示的范围呢?比如:add al,3 ,那么对于 8 位的有符号数据,机器所能表示的范围就是-128~127。

    如果运算结果超出了机器所能表达的范围,将产生溢出。
    注意,这里所讲的溢出,只是对有符号数运算而言。(就像进位只是相对于无符号数而言!)

    如果在进行有符号数运算时发生溢出,那么运算的结果将不正确。
    就上面的两个例子来说:

    mov al,98
    add al,99 

    add指令运算的结果是(al)=0C5H ,因为进行的是有符号数运算,所以 al中存储的是有符号数,而0C5H是有符号数-59的补码。

    如果我们用add 指令进行的是有符号数运算,则98+99=-59这样的结果让人无法接受。
    造成这种情况的原因,就是实际的结果 197,作为一个有符号数,在 8 位寄存器al中存放不下。
    由于在进行有符号数运算时,可能发生溢出而造成结果的错误。所以CPU需要对指令执行后是否产生溢出进行记录。因此有了OF

    记住,一定要注意CF和OF的区别:

    • CF是对无符号数运算有意义的标志位;
    • 而OF是对有符号数运算有意义的标志位。

    对于无符号数运算,CPU用CF位来记录是否产生了进位;对于有符号数运算,CPU 用 OF 位来记录是否产生了溢出,当然,还要用SF位来记录结果的符号。

    对于有无符号,计算机是分不清楚状况的,因此他必须两种都记载着,要怎么用,看的是你当他是什么!
    例如:

    mov al, 98d
    add al, 99d

    对于无符号数运算,98+99没有进位,CF=0;
    对于有符号数运算,98+99发生溢出,OF=1。

    adc指令

    adc是带进位加法指令 ,它利用了CF位上记录的进位值。

    • 格式: adc 操作对象1,操作对象2
    • 功能:操作对象1=操作对象1+操作对象2+CF
    • 比如:adc ax,bx 实现的功能是:(ax)=(ax)+(bx)+CF

    adc指令示例(一)

    mov ax,2 
    mov bx,1 
    sub bx,ax 
    adc ax,l

    执行后,(ax)=4。
    adc执行时,相当于计算: (ax)+1+CF=2+1+1=4。

    adc指令示例(二)

    mov ax,1
    add ax,ax
    adc ax,3

    执行后,(ax)=5。
    adc执行时,相当于计算: (ax)+3+CF=2+3+0=5。

    adc指令示例(三)

    mov al,98H 
    add al,al 
    adc al,3

    执行后,(ax)=34H。
    adc执行时,相当于计算: (ax)+3+CF=30H+3+1=34H。

    在执行 adc 指令的时候加上的 CF 的值的含义,由 adc指令前面的指令决定的,也就是说,关键在于所加上的CF值是被什么指令设置的。
    显然,如果CF 的值是被sub指令设置的,那么它的含义就是借位值;如果是被add指令设置的,那么它的含义就是进位值。

    sbb指令

    sbb(sub with brow)是带借位减法指令,它利用了CF位上记录的借位值。
    格式:sbb 操作对象1,操作对象2
    功能:
    操作对象1=操作对象1–操作对象2–CF
    比如:sbb ax,bx
    实现功能: (ax) = (ax) – (bx) – CF

    利用sbb指令我们可以对任意大的数据进行减法运算。
    计算003E100OH–00202000H,结果放在ax,bx中,程序如下:

    mov bx,1000H
    mov ax,003EH
    sub bx,2000H
    sbb ax,0020H

    sbb和adc是基于同样的思想设计的两条指令,在应用思路上和adc类似。在这里,我们就不再进行过多的讨论。通过学习这两条指令,我们可以进一步领会一下标志寄存器CF位的作用和意义。

    cmp指令

    cmp 是比较指令,功能相当于减法指令,只是不保存结果。cmp 指令执行后,将对标志寄存器产生影响。其他相关指令通过识别这些被影响的标志寄存器位来得知比较结果。

    cmp指令
    格式:cmp 操作对象1,操作对象2
    功能:计算操作对象1–操作对象2 但并不保存结果,仅仅根据计算结果对标志寄存器进行设置

    比如:cmp ax,ax
    做(ax)–(ax)的运算,结果为0,但并不在ax中保存,仅影响flag的相关各位。
    指令执行后:

    ZF=1,
    PF=1,
    SF=0,
    CF=0,
    OF=0。

    下面的指令:

    mov ax,8
    mov bx,3
    cmp ax,bx

    执行后:

    (ax) = 8
    ZF=0,
    PF=1,
    SF=0,
    CF=0,
    OF=0。

    其实,我们通过cmp 指令执行后,相关标志位的值就可以看出比较的结果。
    例如:cmp ax,bx

    现在我们可以看出比较指令的设计思路
    即:通过做减法运算,影响标志寄存器,标志寄存器的相关位记录了比较的结果。
    反过来看上面的例子 cmp ax,ax

    同 add、sub 指令一样,CPU 在执行cmp指令的时候,也包含两种含义:

    • 进行无符号数运算和进行有符号数运算。
    • 所以利用cmp指令可以对无符号数进行比较,也可以对有符号数进行比较。

    下面我们再来看一下如果用cmp来进行有符号数比较时,我们要注意哪些标志位!我们以cmp ah,bh为例进行说明:

    如果(ah)=(bh) 则(ah)-(bh)=0,所以:ZF=1;
    如果(ah)≠(bh) 则(ah)-(bh) ≠0,所以:ZF=0;

    所以,我们根据cmp指令执行后ZF的值,就可以知道两个数据是否相等。

    我们继续看,如果(ah)<(bh)则可能发生什么情况呢?对于有符号数运算,在 (ah)<(bh) 情况下,(ah)-(bh)显然可能引起SF=1,即结果为负。

    比如:
    (ah) = 1,(bh) = 2:
    则(ah)-(bh)=0FFH,0FFH 为 -1 的补码,因为结果为负,所以SF=1。

    (ah)=0FEH,(bx)=0FFH:
    则(ah)-(bh)=(-2)-(-1)=0FFH,因为结果为负,所以SF=1。

    通过上面的例子,我们是不是可以得到这样的结论:cmp 操作对象1,操作对象2 指令执行后,SF=1,就说明操作对象1<操作对象2?
    当然不是。我们再看下面这个例子。

    (ah)=22H,(bh)=0A0H:
    则(ah)-(bh)=34-(-96)=130=82H,82H是 -126的补码,所以SF=1。
    这里虽然SF=1,但是并不能说明(ah)<(bh),因为显然34>-96。

    两个有符号数A 和B 相减,得到的是负数,那么可以肯定A<B,这个思路没有错误;关键在于我们根据什么来断定得到的是一个负数。CPU将 cmp 指令得到的结果记录在flag的相关标志位中。

    我们可以根据指令执行后,相关标志位的值来判断比较的结果。
    单纯地考察SF 的值不可能知道结果的正负。因为SF 记录的只是可以在计算机中存放的相应位数的结果的正负。比如add ah, al执行后,SF记录的是ah中的8位二进制信息所表示的数据的正负。

    所得到的相应结果的正负,并不能说明,运算所应该得到的结果的正负。这是因为在运算的过程中可能发生溢出。如果有这样的情况发生,那么,SF的值就不能说明任何问题。

    如果没有溢出发生的话,那么,实际结果的正负和逻辑上真正结果的正负就一致了。所以,我们应该在考察SF(得知实际结果的正负)的同时考察OF(得知有没有溢出),就可以得知逻辑上真正结果的正负,同时就可以知道比较的结果。下面,我们以cmp ah,bh为例,总结一下CPU执行cmp指令后,SF和OF的值是如何来说明比较的结果的。

    (1)如果SF=1,而OF=0
    OF=0,说明没有溢出,逻辑上真正结果的正负=实际结果的正负;因SF=1,实际结果为负,所以逻辑上真正的结果为负,所以(ah)<(bh)。

    (2)如果SF=1,而OF=1
    OF=1 ,说明有溢出,逻辑上真正结果的正负≠实际结果的正负;
    简单分析一下,就可以看出,如果因为溢出导致了实际结果为负,那么逻辑上真正的结果必然为正。这样,SF=1,OF = 1 ,说明了(ah)>(bh)。

    (3)如果SF=0,而OF=1
    OF=1 ,说明有溢出,逻辑上真正结果的正负≠实际结果的正负;
    简单分析一下,就可以看出,如果因为溢出导致了实际结果为正,那么逻辑上真正的结果必然为负。这样,SF=0,OF = 1 ,说明了(ah)<(bh)。

    (4)如果SF=0,而OF=0
    OF=0,说明没有溢出,逻辑上真正结果的正负=实际结果的正负;
    因SF=0,实际结果非负,所以逻辑上真正的结果必然非负。所以(ah)≥(bh)。

    上面,我们深入讨论了cmp指令在进行有符号数和无符号数比较时,对flag 相关标志位的影响,和CPU如何通过相关的标志位来表示比较的结果。在学习中,要注意领会8086CPU这种工作机制的设计思想。实际上,这种设计思想对于各种处理机来说是普遍的。

    下面的内容中我们将学习一些根据cmp指令的比较结果(即,cmp指令执行后,相关标志位的值)进行工作的指令。
    它们检测的是哪些标志位呢?
    就是被cmp指令影响的那些,表示比较结果的标志位。
    这些条件转移指令通常都和cmp相配合使用,就好像 call 和 ret 指令通常相配合使用一样。

    因为 cmp 指令可以同时进行两种比较,无符号数比较和有符号数比较,所以根据 cmp 指令的比较结果进行转移的指令也分为两种,即:
    根据无符号数的比较结果进行转移的条件转移指令,它们检测ZF、CF的值;
    和根据有符号数的比较结果进行转移的条件转移指令,它们检测 SF、OF和 ZF的值。

    这些指令比较常用,它们都很好记忆,它们的第一个字母都是j,表示jump;后面的:

    • e:表示equal;
    • ne:表示not equal;
    • b:表示below;
    • nb:表示not below;
    • a:表示above;
    • na:表示not above。

    注意观察一下它们所检测的标志位,都是cmp指令进行无符号数比较时候,记录比较结果的标志位。
    比如je,检测 ZF位,当 ZF=1的时候进行转移,如果在 je 前面使用了 cmp 指令,那么je对ZF的检测,实际上就是间接地检测cmp的比较结果是否为两数相等。

    DF标志和串传送指令

    flag的第10位是DF(Direction Flag),方向标志位。在串处理指令中,控制每次操作后si,di的增减。
    DF = 0:每次操作后si,di递增;
    DF = 1:每次操作后si,di递减。

    格式1: movsb(move stream byte
    功能:(以字节为单位传送)
    (1) ((es)×16 + (di)) = ((ds) ×16 + (si))
    (2) 如果DF = 0则: (si) = (si) + 1
    (di) = (di) + 1
    如果DF = 1则: (si) = (si) – 1
    (di) = (di) – 1

    movsb 的功能是将 ds:si 指向的内存单元中的字节送入 es:di中,然后根据标志寄存器DF位的值,将 si和di递增或递减。当然,也可以传送一个字: movsw
    格式2:movswmove stream word
    功能:(以字为单位传送)将 ds:si指向的内存字单元中word送入es:di中,然后根据标志寄存器DF位的值,将si和di递增2或递减2。

    movsb和movsw进行的是串传送操作中的一个步骤,一般来说,movsb 和 movsw 都和rep配合使用,格式如下: rep movsb
    rep的作用是根据cx的值,重复执行后面的串传送指令。
    由于每执行一次movsb指令si和di都会递增或递减指向后一个单元或前个单元,则rep movsb就可以循环实现(cx)个字符的传送。

    由于flag的DF位决定着串传送指令执行后,si和di改变的方向,所以CPU应该提供相应的指令来对DF位进行设置,从而使程序员能够决定传送的方向。8086CPU提供下而两条指令对DF位进行设置:

    • cld(clear direction)指令:将标志寄存器的DF位置0
    • std(set direction)指令:将标志寄存器的DF位置1

    pushf 和 popf

    • pushf :将标志寄存器的值压栈;
    • popf :从栈中弹出数据,送入标志寄存 器中。
    • pushf 和 popf,为直接访问标志寄存器提供了一种方法。

    内中断

    简介

    中断是CPU处理外部突发事件的一个重要技术。它能使CPU在运行过程中对外部事件发出的中断请求及时地进行处理,处理完成后又立即返回断点,继续进行CPU原来的工作。引起中断的原因或者说发出中断请求的来源叫做中断源。根据中断源的不同,可以把中断分为硬件中断和软件中断两大类,而硬件中断又可以分为外部中断和内部中断两类。

    • 外部中断一般是指由计算机外设发出的中断请求,如:键盘中断、打印机中断、定时器中断等。外部中断是可以屏蔽的中断,也就是说,利用中断控制器可以屏蔽这些外部设备 的中断请求。
    • 内部中断是指因硬件出错(如突然掉电、奇偶校验错等)或运算出错(除数为零、运算 溢出、单步中断等)所引起的中断。内部中断是不可屏蔽的中断。
    • 软件中断其实并不是真正的中断,它们只是可被调用执行的一般程序以及DOS的系统功能调用(INT 21H)等都是软件中断。

    CPU为了处理并发的中断请求,规定了中断的优先权,中断优先权由高到低的顺序是:

    1. 除法错、溢出中断、软件中断
    2. 不可屏蔽中断
    3. 可屏蔽中断
    4. 单步中断

    中断处理程序

    CPU的设计者必须在中断信息和其处理程序的入口地址之间建立某种联系,使得CPU根据中断信息可以找到要执行的处理程序。
    我们知道,中断信息中包含有标识中断源的类型码。根据CPU的设计,中断类型码的作用就是用来定位中断处理程序。

    比如CPU 根据中断类型码 4,就可以找到4号中断的处理程序。可随之而来的问题是,若要定位中断处理程序,需要知道它的段地址和偏移地址,而如何根据 8位的中断类型码(8086中断类型码为一个字节)得到中断处理程序的段地址和偏移地址呢? 这就要引入“中断向量表”。

    中断向量表

    CPU用 8 位的中断类型码通过中断向量表找到相应的中断处理程序的入口地址。那么什么是中断向量表呢?中断向量表就是中断向量的列表。

    中断向量表在内存中保存,其中存放着 256个中断源所对应的中断处理程序的入口,如右图所示:

    中断向量表在内存中存放,对于8086PC机,中断向量表指定放在内存地址0处。从内存0000:0000到0000:03FF的1024(段地址:偏移地址各占2个字节,256 * 4 = 1024)个单元中存放着中断向量表。

    中断过程

    从上面的讲解中,我们知道,可以用中断类型码,在中断向量表中找到中断处理程序的入口。找到这个入口地址的最终目的是用它设置CS和IP,使CPU执行中断处理程序。用中断类型码找到中断向量,并用它设置CS和IP,这个工作是由CPU的硬件自动完成的。
    CPU 硬件完成这个工作的过程被称为中断过程。

    8086CPU的中断过程:

    1. (从中断信息中)取得中断类型码;
    2. 标志寄存器的值入栈(保护标志位);
    3. 设置标志寄存器的第8位TF 和第9位IF的 值为0;(这一步的目的后面将介绍)
    4. CS的内容入栈;
    5. IP的内容入栈;
    6. 从内存地址为中断类型码*4 和中断类型 码 *4+2 的两个字单元中读取中断处理 程序的入口地址设置IP和CS。

    可以看到CPU将CS、IP保存在栈中。我们注意到,在中断过程中还要做的一个工作就是设置标志寄存器的TF、IF位。

    我们更简洁的描述中断过程,如下:

    1. 取得中断类型码N;
    2. pushf
    3. TF = 0,IF = 0
    4. push CS
    5. push IP
    6. (IP) = (N4),(CS) = (N4+2)

    在最后一步完成后,CPU 开始执行由程序员编写的中断处理程序。

    由于CPU随时都可能检测到中断信息,也就是说,CPU 随时都可能执行中断处理程序,所以中断处理程序必须一直存储在内存某段空间之中。
    而中断处理程序的入口地址,即中断向量,必须存储在对应的中断向量表表项中。
    中断处理程序的编写方法和子程序的比较相似,下面是常规的步骤:

    1. 保存用到的寄存器。
    2. 处理中断。
    3. 恢复用到的寄存器。
    4. 用 iret 指令返回。

    iret指令的功能用汇编语法描述为:

    pop IP
    pop CS
    popf

    iret通常和硬件自动完成的中断过程配合使用。可以看到,在中断过程中,寄存器入栈的顺序是标志寄存器、CS、IP ,而iret的出栈顺序是 IP、CS、标志寄存器,刚好和其对应,实现了用执行中断处理程序前的CPU现场恢复标志寄存器和CS、IP的工作。iret指令执行后,CPU回到执行中断处理程序前的执行点继续执行程序。

    除法错误中断的处理

    当CPU执行div等除法指令的时候,如果发生了除法溢出错误,将产生中断类型码为 0 的中断信息,CPU将检测到这个信息,然后引发中断过程,转去执行 0 号中断所对应的中断处理程序。

    单步中断

    CPU为什么要提供这样的功能呢?我们在使用Debug的T命令的时候,有没有想过这样的问题,Debug如何能让CPU在执行一条指令后,就显示各个寄存器的状态?

    假想:如果CPU不提供其他功能的话,就按正常方式工作,只要CPU一加电,它就从预设的地址开始一直执行下去……不可控制!可是,我们在Debug中看到的情况却是,Debug可以控制CPU执行被加载程序中的一条指令,然后让它停下来,显示寄存器的状态。Debug有特殊的能力吗?

    我们只能说Debug利用了CPU提供的一种功能。只有CPU提供了在执行一条指令后就转去做其他事情的功能,Debug或是其他的程序才能利用CPU提供的这种功能做出我们使用T命令时的效果。好了,我们先来谈谈CPU是如何实现单步中断机制,然后再来简要地考虑一下Debug是如何利用CPU所提供的单步中断的功能的。

    CPU在执行完一条指令之后,如果检测到标志寄存器的TF(Trap Flag )位为1,则产生单步中断,引发中断过程。
    单步中断的中断类型码为1,则它所引发的中断过程如下:

    1. 取得中断类型码1;
    2. 标志寄存器入栈,TF、IF设置为0;
    3. CS、IP入栈;
    4. (IP)=(14),(CS)=(14+2)。

    如上所述,如果TF=1,则执行一条指令后,CPU就要转去执行1号中断处理程序。同样的道理,Debug提供了单步中断的中断处理程序,功能为显示所有寄存器中的内容后等待输入命令。

    在使用 T 命令执行指令时,Debug 将TF设置为 1,使得CPU在工作于单步中断方式下,则在CPU执行完这条指令后就引发单步中断,执行单步中断的中断处理程序,所有寄存器中的内容被显示在屏幕上,并且等待输入命令。总之,当TF=1时,CPU在执行完一条指令后将引发单步中断,转去执行中断处理程序。执行完中断处理程序后,又返回原来的位置继续。

    我们再来看一下中断过程

    1. 取得中断类型码N;
    2. 标志寄存器入栈,TF=0、IF=0;
    3. CS、IP入栈;
    4. (IP) = (N4),(CS) = (N4+2)

    最后,CPU提供单步中断功能的原因就是,为单步跟踪的执行过程,提供了实现机制。

    响应中断的特殊情况

    一般情况下,CPU在执行完当前指令后,如果检测到中断信息,就响应中断,引发中断过程。可是,在有些情况下,CPU 在执行完当前指令后,即便是发生中断,也不会响应。对于这些情况,我们不一一列举,大家结合实际运用多加体会,这里我们举一种比较典型的情况来进行说明。

    例如,在执行完向 ss寄存器传送数据的指令后,即便检测到中断信号,CPU 也不会响应。这样做的主要原因是,ss:sp联合指向栈顶,而对它们的设置应该连续完成。因为,如果在执行完设置ss的指令后,CPU响应中断,引发中断过程,要在栈中压入标志寄存器、CS和IP的值。而ss改变,sp并未改变,ss:sp指向的不是正确的栈顶,将引起错误。

    所以CPU在执行完设置ss的指令后,不响应中断。这给连续设置 ss和sp,指向正确的栈顶提供了一个时机。即,我们应该利用这个特性,将设置ss和sp的指令连续存放,使得设置sp的指令紧接着设置ss的指令执行,而在此之间,CPU不会引发中断过程。

    比如,我们要将栈顶设为1000:0,

    ;应该
     mov ax,1000h
     mov ss,ax
     mov sp,0 
     mov ax,0
    
    ;而不应该
     mov ax,1000h
     mov ss,ax
     mov ax,0
     mov sp,0
    

    int 指令

    简介

    int格式: int n,n为中断类型码。它的功能是引发中断过程。

    int 指令

    CPU 执行int n指令,相当于引发一个 n号中断的中断过程,执行过程如下:

    1. 取中断类型码n;
    2. 标志寄存器入栈,IF = 0,TF = 0;
    3. CS、IP入栈;
    4. (IP) = (n4),(CS) = (n4+2)。

    从此处转去执行n号中断的中断处理程序。可以在程序中使用int指令调用任何一个中断的中断处理程序。

    程序是没有做除法,但是在结尾使用了int 0指令。CPU执行int 0指令时,将引发中断过程,执行 0号中断处理程序,而系统设置的 0号中断处理程序的功能是显示“Divide overflow”,然后返回到系统。可见,int 指令的最终功能和call指令相似,都是调用一段程序。

    因此,一般情况下,系统将一些具有一定功能的子程序,以中断处理程序的方式提供给应用程序调用。我们在编程的时候,可以用int指令调用这些子程序。当然,也可以自己编写一些中断处理程序供别人使用。以后,我们可以将中断处理程序简称为中断例程。

    BIOS和DOS中断例程的安装过程

    1. 开机后,CPU 一加电,初始化(CS)=0FFFFH,(IP)=0,自动从FFFF:0单元开始执行程序。FFFF:0处有一条转跳指令,CPU执行该指令后,转去执行BIOS中的硬件系统检测和初始化程序。
    2. 初始化程序将建立BIOS 所支持的中断向量,即将BIOS提供的中断例程的入口地址登记在中断向量表中。
    3. 硬件系统检测和初始化完成后,调用int 19h进行操作系统的引导。从此将计算机交由操作系统控制。
    4. DOS 启动后,除完成其它工作外,还将它所提供的中断例程装入内存,并建立相应的中断向量。

    端口

    CPU可以直接读写3 个地方的数据:

    1. CPU 内部的寄存器
    2. 内存单元
    3. 端口

    端口的读写

    对端口的读写不能用mov、push、pop等内存读写指令。端口的读写指令只有两条:in 和 out分别用于从端口读取数据和往端口写入数据。我们看一下CPU 执行内存访问指令和端口访问指令时候,总线上的信息:

    • 访问内存
    • 访问端口

    访问内存

    mov ax,ds:[8];,假设执行前 (ds)=0,执行时,与总线相关的操作:

    1. CPU通过地址线将地址信息8发出;
    2. CPU通过控制线发出内存读命令,选中存储器芯片,并通知它,将要从中读取数据;
    3. 存储器将 8号单元中的数据通过数据线送入CPU。

    访问端口

    in al,60h;从60h号端口读入一个字节,执行时与总线相关的操作:

    • CPU通过地址线将地址信息60h发出;
    • CPU通过控制线发出端口读命令,选中端口所在的芯片,并通知它,将要从中读取数据;
    • 端口所在的芯片将60h端口中的数据通过数据线送入CPU。

    端口的读写

    对0~255以内的端口进行读写:

    in al,20h 	;从20h端口读入一个字节
    out 20h,al 	;往20h端口写入一个字节

    对256~65535的端口进行读写时,端口号放在dx中:

    mov dx,3f8h 	;将端口号3f8送入dx
    in al,dx 	;从3f8h端口读入一个字节
    out dx,al 	;向3f8h端口写入一个字节

    CMOS RAM 芯片

    PC机中有一个CMOS RAM芯片,其有如下特征:

    1. 包含一个实时钟和一个有128个存储单元的RAM存储器。(早期的计算机为64个字节)
    2. 该芯片靠电池供电。因此,关机后其内部的实时钟仍可正常工作, RAM 中的信息不丢失。
    3. 128 个字节的 RAM 中,内部实时钟占用 0~0dh单元来保存时间信息,其余大部分分单元用于保存系统配置信息,供系统启动时BIOS程序读取。BIOS也提供了相关的程序,使我们可以在开机的时候配置CMOS RAM 中的系统信息。
    4. 该芯片内部有两个端口,端口地址为70h和71h。CPU 通过这两个端口读写CMOS RAM。
    5. 70h为地址端口,存放要访问的CMOS RAM单元的地址;71h为数据端口,存放从选定的CMOS RAM 单元中读取的数据,或要写入到其中的数据。

    可见,CPU对CMOS RAM的读写分两步进行。比如:读CMOS RAM的2号单元:

    1. 将2送入端口70h
    2. 从71h读出2号单元的内容

    shl和shr指令

    shl和shr 是逻辑移位指令,shl逻辑左移指令,功能为:

    1. 将一个寄存器或内存单元中的数据向左移位;
    2. 将最后移出的一位写入CF中;
    3. 最低位用0补充。

    例如有如下指令:

    mov al,01001000b
    shl al,1 ;将al中的数据左移一位

    执行后(al)=10010000b,CF=0。

    如果移动位数大于1时,必须将移动位数放在cl中,比如指令:

    mov al,01010001b
    mov cl,3
    shl al,cl

    执行后(al)=10001000b,那请问,CF的值是多少?因为最后移出一位是0,所以CF=0。

    可以看出,将X逻辑左移一位,相当于执行X=X*2。比如:

    shr逻辑右移指令,它和shl所进行的操作刚好相反:

    • 将一个寄存器或内存单元中的数据向右移位;
    • 将最后移出的一位写入CF中;
    • 最高位用0补充。

    可以看出,将X逻辑右移一位,相当于执行X=X/2。

    CMOS RAM中存储的时间信息

    在CMOS RAM中,存放着当前时间:

    • 秒:00H
    • 分:02H
    • 时:04H
    • 日:07H
    • 月:08H
    • 年:09H

    这6个信息的长度长度都为1个字节。

    这些数据以BCD码的方式存放:

    数码:     0      1       2      3      4      5      6       7      8      9      
    BCD码:   0000   0001    0010   0011   0100   0101   0110    0111   1000   1001

    例如:数值26,用BCD码表示为: 0010 0110

    可见,一个字节可表示两个BCD码。则CMOS RAM存储时间信息的单元中,存储了用两个 BCD码表示的两位十进制数,高 4 位的BCD码表示十位,低4 位的BCD 码表示个位。
    比如:00010100b表示14。

    外中断

    以前我们讨论的都是CPU对指令的执行。我们知道,CPU 在计算机系统中,除了能够执行指令,进行运算以外,还应该能够对外部设备进行控制,接收它们的输入,向它们进行输出。也就是说,CPU 除了有运算能力外,还要有 I/O( Input/Output ,输入/输出)能力。

    接口芯片和端口

    在PC 系统的接口卡和主板上,装有各种接口芯片。这些外设接口芯片的内部有若干寄存器,CPU 将这些寄存器当作端口来访问。外设的输入不直接送入内存和CPU ,而是送入相关的接口芯片的端口中;CPU 向外设的输出也不是直接送入外设,而是先送入端口中,再由相关的芯片送到外设。

    CPU 还可以向外设输出控制命令,而这些控制命令也是先送到相关芯片的端口中,然后再由相关的芯片根据命令对外设实施控制。可见,CPU 通过端口和外部设备进行联系。CPU 在执行完当前指令后,可以检测到发送过来的中断信息,引发中断过程,处理外设的输入。

    外中断信息

    在PC 系统中,外中断源一共有两类:

    1. 可屏蔽中断
    2. 不可屏蔽中断

    可屏蔽中断是CPU 可以不响应的外中断。CPU 是否响应可屏蔽中断,要看标志寄存器的IF(Interrupt Enable Flag) 位的设置。当CPU 检测到可屏蔽中断信息时:

    • 如果IF=1,则CPU 在执行完当前指令后响应中断,引发中断过程;
    • 如果IF=0,则不响应可屏蔽中断。

    我们回忆一下内中断所引发的中断过程:

    1. 取中断类型码n;
    2. 标志寄存器入栈,IF=0,TF=0;
    3. CS 、IP 入栈;
    4. (IP)=(n4),(CS)=(n4+2)
    5. 由此转去执行中断处理程序。

    可屏蔽中断所引发的中断过程 ,除在第一步的实现上有所不同外,基本上和内中断的中断过程相同。因为可屏蔽中断信息来自于CPU外部,中断类型码是通过数据总线送入CPU 的;而内中断的中断类型码是在CPU内部产生的。

    现在,我们可以解释中断过程中将IF置为0的原因了。将IF置0的原因就是,在进入中断处理程序后,禁止其他的可屏蔽中断。当然,如果在中断处理程序中需要处理可屏蔽中断,可以用指令将IF 置1 。8086CPU 提供的设置IF的指令如下:

    • sti,用于设置IF=1;
    • cli,用于设置IF=0。

    不可屏蔽中断是CPU 必须响应的外中断。当CPU 检测到不可屏蔽中断信息时,则在执行完当前指令后,立即响应,引发中断过程。对于8086CPU 不可屏蔽中断的中断类型码固定为2。所以中断过程中,不需要取中断类型码。

    不可屏蔽中断的中断过程:

    1. 标志寄存器入栈,IF=0,TF=0
    2. CS、IP入栈
    3. (IP)=(8),(CS)=(0AH)

    几乎所有由外设引发的外中断,都是可屏蔽中断。当外设有需要处理的事件(比如说键盘输入)发生时,相关芯片向CPU 发出可屏蔽中断信息。不可屏蔽中断是在系统中有必须处理的紧急情况发生时用来通知CPU 的中断信息。在我们的课程中,主要讨论可屏蔽中断。

    PC机键盘的处理过程

    下面我们看一下键盘输入的处理过程,并以此来体会一下PC 机处理外设输入的基本方法。

    1. 键盘输入
    2. 引发9号中断
    3. 执行int 9中断例程

    键盘上的每一个键相当于一个开关,键盘中有一个芯片对键盘上的每一个键的开关状态进行扫描。按下一个键时,开关接通,该芯片就产生一个扫描码,扫描码说明了按下的键在键盘上的位置。扫描码被送入主板上的相关接口芯片的寄存器中,该寄存器的端口地址为60H 。松开按下的键时,也产生一个扫描码,扫描码说明了松开的键在键盘上的位置。松开按键时产生的扫描码也被送入60H 端口中。

    一般将按下一个键时产生的扫描码称为通码,松开一个键产生的扫描码称为断码。扫描码长度为一个字节,通码的第 7 位为 0 ,断码的第7位为1,即:断码 = 通码+80H,比如:g键的通码为22H,断码为a2H。

    键盘上部分键的扫描码

    BIOS 提供了int 9中断例程,用来进行基本的键盘输入处理,主要的工作如下:

    1. 读出60H 端口中的扫描码;
    2. 如果是字符键的扫描码,将该扫描码和它所对应的字符码( 即 ASCII码)送入内存中的 BIOS 键盘缓冲区;键盘的输入到达60H 端口时,相关的芯片就会向CPU 发出中断类型码为 9 的可屏蔽中断信息。CPU检测到该中断信息后,如果IF=1,则响应中断,引发中断过程,转去执行int 9中断例程。如果是控制键(比如 Ctrl )和切换键(比如 CapsLock)的扫描码,则将其转变为状态字节( 用二进制位记录控制键和切换键状态的字节 )写入内存中存储状态字节的单元。
    3. 对键盘系统进行相关的控制,比如说,向相关芯片发出应答信息。BIOS键盘缓冲区是系统启动后,BIOS用于存放int 9 中断例程所接收的键盘输入的内存区。
      该内存区可以存储 15 个键盘输入,因为 int 9 中断例程除了接收扫描码外,还要产生和扫描码对应的字符码,所以在BIOS键盘缓冲区中,一个键盘输入用一个字单元存放,高位字节存放扫描码,低位字节存放字符码。
      0040:17 单元存储键盘状态字节,该字节记录了控制键和切换键的状态。键盘状态字节各位记录的信息如下:

    直接定址表

    前面我们一直在代码段中使用标号来标记指令、数据、段的起始地址。比如:下面的程序将code 段中的a 标号处的8个数据累加,结果存储到b标号处的字中。

    assume cs:code
    code segment
             a : db 1,2,3,4,5,6,7,8
             b : dw 0
    start :mov si,offset a
             mov bx,offset b
             mov cx,8
        s : mov al,cs:[si]
             mov ah,0
             add cs:[bx],ax
             inc si
             loop s
             mov ax,4c00h
             int 21h
    code ends
    end start
    

    程序中,code、a、b、start、s都是标号。这些标号仅仅表示了内存单元的地址。但是,我们还可以使用一种标号,这种标号不但表示内存单元的地址,还表示了内存单元的长度,即表示在此标号处的单元,是一个字节单元,还是字单元,还是双字单元。上面的程序我们还可以写成这样:

    assume cs:code
    code segment
              a db 1,2,3,4,5,6,7,8
              b dw 0
    start :  mov si,0
              mov cx,8
        s :   mov al,a[si]
              mov ah,0
              add b,ax
              inc si
              loop s
              mov ax,4c00h
              int 21h
    code ends
    end start

    我们在code 段中使用的标号a、b后面没有“:” ,因此它们是可以同时描述内存地址和单元长度的标号。标号a,描述了地址code:0,和从这个地址开始,以后的内存单元都是字节单元;而标号b描述了地址code:8,和从这个地址开始,以后的内存单元都是字单元。

    因为这种标号包含了对单元长度的描述,所以,在指令中,它可以代表一个段中的内存单元。

    比如,对于程序中的b dw 0。
    指令:mov ax,b 相当于:mov ax,cs:[8]
    指令:mov b,2 相当于:mov word ptr cs:[8],2
    指令:inc b 相当于:inc word ptr cs:[8]

    在这些指令中,标号b 代表了一个内存单元,地址为code:8 ,长度为2 字节。

    下面的指令会引起编译错误:

    mov al,b
    为什么?因为b代表的内存单元是字单元,而al 是8 位寄存器。因此,如果我们将程序中的指令:add b,ax ,写为add b,al,将出现同样的编译错误。

    对于程序中的a db 1,2,3,4,5,6,7,8
    则有—>>>
    指令:mov al,a [si]
    相当于:mov al,cs:0[si]
    指令:mov al,a[3]
    相当于:mov al,cs:0[3]
    指令:mov al,a[bx+si+3]
    相当于:mov al,cs:0[bx+si+3]

    可见,使用这种包含单元长度的标号, 可以使我们以简洁的形式访问内存中的数据。以后,我们将这种标号称为数据标号。它标记了存储数据的单元的地址和长度。它不同于仅仅表示地址的地址标号。

    一般来说,我们不会在代码段中定义数据,而是将数据定义到其他段中。在其他段中,我们也可以使用数据标号来描述存储数据的单元的地址和长度。注意:在后面加有“:”的地址标号,只能在代码段中使用,不能在其他段中使用。下边程序将data 段中 a标号处的 8 个数据累加,结果存储到 b标号处的字中。

    assume cs:code,ds:data
    data segment          
              a db 1,2,3,4,5,6,7,8
              b dw 0
    data ends
    code segment
    start:  mov ax,data
              mov ds,ax
              mov si,0
              mov cx,8
    s:       mov al,a[si]
              mov ah,0
              add b,ax
              inc si
              loop s
              mov ax,4c00h
              int 21h
    code ends
    end start

    注意,如果想在代码段中,直接用数据标号访问数据,则需要用伪指令assume 将标号所在的段和一个段寄存器联系起来。否则编译器在编译的时候,无法确定标号的段地址在哪一个寄存器中。当然,这种联系是编译器需要的,但绝对不是说,我们因为编译器的工作需要,用 assume 指令将段寄存器和某个段相联系,段寄存器中就会真的存放该段的地址。

    比如:在上面的程序中,我们要在代码段code中用 data段中的数据标号 a、b 访问数据,则必须用 assume 将一个寄存器和data 段相联。

    在程序中,我们用 ds寄存器和 data 段相联,则编译器对相关指令的编译如下:
    指令:mov al,a[si]
    编译为:mov al,[si+0]
    指令:add b,ax
    编译为:add [8],ax

    因为这些实际编译出的指令,都默认所访问单元的段地址在ds中,而实际要访问的段为data,所以,若要访问正确,在这些指令执行前,ds 中必须为 data 段的段地址。
    则,我们在程序中使用指令: mov ax,data mov ds,ax
    设置ds指向data段。

    我们可以将标号当作数据来定义,此时,编译器将标号所表示的地址当作数据的值。
    比如: data segment
    a db 1,2,3,4,5,6,7,8
    b dw 0
    c dw a,b
    data ends
    数据标号c处存储的两个字型数据为标号a、b 的偏移地址。

    相当于:

    data segment
    a db 1,2,3,4,5,6,7,8
    b dw 0
    c dw offset a, offset b
    data ends

    再比如:

    data segment
    a db 1,2,3,4,5,6,7,8
    b dw 0
    c dd a,b
    data ends

    数据标号c处存储的两个双字型数据为标号a的偏移地址和段地址、标号b 的偏移地址和段地址。相当于:

    data segment
    a db 1,2,3,4,5,6,7,8
    b dw 0
    c dw offset a, seg a, offset b, seg b
    data ends

    seg操作符,功能为取得某一标号的段地址。

    使用BIOS进行键盘输入和磁盘读写

    大多数有用的程序都需要处理用户的输入,键盘输入是最基本的输入。程序和数据通常需要长期存储,磁盘是最常用的存储设备。BIOS 为这两种外设的I/O提供了最基本的中断例程,在本章中,我们对它们的应用和相关的问题进行讨论。

    《琢石成器:Windows环境下32位汇编语言程序设计》

    我们已经讲过,键盘输入将引发9 号中断,BIOS 提供了int 9 中断例程。CPU 在9 号中断发生后,执行int 9中断例程,从60h 端口读出扫描码,并将其转化为相应的ASCII 码或状态信息,存储在内存的指定空间(键盘缓冲区或状态字节)中。所以,一般的键盘输入,在CPU 执行完int 9 中断例程后,都放到了键盘缓冲区中。

    键盘缓冲区中有16 个字单元,可以存储15个按键的扫描码和对应的入ASCII 码。

  • CDN ByPass

    CDN ByPass

    简介

    什么是CDN加速

    CDN的全称是 Content Delivery Network,即内容分发网络,这个概念是1998年提出的。当时美国麻省理工大学(MT)的一批研究生通过分析当时 Internet E的网络状况,提出了一套能够实现用户就近访问的解决方案,最终设计并实现了其独有的系统。

    CDN有什么作用

    CDN基本思路就是尽可能避开互联网上有可能影响数据传输速度和稳定性的瓶颈和环节,使内容传输的更快、更稳定。通过在网络各处放置节点服务器所构成的在现有的互联网基础之上的一层智能虚拟网络,CDN系统能够实时地根据网络流量和各节点的连接、负载状况以及到用户的距离和响应时间等综合信息将用户的请求重新导向离用户最近的服务节点上。CDN核心的就是使用户可就近访问网络,取得所需内容,解决网络拥挤的状况,明显提高用户访问网站的响应速度或者用户下载速度。

    对用户来说,如果一个网站开启了CDN,用户访问速度或者下载速度相比之前会更快。一般来说,网站开启CDN之后,可以提升用户体验。

    CDN对网站有什么好处

    首先,开启CDN后的网站,会根据用户所在地的不同访问CDN的节点服务器,并不直接访问源服务器,这样可以减少网站服务器宽带资源,降低服务器压力。这也就是大家都在ping百度,但是不同地区得到的反馈ip不一样的原因。

    其次,由于CDN节点的阻挡防护,可以更好的保护员服务器的安全。具体来说,CDN其实是充当了一个替身的角色,无论服务器是渗透还是DD0S攻击,攻击的目标都将是CDN节点,这样一来便间接的保护了网站本身。

    验证是否使用CDN

    多地Ping服务

    使用各种多地 ping 的服务,查看对应 IP 地址是否唯一,如果不唯一,多半是使用了CDN, 多地 Ping 网站有:

    使用nslookup

    使用 nslookup 进行检测,原理同上,如果返回域名解析对应多个 IP 地址多半是使用了 CDN。

    绕过CDN找到真实IP

    一、主动连接我们

    让目标主动暴漏他们的真实IP:

    1. 发邮件
    2. RSS邮件订阅
    3. 利用网站漏洞:比如有代码执行漏洞、SSRF、存储型的XSS都可以让服务器主动访问我们预设的web服务器,那么就能在日志里面看见目标网站服务器的真实IP。

    二、查询DNS历史记录

    1. DNS缓存查询:这里主要是利用管理员疏忽,通过DNS缓存查询,查看 IP 与 域名绑定的历史记录,可能会存在使用 CDN 前的记录,相关查询网站有:
    2. 利用SecurityTrails攻击者就可以精准的找到真实原始IP,只需在搜索字段中输入网站域名,然后按Enter键即可,这时“历史数据”就可以在左侧的菜单中找到。SecurityTrails平台除了过去的DNS记录,即使是当前的记录也可能泄漏原始服务器IP。例如,MX记录是一种常见的查找IP的方式。如果网站在与web相同的服务器和IP上托管自己的邮件服务器,那么原始服务器IP将在MX记录中。

    三、查询子域名

    毕竟 CDN 还是不便宜的,所以很多站长可能只会对主站或者流量大的子站点做了 CDN,而很多小站子站点又跟主站在同一台服务器或者同一个C段内,此时就可以通过查询子域名对应的 IP 来辅助查找网站的真实IP。

    下面介绍些常用的子域名查找的方法和工具:

    1. https://dnsdb.io/zh-cn
    2. Google 搜索;例如:用语法”site:baidu.com -www”就能查看除www外的子域名。
    3. 子域名扫描器

    四、网络空间搜索引擎

    最常见的网络空间搜索引擎有钟馗之眼、shodan、fofa搜索。

    1. 钟馗之眼
    2. Shodan
    3. FOFA

    这里主要是利用网站返回的内容寻找真实原始IP,如果原始服务器IP也返回了网站的内容,那么可以在网上搜索大量的相关数据。只需要浏览网站源代码,寻找独特的代码片段。

    在JavaScript中使用具有访问或标识符参数的第三方服务(例如Google Analytics、reCAPTCHA、统计)是攻击者经常使用的方法。或者说用title,毕竟竟每个网站的title基本上都是独一无二的。 以fofa为例:可以直接以 title=””来搜索。

    再配合最常见的网络空间搜索引擎就可以轻而易举的找到网站的真实的IP。

    五、利用SSL证书寻找真实原始IP

    以CloudFlare这款CDN加速器举例:假如你在 abc.com 上托管了一个服务,原始服务器IP是136.23.63.44。 而CloudFlare则会为你提供DDoS保护,Web应用程序防火墙和其他一些安全服务,以保护你的服务免受攻击。

    为此,你的Web服务器就必须支持SSL并具有证书,此时CloudFlare与你的服务器之间的通信,就像你和CloudFlare之间的通信一样,会被加密(即没有灵活的SSL存在)。这看起来很安全,但问题是,当你在端口443(https://136.23.63.44:443)上直接连接到IP时,SSL证书就会被暴露。

    此时,如果攻击者扫描0.0.0.0/0,即整个互联网,他们就可以在端口443上获取在 aaa.com上的有效证书,进而获取提供给你的Web服务器IP。

    目前Censys工具就能实现对整个互联网的扫描,Censys是一款用以搜索联网设备信息的新型搜索引擎,安全专家可以使用它来评估他们实现方案的安全性,而黑客则可以使用它作为前期侦查攻击目标、收集目标信息的强大利器。

    Censys搜索引擎能够扫描整个互联网,Censys每天都会扫描IPv4地址空间,以搜索所有联网设备并收集相关的信息,并返回一份有关资源(如设备、网站和证书)配置和部署信息的总体报告。

    而攻击者唯一需要做的就是把上面用文字描述的搜索词翻译成实际的搜索查询参数。

    Censys证书查询搜索步骤如下:

    1. aaa.com 证书的搜索查询参数为:parsed.names:aaa.com,只显示有效证书的查询参数为:tags.raw:trusted,攻击者可以在Censys上实现多个参数的组合,这可以通过使用简单的布尔逻辑来完成。
    2. 组合后的搜索参数为:parsed.names: aaa.com and tags.raw: trusted
    3. Censys将向你显示符合上述搜索条件的所有标准证书。要逐个查看这些搜索结果,攻击者可以通过单击右侧的“Explore”,打开包含多个工具的下拉菜单。What’s using this certificate? > IPv4 Hosts

    隐藏服务具有SSL证书,要查找它使用的IPv4,只需将”SHA1 fingerprint”(签名证书的sha1值,也就是SHA1指纹)粘贴到Censys平台IPv4主机搜索中,即可找到证书,使用此方法可以轻松找到配置错误的Web服务器真实IP。

    使用给定的SSL证书查找原始IP可以做很多事情,例如:如果你是执法部门的人员,想要找出一个隐藏在cheesecp5vaogohv.onion下的儿童色情网站。最好的办法,就是找到其原始IP,这样你就可以追踪到其托管的服务器,甚至查到背后的运营商以及金融线索。

    六、全网扫描

    扫描全网开放特定端口的IP,然后获取他们的特定页面的HTM源代码,用这些源代码和目标网站的特定页面的HTM源代码做对比,如果匹配上来了,就很可能是目标网站的真实P,工具匹配会匹配出来很多,最后还是要人工筛选。

    1. 钟馗之眼、Shodan、FOFA空间搜索引擎,这里推荐用FOFA
      第一步:打开fofa;第二步:title=”” 得到IP;第三步:再用  ip==”” 做对比。
    2. 用Zmap扫全网
    wget http://www.ipdeny.com/ipblocks/data/countries/hk.zone #香港IP 
    zmap -w hk.zone -p 80 -B 100M -o hk.res
    ./zgrab -input-file=hk.res -senders=2000 -data-"./http-reg" | grep -E 'memberlogin' >> x.txt
    wget -c http://ftp.apnic.net/stats/apnic/delegated-apnic-latest #全网IP

    七、其它思路

    1. 利用网站敏感信息:比如 phpinfo. php,这个就要看字典了。
    2. 国内很多CDN厂商因为各种原因只做了国内的线路,而针对国外的线路可能几乎没有,此时我们使用国外的主机直接访问可能就能获取到真实IP。
  • 密码保护:软件逆向

    密码保护:软件逆向

    此内容受密码保护。如需查阅,请在下列字段中输入您的密码。

  • C: 指针与内存

    C: 指针与内存

    标准函数库[链接]

    小端序与大端序

    大端序符合人类阅读习惯,但多数计算机平台采用小端序,这跟芯片的制作工艺有关,例如0x5ecff7f0小端序在内存中显示为 f0 f7 cf 5e

    指针

    指针操作

    一个int类型指针 + 1相当于加4个字节,因为int类型分配的空间就是4个字节(与平台架构有关),外挂程序就是通过修改指针赋值实现某些游戏功能。

    指针转换

    指针可以进行强制转换,但转换后获取的值可能会有偏差,例如char类型的指针与int类型的指针,虽然&ab指向的是同一个地址,但char取值只取一个字节,而int取值为四个字节:

    char a = 'd'; 
    int* b = (int*) &a;
    printf("a = 0x%x\n", *&a); // *&a = 0x64 
    printf("b = 0x%x\n", *b); // *b = 0xcccccc64
    
    out:
    a = 0x64
    b = 0xcccccc64

    二级指针/三级指针

    二级指针存放一级指针的地址,三级指针存放二级指针地址

    char a = 'd';
    char* b = &a;
    char** c = &b;
    printf("a = 0x%x\n", *&a);
    printf("b = 0x%x\n", *b);
    printf("c = 0x%x\n", **c);
    printf("c = 0x%p\n", *c);
    
    out:
    a = 0x64
    b = 0x64
    c = 0x64
    c = 0x14fcd4

    指针外挂

    可以使用Cheat Engine对相关地址的值进行修改,也可以自己写个C程序对指针进行修改,低权限进程无法修改高权限进程值。

    指针数组

    数组变量默认就是一个指针,如下所示:a是一个指针,所以*pa == apa[0] == a[0], pa[1] == a[1]b是一个值,所以*pb == &b。指针也可以指向具体某个数组值的地址:*pa4 = &a[4]

    int a[5] = {1,2,3,4,5};
    int b = 6;
    int* pa = a;
    int* pb = &b;
    int* pa4 = &a[4]; // 指针也可以指向具体某个数组值的地址

    数组、函数、指针

    数组传递参数,由于数据变量是指针类型,所以默认传进函数的是指针,也就意味着在主函数内部修改值,外部的值也是会跟着改变

    void test(int a[]) {
        for (int i = 0; i < 3; i++) {        
            printf("i = %d\n", a[i]);
            a[i] = a[i] + 100;
        }
    }
    
    void test2(int c) {
        printf("c = %d\n", c);
        c = 100;
    }
    
    int main() {
        int b[] = {1,2,3};
        test(b);
        for (int j = 0; j < 3; j++) {
            printf("j = %d\n", b[j]);
        }
        /* 打印被修改的值,而不是1,2,3: 
        j = 101
        j = 102
        j = 103 */
    
        int d = 2;
        test2(d);
        printf("d = %d\n", d);
        /* 打印原始值,不会因为函数值被修改而改变
        d = 2*/
    }

    函数名默认也是一个指针,所以数组值也可以是一组函数,需要注意返回类型必须一样

    void* f[2] = { test, test2 };
    printf("f[0] = %p\n", f[0]);
    printf("f[1] = %p\n", f[1]);
    
    out:
    f[0] = 00007FF70C0613D9
    f[1] = 00007FF70C0613E3

    定义一个新函数指向函数指针(会有很多这种使用场景,根据具体业务使用)。

    int b[] = { 1,2,3 };
    void(*pf)(int[]) = test;
    pf(b);// 调用新函数

    pf的值是一个二级指针,指向一个新的复制函数的地址。

    方法里面调用函数指针数组:

    void test() {
        printf("test\n");
    }
    
    void test3() {
        printf("test3\n");
    }
    
    void test2(void (*c[3])()) {
        c[2]();
        return;
    }
    
    int main() {  
        void(*ptest)() = test;
        void* c[3] = {ptest, test, test3}; // 一般指针指向函数,在方法里面调用需要强转
        void(*b[3])() = {ptest, test, test3}; // 声明为函数指针数组
        test2(b);
    }

    自定义类型

    在上面的方法用,void* c[3] = {ptest, test, test3}只是一般指针指向函数,而不是函数指针,如果传入的参数只是一般的指针,而在方法里面调用,此时就需要自定义类型对函数进行强转

    typedef void (*PT)(); // 定义函数类型
    
    void test() {
        printf("test\n");
    }
    
    void test3() {
        printf("test3\n");
    }
    
    void test2(void* c[3]) {
        PT p = (PT)c[0];// 将一般指针强转为函数指针
        p();
        return;
    }
    
    
    int main() {
        void(*ptest)() = test;
        void* c[3] = { ptest, test, test3 }; // 一般指针指向函数
        void(*b[3])() = { ptest, test, test3 }; // 声明为函数指针数组
        test2(c);
    }

    Const 声明位置

    	int a = 6;
    	int b = 7;
    	int* const pa = &a;
    	int const* pb = pa;
    	pa = &b; // 报错,变量已声明为常量,只能指向&a,不能修改为其他指向
    	*pa = 10;
    	pb = &b; // 报错,变量指针已声明为常量,不能修改指针里面的值
    	*pb = 10;
    	const int* const pc = &a; // 全部只读
    

    结构体:根据声明类型决定结构体内存大小,取地址为&s.name,&s.age,结构体大小因为涉及到内存里的对齐(例如int4个字节,char1个字节,结构体为8个字节),所以会比实际的声明大一些,获取结构体大小直接使用sizeof就可以了。

    #include <iostream>
    struct Student {
    	char* name;
    	char age;
    	int clazz;
    };
    
    typedef struct dslbq {
    	char a : 2;
    	int b;
    	short c;
    } d; // 定义结构体为新类型
    
    struct dslbq {
    	char a : 2;
    	int b;
    	short c;
    } d; // 声明d变量
    、
    
    int main() {	
    	Student s;
    	s.age = 30;
    	s.name = (char*)"dslbq";
    	s.clazz = 3;
    
    	Student* ps = &s; // 取结构体地址
    	ps -> name = (char*)"beijing"; // 写结构体声明地址
            printf("%s\n", ps -> name); // 读结构体声明地址
    }

    数组变量初始化时,默认分配一块空间,并且这个变量为const,不能重新指向另一个地址,因为已经分配,避免浪费。

    char b[3];
    b[0] = 'aa';
    b = { 'aa' }; // 报错

    Union:只能使用其中的一个字段,赋值时会把其他值同时更新

    union Human {
    	char a;
    	int age;
    };
    
    struct Student {
    	char* name;
    	char age;
    	int clazz;
    	union { // 匿名联合体
    		int ua;
    		int ub;
    	};
    };
    
    int main() {
    	Human human;
    	human.a = 'b';
    	human.age = 21;
    }
    
    
    a最初赋值为98
    a、age全部更新为21

    枚举:对设定值后面的值依次累加1

    enum Enum {
    	enum1, // 设置值:0
    	enum2, // 设置值:1
    	enum3  // 设置值:2
    };
    
    int main() {
    	Enum e;
    	e = enum1;
    	e = enum2;
    }

    文件包含

    函数声明写在.h文件,函数实现写在.c文件,.c文件include .h实现具体内容,业务调用直接包含.h文件,这样就可以只暴露接口,隐藏具体实现的代码

    引入三方库

    引入.h.c文件到工程指定目录

    宏定义(预处理):Windows大量的使用宏定义,是因为声明变量、函数等需要在栈中开辟空间,而用宏就直接进行文本替换,不需要进栈出栈走函数的那种体系,效率更高。

    #define _CRT_SECURE_NO_WARNINGS
    #define PX printf("main\n");
    #define PP(x) printf("main=>%s\n", x);
    // 格式:预处理指令 宏[宏参数] 替换体
    #ifdef windows // 条件预编译,比如不同操作系统采用不同操作
    int x = 1;
    #else
    int x = 0;
    #endif
    

    可变参数

    #include <stdarg.h>
    #define PR(...) printf(__VA_ARGS__) // 可变宏定义参数
    #define VAR(x) V ## x // 动态语法,例如VAR(2)则动态声明V2这个变量,##:将后面的x粘到前面的参数
    int pr(int num, ...) { // 可变函数参数
    	va_list valist;
    	va_start(valist, num);
    
    	for (int i = 0; i < num; i++){
    		int arg = va_arg(valist, int);
    		printf("%d\n", arg);
    	}
    	va_end(valist);
    	return 0;
    }
    int main(int argc, char* argv[]) {
    	int VAR(1) = 2;
    	pr(5, 2, 3, 4, 5, 6);
            PR("%d\n", 12);
    }

    内存

    栈区 / 堆栈(stack)

    先进后出,程序运行时由程序自动分配,存放函数的参数值、局部变量的值,程序结束时由程序自动释放。

    堆区(heap)

    先进先出,程序运行时在内存开辟另一块存储区域,一般由程序员分配释放,用malloccallocrealloc等分配内存的函数分配得到的就是在堆上。

    全局区(静态区static)

    全局变量、函数体等

    编译过程

    1. 预处理(Preprocessiong)
      预处理指令文本替换:#include,宏定义
    2. 编译成汇编(Compilation)
      转换成对应汇编代码
    3. 汇编转二进制(Assemble)
      汇编代码转换成二进制代码
    4. 链接(Linking)
      链接操作系统库代码,打包到本程序中

  • 密码保护:Zmap学习笔记

    密码保护:Zmap学习笔记

    此内容受密码保护。如需查阅,请在下列字段中输入您的密码。

  • 密码保护:MSF学习笔记

    密码保护:MSF学习笔记

    此内容受密码保护。如需查阅,请在下列字段中输入您的密码。

  • Google高级搜索

    Google高级搜索

    介绍

    Google搜索命令有许多,但是哪些指令才是真正的好用且高效?接下来,给大家分享一些常用的谷歌高级搜索技巧。

    1、”search term”

    搜索词前后加英文双引号,代表完全匹配搜索,可以过滤掉那些模棱两可的搜索结果,增大相关性。

    2、X or Y 或者 X | Y

    这两个搜索指令是一样的,返回的结果是与X或与Y相关的结果。

    3、( )

    ( )来将多个术语或搜索运算符进行分组来控制搜索的执行方式。比如:(ipad OR iphone) apple

    4、site:domain.com

    • 使用site: 搜索指令可以看到某网站被谷歌收录的所有页面

    若再添加上关键词,就可以查找某网站上包含特定关键词的页面,比如:

    •  用site:指令还可以知晓某一具体页面是否被谷歌收录

    只要把页面的完整url地址放到site: 后面就可以。如下图,可知所查的这个页面已被谷歌收录:

    • 查找出网站上非https的页面

    以博客为例,搜索指令为:site:dslbq.com -inurl:https

    • 查找重复内容

    想知道网站内容有没有被分享或者剽窃?只需-site:域名 "内容"即可:

    • 快速查找某人的社交档案

    常用搜索指令:

    name (site:twitter.com | site:facebook.com | site: linkedln.com | youtube.com)

    前提是需要知道对方的姓名,最好再找到几个特征词,毕竟重名的太多。比如:对方的公司名、所在地址、曾经在的地址。总之认为对方会设置在资料里的内容。

    5、cache:

    使用cache: 此指令可以看到某个网页的新缓存,即可以看到谷歌近抓取收录的这个页面是什么样的。如果一个页面你修改很长时间后,还没有更新缓存,则可以在谷歌站长工具或pingfarm上提交一下来加快更新。

    6、intitle: 和 allintitle:

    后面加单词或短语,可以检索出在标题中含有特定字词的网页。比如:intitle:apple将返回网页title中含有apple的所有网页。

    7、inurl:keyword 和 allinurl:keyword1 keyword2 

    这两个搜索指令可以检索出URL中包含特定字词的结果。其中allinurl:keyword1 keyword2 返回的结果是url中包含所有指定的字词。主要是在寻找guest post资源时使用此类搜索指令,如下图:

    查询结果是一些可以做客座博客的网站资源。

    附:找客座博客平台的其他常用google高级搜索技巧语法

    • “become a contributor”
    • “contribute to”
    • “write for me”
    • “guest post guidelines”
    • inurl:guest-post
    • inurl:guest-contributor-guidelines

    8、inanchor: 和 allinanchor:

    inanchor:检索出外链锚文本中含有特定字词的网页,比如指令inanchor:apple iphone,检索结果则是一些其外链锚文本中含有apple或iphone的网页。

    allinanchor:inanchor:类似,但检索结果是包含所有指定字词的结果,比如 allinanchor:apple iphone,检测结果是外链锚文本中含有apple iphone的网页。

    9、intext:keyword 和 allintext:keyword1 keyword2

    intext:keyword 搜索指令可以帮你检索出包含某字词的网页。比如:intext:apple

    allintext:keyword1 keyword2 搜索指令可以检索出网页内容里同时包含多个字词的网页。比如:allintext:apple iphone

    10、filetype:

    利用filetype:可以来轻松查找特定的文件类型,比如pdf, jpg、png或gif等格式的图。举例:下图运用了filetype指令,找出了网上分享的几本学习谷歌seo的书的pdf版。

    11、related:

    使用此指令可以找出与被搜索网站/网页相关联的结果。比如related:amazon.com,我们可以得到与亚马逊有关联的其他网页。至于谷歌是怎么判断的有关联,谷歌没有明说,但一般是认为有共同外链的网站。

    举个例子:做谷歌seo的都知道Ahrefs工具,他家的网站博客地址是https://ahrefs.com/blog,经常会分享一些SEO学习干货,利用搜索指令related:ahrefs.com/blog,可以找到与他的博客类似的一些学习网站,如下图:

    然后看到了第二个搜索结果yoast.com/seo-blog,假如对这个网站一无所知,那么怎么判断其相关性呢,有个很准确的办法:

    • 进行site:domain.com对 yoast.com 搜索,记下结果的数量
    • 进行site:domain.com seo搜索,然后记下结果的数量
    • 将第二个数字除以第一个,如果它高于0.5,这是一个很好的相关网站; 如果它高于0.75,这是一个超级相关的网站。

    在此例中,2760除以4030约为0.68,所以数据表明,yoast.com是个很不错的可学SEO的相关网站。

    12、*

    * 即通配符,Google会检索出与你指定的字词相匹配并用随机单词或短语来替换通配符的一些结果,非常适合为内容创作寻找主题和创意,举个例子,想找一些有关wordpress的文章主题,然后在谷歌中进行了top * wordpress * 的指令搜索,结果如下图:

    Good,找到了wordpress 主题、工具、插件等不错的内容主题。

  • Shodan手册

    Shodan手册

    前言

    本文主要对Complete Guide to Shodan的内容进行增、删、改,虽篇幅较长,但内容涉及了Shodan的搜索语法、客户端命令行、API编程、工具插件、工控等,较为详细,适合当作Shodan手册。代码和搜索示例均已亲自测试并附截图。

    介绍

    Shodan是一个搜索互联网连接设备的搜索引擎,不同于谷歌、必应、百度这些搜索引擎。用户可以在Shodan上使用Shodan搜索语法查找连接到互联网的摄像头、路由器、服务器等设备信息。在渗透测试中,是个很不错的神器。

    Shodan的搜索流程:

    关于数据

    Shodan采集的基本数据单位是banner。banner是描述设备所运行服务的标志性文本信息。对于Web服务器来说,其将返回标题或者是telnet登陆界面。Banner的内容因服务类型的不同而相异。

    以下这是一个典型的HTTP Banner:

    HTTP/1.1 200 OK
    Server: nginx/1.1.19
    Date: Sat, 03 Oct 2015 06:09:24 GMT
    Content-Type: text/html; charset=utf-8
    Content-Length: 6466
    Connection: keep-alive
    

    上面的banner显示该设备正在运行一个1.1.19版本的nginx Web服务器软件

    下面是西门子S7工控系统协议的一个banner,这次返回的banner完全不同,提供了大量的详细数据(有关的固件信息、序列号):

    Copyright: Original Siemens Equipment
    PLC name: S7_Turbine
    Module type: CPU 313C
    Unknown (129): Boot Loader           A
    Module: 6ES7 313-5BG04-0AB0  v.0.3
    Basic Firmware: v.3.3.8
    Module name: CPU 313C
    Serial number of module: S Q-D9U083642013
    Plant identification: 
    Basic Hardware: 6ES7 313-5BG04-0AB0  v.0.3
    

    注:Shodan搜索的是联网设备运行服务的banner,不是单一的主机信息。如果单个IP暴露很多服务信息,在指定特定搜索内容的时候,搜索结果只会出现指定的内容,不会显示其他的服务信息。

    设备元数据

    除了获取banner,Shodan还可以获取相关设备的元数据,例如地理位置、主机名、操作系统等信息。大部分元数据可以通过Shodan的官网获取,小部分的可以通过使用API编程获取。

    IPv6

    据统计,截至2015年10月,Shodan每月收集数百万条关于IPv6设备的可用数据。不过与收集的数亿个IPv4 banner相比,这些数字还是苍白。预计在未来几年会有所增长。

    数据采集

    频率

    Shodan的爬虫全天候工作,并实时更新数据库。在任何时候使用Shodan查询,都可以获得最新的结果。

    分布式

    爬虫分布于世界各地,包括:

    • 美国(东海岸和西海岸)
    • 中国
    • 冰岛
    • 法国
    • 台湾
    • 越南
    • 罗马尼亚
    • 捷克共和国

    从世界各地收集数据是为了防止地区各种因素的差异造成数据的偏差。例如,美国的许多系统管理员会封锁整个中国的IP范围,分布在世界各地的Shodan爬虫就可以确保任何全国性的封锁不会影响数据收集。

    随机

    Shodan爬虫的基本算法是:

    1. 生成一个随机的IPv4地址
    2. 从Shodan能解析的端口列表中生成一个随机端口测试
    3. 检测随机端口上的随机IPv4地址,并获取Banner
    4. 继续步骤1

    这意味着爬行器不扫描增量的网络范围。爬行是完全随机的,以确保在任何给定的时间内对因特网进行统一的覆盖,并防止数据的偏差。

    深入SSL

    SSL正在成为互联网上重要的服务,Shodan也随之扩展性地收集其banner,包括收集每个SSL的功能服务及其漏洞信息,比如HTTPS,不仅仅是SSL证书。

    漏洞测试

    心脏出血漏洞

    如果某服务有心脏出血漏洞Heartbleed bug),返回的banner将包含以下2个附加属性。opts.heartbleed包含了对服务进行心脏出血漏洞测试的原始回应(在测试中,爬虫只抓取少量溢出来确认服务是否受到心脏出血的影响,不会获取泄露的私钥)。若设备容易受到攻击,爬虫会将CVE-2014-0160添加到opts.vulns列表中;若不易受到攻击,则会返回!CVE-2014-0160

    {
        "opts": {
            "heartbleed": "... 174.142.92.126:8443 - VULNERABLE\n",
            "vulns": ["CVE-2014-0160"]
        }
    }
    

    Shodan也支持漏洞信息搜索。比如,要搜索美国受心脏滴血漏洞影响可在shodan输入country:US vuln:CVE-2014-0160

    FREAK

    如果服务支持导出密码,则爬虫程序就将“CVE-2015-0204”项添加到opts.vulns属性

    "opts": {
        "vulns": ["CVE-2015-0204"]
    }
    
    Logjam

    爬虫将短暂使用Diffie-Hellman密码连接到SSL服务,若连接成功就存储返回以下信息:

    "dhparams": {
        "prime": "bbbc2dcad84674907c43fcf580e9...",
        "public_key": "49858e1f32aefe4af39b28f51c...",
        "bits": 1024,
        "generator": 2,
        "fingerprint": "nginx/Hardcoded 1024-bit prime"
    }
    
    版本

    通常情况下,当浏览器连接到SSL服务时,它将协商应该与服务器一起使用的SSL版本和密码。然后,他们会就某个SSL版本(如TLSv1.2)达成一致,然后将其用于通信。

    Shodan爬虫开始按照上面所述的正常请求,与服务器进行协商连接SSL。但是,之后,他们还显式地尝试使用其他的SSL版本连接到服务器。换句话说,爬行器尝试使用SSLv2、SSLV3、TLSv1.0、TLSv1.1和TLSv1.2连接到服务器,来确定该SSL服务支持的所有版本。

    收集到的信息将在ssl.versions版本字段中显示:

    {
        "ssl": {
            "versions": ["TLSv1", "SSLv3", "-SSLv2", "-TLSv1.1", "-TLSv1.2"]
        }
    }
    

    如果在版本前面有一个-符号,那么该设备不支持SSL版本。如果版本不以-开头,则服务支持给定的SSL版本。例如,上面的服务器支持: TLSv1SSLv3

    不支持版本:SSLv2TLSv1.1TLSv1.2

    TLS 1.0通常被标示为SSL 3.1,TLS 1.1为SSL 3.2,TLS 1.2为SSL 3.3。见传输层安全性协议

    版本信息可以通过shodan的网站或者API搜索。例如,输入ssl.version:sslv2搜索查询将返回允许使用SSLv2的所有SSL服务(包括HTTP、SMTP、HTTP、POP3、IMAP等)

    遵循链

    证书链是从root到最终用户的SSL证书列表;SSL的banner服务包括一个ssl.chain属性,该属性包含PEM序列化证书中链的所有SSL证书。

    基础之上

    对于大多数服务,爬虫试图分析主要的banner文本,并解析出有用的信息。不过有些情况是,抓取MongoDB的集合名称,从远程桌面服务获取屏幕截图,存储比特币的对等点列表。有两种Shodan使用的高级数据分析技术,我想强调一下:

    Web组件

    爬虫也会尝试确定创建网站的Web技术。对于http和https模块,将分析header和HTML来分解网站的组件。结果信息存储在http.components属性中。该属性是一个技术字典,关键是技术的名称(例如jQuery),值是另一个具有类别属性的字典。categories属性是与技术相关的类别列表。例如

    "http": {
        ...
            "components": {
                "jQuery": {
                    "categories": ["javascript-frameworks"]
                },
                "Drupal": {
                    "categories": ["cms"]
                },
                "PHP": {
                    "categories": ["programming-languages"]
                }
            },
                 ...
        },
    

    http.components表明该网站在运行Drupal内容管理系统,该系统本身使用的jQueryPHPShodan REST API使信息通过过滤器http.component和2个方面(http.componen和http.component_category)进行搜索。要获得所有可能的组件/类别值的完整列表,请使用新的方面。例如,要获得所有可能类别的完整列表,请使用以下shodan命令:

    shodan stats --facets http.component_category:1000 http
    

    级联

    如果一个banner返回了关于对等点的信息,或者有关于另一个运行服务的IP地址的信息,那么爬虫就会试图在这个IP/服务上执行一个banner抓取。例如:主线DHT(过去用于Bittorrent)的默认端口是6881。这样一个DHT节点的banner看起来如下:

    DHT Nodes
    97.94.250.250	58431
    150.77.37.22	34149
    113.181.97.227	63579
    252.246.184.180	36408
    83.145.107.53	52158
    77.232.167.126	52716
    25.89.240.146	27179
    147.23.120.228	50074
    85.58.200.213	27422
    180.214.174.82	36937
    241.241.187.233	60339
    166.219.60.135	3297
    149.56.67.21	13735
    107.55.196.179	8748
    

    以前,抓取工具会抓取上面的banner,然后继续抓取。现通过为DHT banner的抓取启用级联功能,抓取工具现在可以为所有的对等启动新的banner抓取请求。在上面的示例中,搜寻器将使用DHT banner捕获器,在IP为97.94.250.250的58431端口上启动扫描150.77.37.22,继续在端口34149上启动扫描,依此类推。也就是说,如果初始扫描数据包含有关其他潜在主机的信息,对IP的单次扫描可能会导致级联扫描。

    为了跟踪初始扫描请求与任何子级/级联请求之间的关系,我们引入了2个新属性:

    • _shodan.id:banner的唯一ID。如果可以从服务启动级联请求,这个属性就一定存在,但这并不一定意味着级联请求会成功。
    • _shodan.options.referrer:提供触发创建当前banner的banner的唯一ID。即引用者是当前banner的父代。

    Web界面

    访问Shodan收集的数据最简单的方法是通过Web界面。所有的人都可以输入一个需要查询的设备。

    搜索查询说明

    默认情况下,搜索查询仅查看主横幅文本,不搜索元数据。例如,如果您要搜索“Google”,那么搜索结果将只包含标题中显示“Google”文本的结果;它不一定会返回Google网络范围的结果。

    如上所述,搜索“谷歌”返回许多组织购买并连接到Internet的谷歌搜索设备;它不返回谷歌的服务器。

    Shodan将尝试找到匹配所有搜索项的结果,这意味着隐含地在每个搜索项之间存在+AND连接符号。例如,搜索查询“apache + 1.3”相当于“apache 1.3”

    要搜索特定的元数据需要使用搜索过滤器。

    过滤器

    过滤器是Shodan用来根据服务或设备的元数据缩小搜索结果的特殊关键字。输入过滤器的格式是:

    注意:和值之间没有空格

    要使用包含过滤器空间的值,必须将值包含在双引号中。例如搜索圣地亚哥的所有联网设备:

    筛选多个值可以使用,分开。比如要查找23号端口和1023号端口上运行telnet的设备:

    如果过滤语法不允许使用逗号(如port,hostname,net),那么可以使用多个值联合查询:

    port:80,8080  city:"beijing" hostname:"baidu.com"
    

    当然也可以使用-排除结果。比如:

    port:23,1023  -city:"San Diego"
    

    有时候也需要过滤一些主文本标题为空的搜索结果,这时候就需要使用-hash:0。比如:

    Shodan上的每个banner都有一个哈希值,对于空的banner,hash值就为0。若尝试搜索简短的、静态的banner,使用哈希过滤语法就比较便捷。

    shodan支持大量的过滤器,比较常用的是:

    过滤器名描述举例
    category现有的分类:ics, malwarecategory:"malware"
    city城市的名字city:"San Diego"
    country国家简写country:"ES" country:"CN"
    netCIDR格式的IP地址net:190.30.40.0/24
    hostname主机名或域名hostname:"google"
    port端口号port:21
    org组织或公司org:"google"
    ispISP供应商isp:"China Telecom"
    product软件、平台product:"Apache httpd" product:"openssh"
    os操作系统os:"Windows 7 or 8"
    version软件版本version:"2.6.1"
    geo经纬度geo:"46.9481,7.4474"

    搜索引擎

    默认情况下,搜索查询将查看过去30天内收集的数据。

    下载数据

    完成搜索后,顶部将出现一个名为“下载数据”的按钮。点击该按钮将为您提供以JSON,CSV(XML已经弃用)格式下载搜索结果的选项。

    JSON格式的文件,其中每行包括全部的banner和所有附带的元数据收集信息。这是保存所有可用信息的首选格式。格式与Shodan命令行客户端兼容,这意味着可以从Shodan网站下载数据,然后使用终端进一步处理。

    CSV格式的文件,返回包含IP、端口,banner,组织和主机名内容。它不包含由于CSV文件格式的限制收集的所有Shodan信息。如果只关心结果的基本信息,并希望快速将其加载到外部工具(如Excel)中,可以使用此方法。

    下载数据会消耗积分,积分需要在网站上购买。积分与Shodan API没有任何关联,并且不会每月自动更新。1积分可用于下载10000个结果。

    生成报告

    Shodan可让根据搜索查询生成报告。该报告包含图表,可以全面了解互联网上的分发结果。这个功能是免费的,任何人都可以使用。

    分享结果

    搜索完成之后,可以自定义,分享内容给其他Shodan用户。

    共享搜索查询是公开查看的

    例子

    搜索美国的服务器在8080端口上运行8.0版本IIS服务的服务器:

    port:8080 product:"Microsoft-IIS" country:"US" version:"8.0"
    

    搜索可能受到DOUBLEPULSAR攻击的机器:

    port:445 "SMB Version: 1" os:Windows !product:Samba
    

    搜索所有nist.gov域名里的apache服务:

    apache hostname:.nist.gov
    

    图标搜索

    通过已知icon hash搜索目标

    0x01 获取谷歌地址

    ping一下google,获得一个谷歌ip,为172.217.27.174

    0x02 查看原数据

    使用shodan搜索刚才获取的IP,并找到查看原始数据那一行

    0x03 查找favicon hash

    打开原始数据,找到data.1.http.favicon.hash,其值为708578229:

    0x04 图标hash搜索

    在Shodan网页搜索的过滤器处输入http.favicon.hash:708578229就可以搜索shodan所收录的谷歌的网站。

    0x05 favicon data

    data.1.http.favicon.data的值是base64,可以自行验证

    通过未知icon hash搜索目标

    0x01 背景

    在分析一个站点的登陆界面,没有其他信息,只有一个icon的时候,这时候就可以通过shodan搜索相同模板的站来入手,找突破口。

    如果目标已经被shodan收录,直接搜索对应的IP就能通过上述方法进行图标hash搜索。如果是目标未被收录,就可以通过现在这个方法来进行侧面突破。

    0x02 原理

    shodan对icon的处理其实是通过MurmurHash进行存储的。MurmurHash是一种非加密型哈希函数,适用于一般的哈希检索操作。由Austin Appleby在2008年发明,并出现了多个变种,[6]都已经发布到了公有领域(public domain)。与其它流行的哈希函数相比,对于规律性较强的key,MurmurHash的随机分布特征表现更良好。其地址在github

    0x03 icon hash的生成验证

    直接在本地使用pip安装mmh3。

    验证代码如下:

    import mmh3
    import requests
    response = requests.get('https://www.baidu.com/favicon.ico')
    favicon = response.content.encode('base64')
    hash = mmh3.hash(favicon)
    print hash
    

    运行验证代码获得icon hash

    在shodan中进行http.favicon.hash:-1507567067搜索,验证成功

    0x04 扩展

    例如http://common.cnblogs.com/favicon.ico得到博客园的icon,直接将图标地址放入脚本中计算其hash值,得到-395680774,再在shodan中搜索http.favicon.hash:-395680774

    Shodan地图

    https://maps.shodan.io/

    直观的地图界面,显示搜索的结果

    使用可改变显示风格,有卫星、街景选项。

    Shodan Exploits

    https://exploits.shodan.io

    Shodan Exploits收集来自CVE,Exploit DB和Metasploit的漏洞和攻击,使其可通过Web界面进行搜索。

    Shodan Exploit搜索的结果不同于Shodan,Exploit搜索的内容包括exploit的信息和元数据。

    名称描述举例
    author漏洞或exploit的作者author:"Tom Ferris"
    description描述description:"remote"
    platform平台platform:"php" platform:"windows" platform:"Linux"
    type漏洞类型type:"remote" type:"dos"

    Shodan Images

    https://images.shodan.io

    顶部的搜索框使用与Shodan主搜索引擎相同的语法。使用搜索框按组织或网点筛选最有用。但是,它也可以用于过滤显示的图像类型。

    图像数据主要来自:

    • VNC
    • Remote Desktop (RDP)
    • RTSP
    • Webcams
    • X Windows

    每个图像源来自不同的端口/服务,因此具有不同的banner。如果只想看到来自摄像头的图像,你可以搜索:

    查找VNC可以输入RFB,查找RTSP可以使用RTSP

    如果在Shodan中搜索截图,可以使用has_screenshot:true

    has_screenshot:true country:"KR"
    

    共享的Shodan搜索语法

    链接见https://www.shodan.io/explore

    关于Shodan的外部工具

    Shodan命令行界面

    安装最新的Shodan可以通过

    安装该工具之后,使用API对其进行初始化操作:

    alert命令

    alert命令提供创建、列表、清除和删除网络提醒的能力。

    convert命令

    convert命令是导出Shodan的搜索结果文件,比如JSON文件。

    count命令

    统计查询结果

    shodan count microsoft iis 6.0
    

    download命令

    搜索Shodan并将结果导出到JSON文件中(请参阅附录A)。

    默认情况下,只下载1000个结果。如果想导出更多的结果,加上--limit标志。

    download命令可以保存搜索结果到本地,随时使用parse命令处理结果,当再次导出相同搜索内容的时候,就不必再花费积分导出。

    shodan download  microsoft iis 6.0
    

    host命令

    查看有关主机的信息,例如它位于何处,哪些端口是开放的以及哪个组织拥有其IP。

    shodan host 189.201.128.250
    

    honeyscore命令

    检查IP地址是否是蜜罐。

    shodan honeyscore 41.231.95.212
    

    info命令

    查询自己的API账户信息,包括本月剩余的查询次数和扫描积分数量。

    myip命令

    返回本机的出口IP(类似于curl ip.cn

    parse命令

    使用parse来分析使用导出的搜索结果。它可以让过滤出感兴趣的字段,也可以将JSON转换为CSV。这样,对于导入到其他脚本比较友好。

    以下命令输出之前下载的Microsoft-IIS数据,并且将JSON文件转换为的CSV格式的文件。其中包括IP地址,端口和归属组织:

    shodan parse --fields ip_str,port,org --separator , microsoft-data.json.gz
    

    scan命令

    scan命令提供了一些子命令。最重要的一个是submit命令,这个命令可以使用shodan执行网络扫描。

    shodan scan submit 198.20.99.0/24
    

    默认情况下,它将显示IP,端口,主机名和数据。您可以使用--fields来打印感兴趣的任何banner字段。

    search命令

    search命令搜索时候,终端将以友好的方式显示结果。默认情况下,它将显示IP,端口,主机名和数据。不过可以使用--fields参数来打印感兴趣的任何banner字段。

    shodan search --fields ip_str,port,org,hostnames microsoft iis 6.0
    

    stats命令

    提供关于搜索的摘要信息,当然可以用于统计信息。命令显示了Apache Web服务器所在的最常用的国家:

    shodan stats --facets country apache
    

    stream命令

    stream命令提供对Shodan爬虫收集的实时数据流的访问。

    该命令支持许多不同的选项,但有2个是重要的提及:

    • --datadir:可以指定流数据存储的目录,生成的文件格式YYYY-MM-DD.json.gz。每天只要保持数据流运行,就会生成一个新的文件。
      • 例如:从实时数据流保存数据到指定路径,可以使用shodan stream --datadir /var/lib/shodan/
    • --limit:指定应下载多少结果。默认情况下,该stream命令将一直运行,直到退出该工具。但是,如果只想收集数据样本,那么--limit参数可确保收集少量记录。
      • 例如:shodan stream --limit 100可以连接到Shodan实时流,打印出前100条记录,然后退出
    • --ports:接受逗号分隔端口,收集指定端口记录的清单
      • 例如:shodan stream --ports 80,8080

    例子

    官方例子

    官方例子见 28 public asciicasts by Shodan:https://asciinema.org/~Shodan

    网络分析例子

    Shodan最常见的使用案例是使用它来更好地了解公共网络范围内的运行情况。Shodan命令行工具可以快速帮助、处理你的目标。例如,查看78.13.0.0/16段在网上暴露了多少服务。

    shodan count net:78.13/16
    

    count命令提供了关于78.13.0.0/16段暴露了4960个服务

    统计出该网段暴露的最多端口数的top10:

    shodan stats --facets port net:78.13/16
    

    有时候也需要详尽的端口暴露数分配报告,因此指定最大的端口,返回此端口范围的暴露数统计情况:

    shodan stats --facets port:100000 net:78.13/16
    

    该网络上有955个端口被发现暴露,并统计出各自端口暴露数。最多的是7547端口,这个端口是调制解调器的端口,这个端口也有很多的安全问题可以关注。在统计中也有一些非常见的标准端口,这些端口大多是为了掩盖自己真实的Web服务。

    统计该网段的80、443端口上的服务情况:

    shodan stats --facets product "HTTP net:78.13/16 -port:80,443"
    

    这里应将端口号放在引号中,防止bash认为该port是shodan的命令参数。

    分析SSL的例子

    若要了解整个网络的SSL使用情况,可按照以下来查询

    shodan stats --facets ssl.version HTTP net:78.13/16
    

    可以看出大多运行的是TLS1.0及其以上的版本,不过还有一些古老的设备在使用sslv2。

    在web端的shodan搜索出来:

    分析telnet情况

    mkdir telnet-data
    
    shodan stream --ports 23,1023,2323 --datadir telnet-data / --limit 10000
    

    创建一个名为telnet-data的目录来存储telnet数据,然后从实时数据流中导出10000条记录,存储在telnet-data文件夹中

    Maltego扩展组件/assets

    shodan扩展组件为Maltego提供了2/assets新的实体功能(service和exploit)和五个转换:

    • searchShodan
    • searchShodanByDomain
    • searchShodanByNetblock
    • toShodanHost
    • searchExploits

    浏览器插件

    ChromeFirefox都有插件可以使用

    Metasploit

    MSF主要有两个shodan模块:

    1. Shodan的常用搜索:`auxiliary/gather/shodan_search`
    
    use auxiliary/gather/shodan_search
    
    set SHODAN_APIKEY ********************
    
    set QUERY ****
    
    run 
    
    1. Shodan的蜜罐验证:auxiliary/gather/shodan_honeyscore:
    use auxiliary/gather/shodan_honeyscore 
    
    set SHODAN_APIKEY ********************
    
    set TARGET your_target
    
    run
    

    Recon-ng

    先添加shodan API的key:

    keys add shodan_api VZbVJy***n8S****hS1YM1****enTuy
    

    显示可用的模块:

    使用shodan搜索主机名:

    use recon/domains-hosts/shodan_hostname
    

    设置参数,并运行:

    show options
    set SOURCE google
    set LIMIT 1
    run
    

    Developer API

    Shodan提供了一个开发者的API(https://developer.shdan.io/api)来编程,获取所需要的信息。可以通过网站完成的事情都可以通过代码完成。

    该API分为两部分:REST API和Streaming API。REST API提供搜索Shodan的方法,查找主机,获取关于查询的信息的摘要。Streaming API提供Shodan当前收集的数据的原始实时返回。有几个不同的套餐可以获取,该API获取的数据不能被搜索或用其他方式进行交互,适用于需要获取大量数据的人。

    只有购买开发者API计划的人才能获得Streaming API。

    使用限制

    根据API的套餐不同,API会有不同的限制:

    1. 搜索:每月的搜索次数有不同的限制,且需要使用查询积分。如果直接查询不会消耗查询积分,若进行过滤器或者是超过一页的搜索就需要消耗查询积分。搜索apache不需要消耗查询积分,搜apache country:"US"将会消耗一个查询积分,就算查询第二页也只会消耗一个查询积分。
    2. 扫描:按需获得的API也会根据积分限制每月扫描主机的数量。对于每个主机的扫描都需要一个扫描积分才能扫描。
    3. 网络提醒:根据不同的API的使用次数可以使用提醒功能监视所查询的IP。只有付费客户才能使用此功能,且无法在账户中创建超过100条提醒。

    Facets

    过滤器缩小搜索结果。而Facets提供关注的banner字段的聚合信息,会得到一个大的结果视图。例如,Shodan网站使用Facets来提供搜索的统计信息,类似于在左侧显示的:

    Facets有很多可以选择的参数。比如port:22Facetsssh.fingerprint,输出的时候就能显示出网络上SSH的详细条目。

    目前Facet只能在API和Shodan的命令行上使用

    入门

    所有的例子使用python来演示,当然其他的语言也有Shodan的库/客户端

    为python环境安装Shodan库:

    如果已经安装shodan,可以使用:

    初始化

    首先要做的是初始化:

    import shodan
    api = shodan.Shodan('YOUR API KEY')
    

    在https://account.shodan.io获取API密钥

    搜索

    已经拥有API密钥之后,准备进行搜索:

    #将请求包放在一个try/except块中,捕获抛出的异常
    try:
            # Search Shodan
            results = api.search('apache')
     
            # Show the results
            print 'Results found: %s' % results['total']
            for result in results['matches']:
                    print 'IP: %s' % result['ip_str']
                    print result['data']
                    print ''
    except shodan.APIError, e:
            print 'Error: %s' % e
    

    首先调用api对象的Shodan.search()方法,该方法返回的结果放入字典之中。然后,打印出搜索结果数量,最后将返回的结果进行遍历循环,并打印其IP和banner。每一页的搜索结果多达100个。

    当然查询的时候还有很多返回信息。下面是Shodan.search返回的部分信息:

    {
    	'total': 23199543,
    	'matches': [
    		{
    			'data': 'HTTP/1.1 301 Moved Permanently\r\nDate: Thu, 01 Feb 2018 02:43:53 GMT',
    			'hostnames':['mypage.ponparemall.com'],
    			'ip': 2685469827L,
    			'ip_str': '160.17.4.131',
    			'port': 80,443,
    			'isp': Recruit Holdings Co.,Ltd.,
    			'timestamp': '2018-02-01T02:47:34.036371'
    		},
    		...
    	]
    }
    

    有关banner可能包含的属性的完整列表请参阅附录A

    默认情况下,为了节省带宽使用量,banner中的一些大的字段会被截断(比如HTML的)。若想要检索所有的信息,只需使用minify=False禁用概要。例如,用以下代码可以将匿名VNC服务的查询结果全部返回:

    results = api.search('has_screenshot:true', minify=False)
    

    任何错误都会引发异常,将API请求封装在try/except是一种良好的代码习惯,不过为了简单起见,例子暂时不用try/except

    以上脚本仅输出第一页的搜索结果。要想获得第二页或更多的结果,可以使用page参数执行请求:

    results = api.search('apache',page=2)
    

    如果想遍历所有的结果,使用search_cursor()方法会更加便捷:

    for banner in api.search_cursor('apache'):
        print(banner['ip_str']) # 打印出该IP的每个banner
    

    search_cursor()方法只能循环结果,返回banner,不能使用Facets

    主机查找

    要用Shodan查找特定的IP,可用Shodan.host()函数:

    # 查询主机
    host = api.host('217.140.75.46')
    
    # 打印一般信息
    print """
    	IP: %s
    	Organization: %s
    	Operating System: %s
    """ % (host['ip_str'], host.get('org', 'n/a'), host.get('os', 'n/a'))
    
    # 打印所有的banner
    for item in host['data']:
    	print """
    		Port: %s
    		Banner: %s
    		""" % (item['port'], item['data'])
    

    默认情况下,Shodan只返回最近收集的主机的信息。如果想获取IP地址的完整历史记录,就使用history参数。

    host = api.host('217.140.75.46', history=True)
    

    虽然以上代码可以返回所有banner,但其中也包括一些可能不再在主机上活动的服务。

    扫描

    Shodan每月至少爬取一次,但是如果想用Shodan立即扫描网络,就可以使用API​​扫描。(订购的不同API有不同的扫描次数)

    与Nmap等工具扫描不同,使用Shodan进行的扫描是异步完成的,在进行Shodan扫描之后不会马上就收到扫描结果。这取决于开发人员是如何收集扫描结果的,是直接查找IP信息,是用Shodan直接搜索,还是订购实时信息流查找。Shodan命令行界面在启动扫描后,创建临时网络提醒,然后根据实时数据流出扫描结果。

    scan = api.scan('198.20.69.0/24')
    

    也可以使用CIDR表示法的地址来提供扫描目标:

    scan = api.scan(['198.20.49.30', '198.20.74.0/24'])
    

    在向API提交了扫描请求之后,会返回以下信息:

    {
    	'id': 'R2XRT5HH6X67PFAB',
    	'count': 1,
    	'credits_left': 5119
    }
    
    • id:唯一的扫描标识符
    • count: 提交的IP扫描数
    • credits: 剩余的扫描积分

    实时数据流

    Streaming API是一种基于HTTP的服务,可返回Shodan收集的实时数据流。它不提供任何搜索或查找功能,它只是抓取工具收集的所有内容的概要。

    例如,以下是一个脚本的部分代码,可以从容易受到FREAK(CVE-2015-0204)攻击的设备输出banner:

    def has_vuln(banner, vuln):
        if 'vulns' in banner['opts'] and vuln in banner['opts']['vulns']:
            return True
        return False
    
    for banner in api.stream.banners():
        if has_vuln(banner, 'CVE-2015-0204'):
            print banner
    

    在上面的例子中,has_vuln()方法检查服务是否可能存在CVE-2015-0204

    banner中的许多属性是可选的,这样可以节省空间和带宽。为了使可选属性更容易处理,最好在函数中封装对属性的访问。

    注意:常规的API能访问的数量仅仅是订购的API数量的1%,只有订购了API具有许可证的客户才有100%的访问权限

    网络提醒

    要创建网络提醒,需要提供一个提醒名称和一个IP范围。名称应该具有概述性,当受到提醒的时候,就大概知道是什么监视内容被返回了。

    alert = api.create_alert('Production network', '198.20.69.0/24')
    

    正如scan()方法一样,可以提供监视的网络范围列表:

    alert = api.create_alert('Production and Staging network', [
    	'198.20.69.0/24',
    	'198.20.70.0/24',
    ])
    

    只能使用有限数量的监视IP,并且每个帐户不能有超过100个监视提醒在活动。

    将网络提醒与扫描API结合使用时,一个有用的技巧是设置提醒的时间:

    alert = api.create_alert('Temporary alert', '198.20.69.0/24', expires=60)
    

    上述代码可以使得提醒会激活使用60s,60s之后就会停止,不再进行监视。

    成功创建提醒之后,API将返回以下对象:

    {
    	"name": "Production network", 
    	"created": "2015-10-17T08:13:58.924581", 
    	"expires": 0, 
    	"expiration": null, 
    	"filters": {
    		"ip": ["198.20.69.0/24"]
    	},
    	"id": "EPGWQG5GEELV4799",
    	"size": 256
    }
    

    订阅

    一旦创建了提醒,就可以将其用作实时数据流的监视。

    for banner in api.stream.alert(alert['id']):
    	print banner
    

    与常规实时流一样,该alert()方法提供了一个迭代器,其中每个项目都是由Shodan爬虫收集的banner。idalert()方法需要的唯一参数,在有网络提醒时会返回提醒ID。

    使用Shodan命令行界面

    接下来讲述如何使用Shodan的命令行界面快速清除基于Python代码创建的提醒。

    clear命令将会清除电脑上创建的所有alert。

    清除所有alert:

    $ shodan alert clear
    Removing Scan: 198.20.69.0/24 (ZFPSZCYUKVZLUT4F)
    Alerts deleted
    

    列出alert列表,确认有无alert:

    $ shodan alert list
    You haven't created any alerts yet.
    

    创建一个新的alert:

    $ shodan alert create "Temporary alert" 198.20.69.0/24
    Successfully created network alert!
    Alert ID: ODMD34NFPLJBRSTC
    

    最后一步是订阅监视提醒,并存储返回的数据。要为创建的警报传输结果,将ODMD34NFPLJBRSTC的警报ID提供给stream命令:

    $ mkdir alert-data
    $ shodan stream --alert=ODMD34NFPLJBRSTC --datadir=alert-data
    

    上面的命令中,使用ID为ODMD34NFPLJBRSTC的alert提醒,并且使用数据流传输结果。结果将存储在名为alert-data的目录中。每天都会在alert-data目录中生成具有一个当天收集banner的新文件。也就是说,不用关心轮转文件,stream命令会处理这些。几天后,目录将如下所示:

    $ ls alert-data
    2016-06-05.json.gz
    2016-06-06.json.gz
    2016-06-07.json.gz
    

    例子

    常用 Shodan 库函数

    • shodan.Shodan(key):初始化连接API
    • Shodan.count(query, facets=None):返回查询结果数量
    • Shodan.host(ip, history=False):返回一个IP的详细信息
    • Shodan.ports():返回Shodan可查询的端口号
    • Shodan.protocols():返回Shodan可查询的协议
    • Shodan.services():返回Shodan可查询的服务
    • Shodan.queries(page=1, sort='timestamp', order='desc'):查询其他用户分享的查询规则
    • Shodan.scan(ips, force=False):使用Shodan进行扫描,ips可以为字符或字典类型
    • Shodan.search(query, page=1, limit=None, offset=None, ufacets=None, minify=True):查询Shodan数据

    例子——基本的搜索

    #!/usr/bin/env python
    #
    # shodan_ips.py
    # Search SHODAN and print a list of IPs matching the query
    #
    # Author: achillean
    
    import shodan
    import sys
    
    # Configuration
    API_KEY = "VZbVJyrIrn8SCSxHhS1YM1XPeKt5nTuy"
    
    # Input validation
    if len(sys.argv) == 1:
            print 'Usage: %s <search query>' % sys.argv[0]
            sys.exit(1)
    
    try:
            # Setup the api
            api = shodan.Shodan(API_KEY)
    
            # Perform the search
            query = ' '.join(sys.argv[1:])
            result = api.search(query)
    
            # Loop through the matches and print each IP
            for service in result['matches']:
                    print service['ip_str']
    except Exception as e:
            print 'Error: %s' % e
            sys.exit(1)
    

    例子——使用Facets收集概要信息

    Shodan API的强大功能其中之一是获取各种属性的概要信息。若想了解哪些国家的Apache服务器最多,那么可以使用Facets;若想知道哪个版本的nginx最受欢迎,可以使用Facets;若想查看Microsoft-IIS服务器的正常运行时间,那么可以使用Facets

    以下脚本显示了如何使用shodan.Shodan.count()方法在使用Shodan API进行搜索时候不返回任何详细结果,只返回组织、域、端口、ASN和国家的信息:

    #!/usr/bin/env python
    #
    # query-summary.py
    # Search Shodan and print summary information for the query.
    #
    # Author: achillean
    
    import shodan
    import sys
    
    # Configuration
    API_KEY = 'YOUR API KEY'
    
    # 键入想获取的信息
    FACETS = [
        'org',
        'domain',
        'port',
        'asn',
    
        # 只关注前三城市,('country', 3)让Shodan返回前三城市
        # Facet默认五个搜索区域(比如搜索1000个国家,就键入('country', 1000)
    ]
    
    FACET_TITLES = {
        'org': 'Top 5 Organizations',
        'domain': 'Top 5 Domains',
        'port': 'Top 5 Ports',
        'asn': 'Top 5 Autonomous Systems',
        'country': 'Top 3 Countries',
    }
    
    # Input validation
    if len(sys.argv) == 1:
        print 'Usage: %s <search query>' % sys.argv[0]
        sys.exit(1)
    
    try:
        # 键入Shodan API
        api = shodan.Shodan(API_KEY)
    
        # Generate a query string out of the command-line arguments
        query = ' '.join(sys.argv[1:])
    
        # 使用count()方法,因为它不返回结果,不需要通过付费API才能使用
        # count()运行速度快于search().
        result = api.count(query, facets=FACETS)
    
        print 'Shodan Summary Information'
        print 'Query: %s' % query
        print 'Total Results: %s\n' % result['total']
    
        # 从Facets打印摘要信息。
        for facet in result['facets']:
            print FACET_TITLES[facet]
    
            for term in result['facets'][facet]:
                print '%s: %s' % (term['value'], term['count'])
    
            # 在摘要信息之间打印一条空行
            print ''
    
    except Exception, e:
        print 'Error: %s' % e
        sys.exit(1)
    

    例子——利用API编写GIF图片

    Shodan很好得保存了爬取的IP的完整历史记录,可以通过API编写Python脚本,输出Shodan爬虫收集的屏幕截图的。

    环境:

    需要的Python包:

    • shodan:sudo easy_install shodan安装
    • arrow: sudo easy_install arrow安装。arrow包用于将banner的时间戳字段解析为Python datetime对象。

    需要的软件包:

    sudo apt-get install imagemagick //将多个图像合并成一个GIF动画所需要
    
    easy_install -U requests simplejson
    

    代码参见:Timelapse GIF Creator using the Shodan API

    import arrow
    import os
    import shodan
    import shodan.helpers as helpers
    import sys
    
    
    # Settings
    API_KEY = ''
    
    # The user has to provide at least 1 Shodan data file
    if len(sys.argv) < 2:
    	print('Usage: {} <shodan-data.json.gz> ...'.format(sys.argv[0]))
    	sys.exit(1)
    
    # GIFs are stored in the local "data" directory
    os.mkdir('data')
    
    # Setup the Shodan API object
    api = shodan.Shodan(API_KEY)
    
    # Loop over all of the Shodan data files the user provided
    for banner in helpers.iterate_files(sys.argv[1:]):
    	# See whether the current banner has a screenshot, if it does then lets lookup
    	# more information about this IP
    	has_screenshot = helpers.get_screenshot(banner)
    	if has_screenshot:
    		ip = helpers.get_ip(banner)
    		print('Looking up {}'.format(ip))
    		host = api.host(ip, history=True)
    		
    		# Store all the historical screenshots for this IP
    		screenshots = []
    		for tmp_banner in host['data']:
    			# Try to extract the image from the banner data
    			screenshot = helpers.get_screenshot(tmp_banner)
    			if screenshot:
    				# Sort the images by the time they were collected so the GIF will loop
    				# based on the local time regardless of which day the banner was taken.
    				timestamp = arrow.get(banner['timestamp']).time()
    				sort_key = timestamp.hour
    
    				# Add the screenshot to the list of screenshots which we'll use to create the timelapse
    				screenshots.append((
    					sort_key,
    					screenshot['data']
    				))
    
    		# Extract the screenshots and turn them into a GIF if we've got more than a few images
    		if len(screenshots) >= 3:
    			# screenshots is a list where each item is a tuple of:
    			# (sort key, screenshot in base64 encoding)
    			# 
    			# Lets sort that list based on the sort key and then use Python's enumerate
    			# to generate sequential numbers for the temporary image filenames
    			for (i, screenshot) in enumerate(sorted(screenshots, key=lambda x: x[0], reverse=True)):
    				# Create a temporary image file
    				# TODO: don't assume that all images are "jpg", use the mimetype instead
    				open('/tmp/gif-image-{}.jpg'.format(i), 'w').write(screenshot[1].decode('base64'))
    			
    			# Create the actual GIF using the  ImageMagick "convert" command
    			# The resulting GIFs are stored in the local data/ directory
    			os.system('convert -layers OptimizePlus -delay 5x10 /tmp/gif-image-*.jpg -loop 0 +dither -colors 256 -depth 8 data/{}.gif'.format(ip))
    
    			# Clean up the temporary files
    			os.system('rm -f /tmp/gif-image-*.jpg')
    
    			# Show a progress indicator
    			print('GIF created for {}'.format(ip))
    

    使用iterate_files()遍历Shodan数据文件。在shodan.Shodan.host()方法中获取所有Shodan收集的IP的banner历史记录。

    下载屏幕截图的JSON数据包:

    shodan download --limit -1 screenshots.json.gz has_screenshot:true
    

    *例子——公开的MongoDB数据

    MongoDB是一个流行的NoSQL数据库。在很长一段时间内,MongoDB默认是不需要输入用户名和密码,就可以登录。导致了许多MongoDB的服务在互联网上可以被公开访问。(见shodan扫描结果:3万个MongoDB可以无密码访问 大概包含595Tb数据

    使用Shodan抓取这些数据库的banner,其中包含了大量关于存储数据的信息。以下是banner的概要:

    IP: 115.231.180.54
    MongoDB Server Information
    Authentication partially enabled
    {
        "storageEngines": [
            "devnull", 
            "ephemeralForTest", 
            "mmapv1", 
            "wiredTiger"
        ], 
        "maxBsonObjectSize": 16777216, 
        "ok": 1.0, 
        "bits": 64, 
        "modules": [], 
        "openssl": {
            "compiled": "OpenSSL 1.0.1f 6 Jan 2014", 
            "running": "OpenSSL 1.0.1f 6 Jan 2014"
        }, 
        "javascriptEngine": "mozjs", 
        "version": "3.2.18", 
        "gitVersion": "4c1bae566c0c00f996a2feb16febf84936ecaf6f", 
        "versionArray": [
            3, 
            2, 
            18, 
            0
        ], 
        "debug": false, 
        "buildEnvironment": {
            "cxxflags": "-Wnon-virtual-dtor -Woverloaded-virtual -Wno-maybe-uninitialized -std=c++11", 
            "cc": "/opt/mongodbtoolchain/bin/gcc: gcc (GCC) 4.8.2", 
            "linkflags": "-fPIC -pthread -Wl,-z,now -rdynamic -fuse-ld=gold -Wl,-z,noexecstack -Wl,--warn-execstack", 
            "distarch": "x86_64", 
            "cxx": "/opt/mongodbtoolchain/bin/g++: g++ (GCC) 4.8.2", 
            "ccflags": "-fno-omit-frame-pointer -fPIC -fno-strict-aliasing -ggdb -pthread -Wall -Wsign-compare -Wno-unknown-pragmas -Winvalid-pch -Werror -O2 -Wno-unused-local-typedefs -Wno-unused-function -Wno-deprecated-declarations -Wno-unused-but-set-variable -Wno-missing-braces -fno-builtin-memcmp", 
            "target_arch": "x86_64", 
            "distmod": "ubuntu1404", 
            "target_os": "linux"
        }, 
        "sysInfo": "deprecated", 
        "allocator": "tcmalloc"
    },
    ....
    

    基本上,banner是MongoDB服务器信息和及其3个JSON对象用逗号分隔而组成。或者有的banner里有需要登陆凭据的验证字符——“authentication enabled”。每个JSON对象都包含有关数据库的不同信息,建议在Shodan网站上查看完整的头信息,方法是搜索:

    metrics字符保证了搜索时只搜索不需要认证的MongoDB服务

    接下来使用banner信息统计最流行的数据库名称,以及在互联网上暴露的数据量。基本的工作流程:

    1. 下载所有的MongoDB banner数据
    2. 处理下载的文件,并输出前十个数据库名称的列表以及总数据大小
    shodan download --limit -1 mongodb-servers.json.gz product:mongodb
    

    上述命令功能是,利用Shodan搜索关于mongodb的设备信息,并使用--limit -1指令下载所有的查询结果,输出到mongodb-servers.json.gz文件。

    使用Python脚本处理刚才所下载的数据。

    要轻松遍历文件,将使用shodan.helpers.iterate_files()方法:

    import shodan.helpers as helpers
    import sys
    
    # datafile变量是命令行的第一个参数
    datafile = sys.argv[1]
    
    for banner in helpers.iterate_files(datafile):
        # 获得banner
    

    由于每个banner仅仅是带有头信息的JSON,因此可以使用simplejson库将banner处理为本地的Python字典:

    # 去除Shodan加入的MongoDB的头信息
    data = banner['data'].replace('MongoDB Server Information\n', '').split('\n},\n'\
    )[2]
    # 加载数据库信息
    data = simplejson.loads(data + '}')
    

    接下来的事情就是记录暴露的数据总量和最流行的数据库名称:

    total_data = 0
    databases = collections.defaultdict(int)
    
    ...
    
        # 然后循环
        # 跟踪可访问的数据
        total_data += data['totalSize']
    
        # 跟踪哪些数据库使用最多
        for db in data['databases']:
            databases[db['name']] += 1
    

    Python有一个有用的collections.defaultdict类,该类的功能是,如果字典的键尚不存在,这个类将自动为字典的键创建一个默认值。只需访问 MongoDB banner的totalSizedatabases属性即可收集的信息。最后,我们只需要输出实际结果:

    print('Total: {}'.format(humanize_bytes(total_data)))
    
    counter = 1
    for name, count in sorted(databases.iteritems(), key=operator.itemgetter(1),reverse=True[:10]:
        print('#{}\t{}: {}'.format(counter, name, count))
        counter += 1
    

    首先打印数据总量,然后使用humanize_bytes()方法将字节存储转换为易读的MB、GB等存储格式。

    其次,对数据库集合进行多次逆序遍历排序(key=operator.itemgetter(1)),取结果的TOP10([:10])。

    以下是读取Shodan数据文件并分析banner的完整脚本:

    #!C:\Python27\python.exe
    # -*- coding: utf-8 -*-
    # @Time    : 2018/2/2 10:01
    # @Author  : b404
    # @File    : MongDB_Shodan.py
    # @Software: PyCharm
    
    import collections
    import operator
    import shodan.helpers as helpers
    import sys
    import simplejson
    
    def humanize_bytes(bytes, precision=1):
        """将字节转换为易读的存储单位MB、GB等
    
        Assumes `from __future__ import division`.
    
        >>> humanize_bytes(1)
        '1 byte'
        >>> humanize_bytes(1024)
        '1.0 kB'
        >>> humanize_bytes(1024*123)
        '123.0 kB'
        >>> humanize_bytes(1024*12342)
        '12.1 MB'
        >>> humanize_bytes(1024*12342,2)
        '12.05 MB'
        >>> humanize_bytes(1024*1234,2)
        '1.21 MB'
        >>> humanize_bytes(1024*1234*1111,2)
        '1.31 GB'
         >>> humanize_bytes(1024*1234*1111,1)
            '1.3 GB'
            """
        abbrevs = (
            (1 << 50L, 'PB'),
            (1 << 40L, 'TB'),
            (1 << 30L, 'GB'),
            (1 << 20L, 'MB'),
            (1 << 10L, 'kB'),
            (1, 'bytes')
        )
        if bytes == 1:
            return '1 byte'
        for factor, suffix in abbrevs:
            if bytes >= factor:
                break
        return '%.*f %s' % (precision, bytes / factor, suffix)
    
    total_data = 0
    databases = collections.defaultdict(int)
    for banner in helpers.iterate_files(sys.argv[1]):
        try:
            # 去除Shodan加入的MongoDB的头信息
            data = banner['data'].replace('MongoDB Server Information\n', '').split('\n},\n')[2]
    
            # 加载数据库信息
            data = simplejson.loads(data + '}')
            # 记录公开访问的数据量
            total_data += data['totalSize']
    
            # 跟踪哪些数据库名称最常见
            for db in data['databases']:
                databases[db['name']] += 1
        except Exception, e:
            pass
    
    print('Total: {}'.format(humanize_bytes(total_data)))
    
    counter = 1
    for name, count in sorted(databases.iteritems(), key=operator.itemgetter(1), reverse=True)[:10]:
        print('#{}\t{}: {}'.format(counter, name, count))
    counter += 1
    

    该脚本输出:

    Total: 569.7 GB
    #1	Warning: 1448
    #1	local: 1172
    #1	admin: 488
    #1	README: 113
    #1	config: 67
    #1	test: 43
    #1	DATA_HAS_BEEN_BACKED_UP: 19
    #1	logs: 19
    #1	db_has_been_backed_up: 15
    #1	gpsreal: 11
    

    工业控制系统

    工控指的是工业自动化控制,主要利用电子电气、机械、软件组合实现。打比方说,工业控制系统(ICS)是控制周围世界的计算机。它们负责管理办公室的空调、发电厂的涡轮机、剧院的照明设备或者工厂的机器人。

    可以去灯塔实验室(http://plcscan.org/blog/)和https://cs3sthlm.se/ 了解工控的相关姿势

    SHINE项目(SHodan INTElligence Extraction)研究表明,从2012至2014年,互联网上至少有200万个可公开访问的工控设备。在2012年,第一个包含500,000个ICS设备的数据库被发送到ICS- cert。ICS-CERT认定,在美国50万的设备中,大约有7200个是关键的基础设施。随着对工控安全做出很多努力,越来越多的工控设备进行漏洞修补或者关机下线。但这是一个具有挑战性的问题,而且针对工控安全问题的解决方案没有一个是简单的。

    下图是2014年Shodan爬取的工控全球分布图:

    常用缩写

    以下是一些常用的缩写,有助于后面了解协议,快速找到ICS设备:

    缩写说明
    SCADA数据采集与监视控制系统
    ICS工业控制系统
    DCS分布式控制系统/集散控制系统
    PCS过程控制系统
    ESD应急停车系统
    PLC可编程序控制器(Programmable Logic Controller)
    RTU远程终端控制系统
    IED智能监测单元
    HMI人机界面(Human Machine Interface)
    MIS管理信息系统(Management Information System)
    SIS生产过程自动化监控和管理系统(Supervisory Information System)
    MES制造执行管理系统
    BMS建筑管理系统(uilding Management System)
    VNC虚拟网络计算(Virtual Network Computing)

    协议

    识别联网的控制系统有两种不同的方式:

    1. 在ICS环境中使用的非ICS协议。

    Shodan的ICS搜索结果大部分是通过搜索WEB服务器或其他常用的协议发现的,这些协议并不是直接与ICS相连接,而是可以在ICS网络上看到。例如,运行在HMI上的Web服务器或未经身份验证的Windows远程桌面直接连接在ICS网络上,这些协议将回提供一个ICS的可视化界面。(但大多常用某种形式进行身份验证)

    2. ICS协议

    ICS协议是控制系统使用的原始协议。每个ICS协议都有其独特的标志,它们都有一个共同点:它们不需要任何认证。这意味着,如果可以远程访问工业设备,则可以自动对其进行读取和写入。但是,原始的ICS协议往往很难开发,是独有的,实际也很难与控制系统交互。也就是使用Shodan很容易检查设备是否支持ICS协议。

    下图的banner是一个Siemens S7 PLC设备的Shodan搜索图,banner暴露了其序列号和位置:

    Modbus

    MODBUS协议定义了一个与基础通信层无关的简单协议数据单元(PDU)。特定总线或网络上的MODBUS协议映射能够在应用数据单元(ADU)上引入一些附加域。

    工控安全问题:

    • 缺乏认证:仅需要使用一个合法的Modbus地址和合法的功能码即可以建立一个Modbus会话
    • 缺乏授权:没有基于角色的访问控制机制,任意用户可以执行任意的功能。
    • 缺乏加密:地址和命令明文传输,可以很容易地捕获和解析

    PROFIBUS

    PROFIBUS 一种用于工厂自动化车间级监控和现场设备层数据通信与控制的现场总线技术,可实现现场设备层到车间级监控的分散式数字控制和现场通信网络

    DNP3

    DNP3 DNP(Distributed Network Protocol,分布式网络协议)是一种应用于自动化组件之间的通讯协议,常见于电力、水处理等行业。简化OSI模型,只包含了物理层,数据层与应用层的体系结构(EPA)。SCADA可以使用DNP协议与主站、RTU、及IED进行通讯。

    ICCP

    ICCP 电力控制中心通讯协议。

    OPC

    OPC 过程控制的OLE (OLE for Process Control)。OPC包括一整套接口、属性和方法的标准集,用于过程控制和制造业自动化系统。

    BACnet

    BACnet 楼宇自动控制网络数据通讯协议(A Data Communication Protocol for Building Automation and Control Networks)。BACnet 协议是为计算机控制采暖、制冷、空调HVAC系统和其他建筑物设备系统定义服务和协议

    CIP

    CIP 通用工业协议,被deviceNet、ControINet、EtherNet/IP三种网络所采用。

    Siemens S7

    Siemens S7 属于第7层的协议,用于西门子设备之间进行交换数据,通过TSAP,可加载MPI,DP,以太网等不同物理结构总线或网络上,PLC一般可以通过封装好的通讯功能块实现。

    其他工控协议

    其他工控协议IEC 60870-5-104、EtherNet/IP、Tridium Niagara Fox、Crimson V3、OMRON FINS、PCWorx、ProConOs、MELSEC-Q

    架构缺陷

    • 未经真正安全考验的组件与协议
      • 若有Web服务,那就利用Web服务该有的缺陷
      • 经典渗透技巧大多适用于工控网络
    • 稳定性优先+懒 → 升级难
    • 互联网的便利性,让工控组件暴露在网络空间里
    • 以协议研究为出发点

    工控系统的指纹识别技术

    http://plcscan.org/blog/2017/03/fingerprint-identification-technology-of-industrial-control-system/

    信息探测

    Ethernet/IP

    Port:44818

    • Nmap: enip-enumerate.nse
    • Python: https://github.com/paperwork/pyenip
    • Wireshark dissector
      • src/epan/dissector/packet-etherip.c

    Modbus

    • port:502
    • 抓取设备相关信息
    • Nmap: modicon-info.nse
    • Wireshark dissector
      • src/epan/dissector/packet-mbtcp.c
    • 认证与加密缺失

    IEC 61870-5-101/104

    • Port: 2404
    • 判断是否使用该协议
      • Nmap: iec-identify.nse
      • Wireshark dissector
        • src/epan/dissectors/packet-iec104.c
    • 认证与加密缺失

    Siemens S7

    • key:Siemens S7 PLC(102)
    • 抓取设备相关信息
    • Nmap: s7-enumerate_.nse
      • http://plcscan.org/blog/wp-content/uploads/2014/11/s7-enumerate.nse_.txt
    • Wireshark plugins
      • http://sourceforge.net/projects/s7commwireshark/
    • 弱加密,易破解

    Tridium Niagara Fox

    • port:1911
    • 抓取设备信息
      • Nmap: fox-info.nse

    以下是有关banner的最新属性字段列表(若要查看最新的,请访问在线文档):

    常用属性

    名称描述举例
    asn自治系统号AS4837
    data服务的主要bannerHTTP/1.1 200…
    ip整数型的IP地址格式493427495
    ip_str字符串型IP199.30.15.20
    ipv6字符串型IPv62001:4860:4860::8888
    port服务的端口号80
    timestamp收集信息的日期2014-01-15T05:49:56.283713
    hash数据属性的散列值Numeric hash of the data property
    hostnames目标IP的主机名[“shodan.io”, “www.shodan.io”]
    domains目标IP的所有域名[“shodan.io”]
    link网络连接类型以太网/调制解调器
    location设备的物理地址见下文
    opts补充或者实验数据不包含在主要的banner中 
    org分配IP的组织Google Inc.
    isp负责IP空间的ISPVerizon Wireless
    os操作系统Linux
    uptimeIP上线时间50
    tags描述设备用途的标签列表(仅供企业型账号使用)[“ics”, “vpn”]
    transport用于收集banner的传输协议(UDP或者是TCP)tcp

    Elastic属性

    下列属性是为 Elastic (曾经的 ElasticSearch)收集的:

    名称描述
    elastic.cluster有关集群的一般信息
    elastic.indices集群上可用的索引列表
    elastic.nodes群集的节点/对等点列表及其信息

    HTTP(S)属性

    Shodan遵循HTTP响应的重定向,并将所有中间数据存储在banner中。抓取工具不遵循重定向的情况是HTTP请求被重定向到HTTPS位置,反之亦然:

    名称描述
    http.components用于创建网站的网络技术
    http.host发送主机名来抓取网站的HTML
    http.html网站的HTML内容
    http.html_hashhttp.html属性的数字散列
    http.location最终的HTML响应的位置
    http.redirects遵循的重定向列表。每个重定向项目有3个属性:主机,数据和位置
    http.robotsrobots.txt文件的网站
    http.serverHTTP响应的服务器头
    http.sitemap网站的Sitemap XML
    http.title网站的标题

    位置属性

    名称描述
    area_code设备位置的区号
    city城市名称
    country_code2个字母组成的国家代码
    country_code33个字母组成的国家代码
    country_name国家的全名
    dma_code指定市场区号(仅限美国)
    latitude纬度
    longitude经度
    postal_code邮政编码
    region_code地区代码

    SMB属性

    名称描述
    smb.anonymous服务是否允许匿名连接(true/false)
    smb.capabilities服务支持的功能列表
    smb.shares可用的网络共享列表
    smb.smb_version用于收集信息的协议版本
    smb.software提供服务的软件
    smb.raw服务器发送的十六进制编码数据包列表;如果想做SMB解析,这很有用

    SSH属性

    名称描述
    ssh.cipher协商期间使用的密码
    ssh.fingerprint设备的指纹
    ssh.kex服务器支持的密钥交换算法列表
    ssh.key服务器的SSH密钥
    ssh.mac消息认证码算法

    SSL 属性

    如果服务使用SSL进行包装,则Shodan将执行附加测试,并在以下属性中提供结果:

    名称描述
    ssl.acceptable_cas服务器接受的证书颁发机构列表
    ssl.cert可解析的SSL证书
    ssl.cipherSSL连接的首选密码
    ssl.chain从用户证书到根证书的SSL证书列表
    ssl.dhparamsDiffie-Hellman参数
    ssl.tlsext服务器支持的TLS扩展列表
    ssl.versions支持的SSL版本;如果该值用-开头,则该服务不支持该版本(例如:“-SSLv2”是指不支持SSLv2的服务)

    ISAKMP属性

    以下为使用ISAKMP协议的VPN(例如IKE)收集的属性:

    名称描述
    isakmp.initiator_spi初始化器的hex编码的安全参数索引
    isakmp.responder_spi用于响应器的hex编码的安全参数索引
    isakmp.next_payload启动后发送的下一个载荷
    isakmp.version协议版本;例如“1.0”
    isakmp.exchange_type交换类型
    isakmp.flags.encryption加密设置:true或false
    isakmp.flags.commit提交设置:true或false
    isakmp.flags.authentication认证设置:true或false
    isakmp.msg_id消息的十六进制编码标识
    isakmp.lengthISAKMP数据包的大小

    特殊属性

    _shodan

    _shodan属性包含有关数据如何被Shodan收集的信息。它与其他属性不同,因为它不提供有关设备的信息。相反,它会告诉你Shodan使用哪一个banner抓取器来与目标IP交互。这对于探知服务器上的端口运行服务情况是很重要的。例如,80端口是最知名的Web服务端口,但它也被各种恶意软件用来规避防火墙规则,_shodan属性将让你知道是该端口否用HTTP模块来收集数据或是否使用了反恶意软件模块。

    名称描述
    _shodan.crawler识别Shodan爬取工具的唯一ID
    _shodan.id此banner的唯一ID
    _shodan.module爬虫抓取banner而使用SHodan模块的名称
    _shodan.options数据收集期间使用的配置选项
    _shodan.hostname发送Web请求时使用的主机名
    _shodan.options.referrer为某端口/服务触发扫描的banner的惟一ID

    例子

    {
       "timestamp": "2014-01-16T08:37:40.081917","timestamp": "2014-01-16T08:37:40.081917",
       "hostnames": ["hostnames": [
          "99-46-189-78.lightspeed.tukrga.sbcglobal.net""99-46-189-78.lightspeed.tukrga.sbcglobal.net"
       ],],
       "org": "AT&T U-verse","org": "AT&T U-verse",
       "guid": "1664007502:75a821e2-7e89-11e3-8080-808080808080","guid": "1664007502:75a821e2-7e89-11e3-8080-808080808080",
       "data": "NTP\nxxx.xxx.xxx.xxx:7546\n68.94.157.2:123\n68.94.156.17:123","data": "NTP\nxxx.xxx.xxx.xxx:7546\n68.94.157.2:123\n68.94.156.17:123",
       "port": 123,"port": 123,
       "isp": "AT&T U-verse","isp": "AT&T U-verse",
       "asn": "AS7018","asn": "AS7018",
       "location": {"location": {
          "country_code3": "USA","country_code3": "USA",
          "city": "Atlanta","city": "Atlanta",
          "postal_code": "30328","postal_code": "30328",
          "longitude": -84.3972,"longitude": -84.3972,
          "country_code": "US","country_code": "US",
          "latitude": 33.93350000000001,"latitude": 33.93350000000001,
          "country_name": "United States","country_name": "United States",
          "area_code": 404,"area_code": 404,
          "dma_code": 524,"dma_code": 524,
          "region_code": null"region_code": null
       },},
       "ip": 1664007502,"ip": 1664007502,
       "domains": ["domains": [
          "sbcglobal.net""sbcglobal.net"
       ],],
       "ip_str": "99.46.189.78","ip_str": "99.46.189.78",
       "os": null,"os": null,
       "opts": {"opts": {
          "raw": "\\x97\\x00\\x03*\\x00\\x03\\x00H\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x01G\\x06\\xa7\\x8ec.\\xbdN\\x00\\x00\\x00\\x01\\x1dz\\x07\\x02\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00q\\x00\\x00\\x00i\\x00\\x00\\x00\\x00\\x00\\x00\\x00XD^\\x9d\\x02c.\\xbdN\\x00\\x00\\x00\\x01\\x00{\\x04\\x04\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00q\\x00\\x00\\x00o\\x00\\x00\\x00\\x00\\x00\\x00\\x00YD^\\x9c\\x11c.\\xbdN\\x00\\x00\\x00\\x01\\x00{\\x04\\x04\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00","raw": "\\x97\\x00\\x03*\\x00\\x03\\x00H\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x01G\\x06\\xa7\\x8ec.\\xbdN\\x00\\x00\\x00\\x01\\x1dz\\x07\\x02\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00q\\x00\\x00\\x00i\\x00\\x00\\x00\\x00\\x00\\x00\\x00XD^\\x9d\\x02c.\\xbdN\\x00\\x00\\x00\\x01\\x00{\\x04\\x04\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00q\\x00\\x00\\x00o\\x00\\x00\\x00\\x00\\x00\\x00\\x00YD^\\x9c\\x11c.\\xbdN\\x00\\x00\\x00\\x01\\x00{\\x04\\x04\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00",
          "ntp": {"ntp": {
             "more": false"more": false
          }}
       }}
    }}
    
    

    附录B:搜索语法关键词列表

    常用语法

    过滤器名描述类型举例
    after只显示给出日期之后的结果(dd/mm/yyyy)stringafter:"04/02/2017"
    asn自治系统号码stringasn:"AS4130"
    before只显示给出日期之前的结果(dd/mm/yyyy)stringbefore:"04/02/2017"
    category现有的分类:ics,malwarestringcategory:"malware"
    city城市的名字stringcity:"San Diego"
    country国家简写stringcountry:"ES" country:"CN"
    geo经纬度stringgeo:"46.9481,7.4474"
    hash数据的hash值int-hash:0
    has_ipv6是否是IPv6booleanhas_ipv6:true
    has_screenshot是否有截图booleanhas_screenshot:true
    hostname主机名或域名stringhostname:"google"
    ipip地址stringip:"54.67.82.248 "
    ispISP供应商stringisp:"China Telecom"
    org组织或公司stringorg:"google"
    os操作系统stringos:"Windows 7 or 8"
    port端口号intport:21
    postal邮政编码(仅限于美国)stringpostal:"98221"
    product软件、平台stringproduct:"Apache httpd" product:"openssh"
    region地区或国家别名string
    state string
    netCIDR格式的IP地址stringnet:190.30.40.0/24
    version软件版本stringversion:"2.6.1"
    vuln漏洞的CVE IDstringvuln:CVE-2014-0723

    HTTP过滤器

    名称描述类型
    http.component网站上所使用的网络技术名称string
    http.component_category网站上使用的网络组件的类别string
    http.htmlWeb bannerstring
    http.html_hash网站HTML的哈希值int
    http.status响应状态码int
    http.title网站title得bannerstring

    NTP 过滤器

    名称描述类型
    ntp.ip查找在其monlist中NTP服务器的IP 
    ntp.ip_count初始monlist返回的IP数量int
    ntp.more真/假; monlist集是否有更多的IP地址boolean
    ntp.portmonlist中的IP地址使用的端口int

    SSL过滤器

    名称描述类型
    has_ssl有无SSLboolean
    SSL搜索所有SSL的数据string
    ssl.alpn诸如HTTP/2的应用层协议string
    ssl.chain_count链中的证书数量int
    ssl.version可能的值:SSLv2,SSLv3,TLSv1,TLSv1.1,TLSv1.2string
    ssl.cert.alg证书算法string
    ssl.cert.expired是否是过期证书boolean
    ssl.cert.extension证书中的扩展名string
    ssl.cert.serial序列号为整数或十六进制字符串int/string
    ssl.cert.pubkey.bits公钥的位数int
    ssl.cert.pubkey.type公钥类型string
    ssl.cipher.versionSSL版本的首选密码string
    ssl.cipher.bits首选密码中的位数int
    ssl.cipher.name首选密码的名称string

    Telnet 过滤器

    名称描述类型
    telnet.option搜索所有选项string
    telnet.do对方执行的请求或期望对方执行指示的选项string
    telnet.dont对方停止执行的请求或不再期望对方执行指定的选项string
    telnet.will确认现在正在执行指定的选项string
    telnet.wont表示拒绝执行或继续执行指定的选项string

    附录C:Facets搜索

    常用Facets

    名称描述
    asn自治系统号码
    city城市的全名
    country国家的全名
    domain设备的域名
    has_screenshot有无可用的截图
    ispISP管理网络块
    link网络连接的类型
    org拥有该网块的组织
    os操作系统
    port服务的端口号
    postal邮政编码
    product软件/产品的名称
    region地区/国家的名称
    state区域的别名
    uptime主机启动的时间(以秒为单位计算)
    vuln漏洞的CVE ID

    HTTP Facets

    名称描述类型
    http.component网站上使用的网络技术的名称string
    http.component_category网站上使用的网络组件的类别string
    http.html_hashHTML网站的哈希值int
    http.status响应状态码int

    NTP Facets

    名称描述
    ntp.ipmonlist返回的IP地址
    ntp.ip_count初始monlist返回的IP数量
    ntp.more真假; monlist收集的是否有更多的IP地址
    ntp.portmonlist中的IP地址使用的端口

    SSH Facets

    名称描述
    ssh.cipher密码的名称
    ssh.fingerprint设备的指纹
    ssh.mac使用的MAC算法名称(例如:hmac-sha1)
    ssh.type认证密钥的类型(例如:ssh-rsa)

    SSL Facets

    名称描述
    ssl.version支持SSL版本
    ssl.alpn应用层协议
    ssl.chain_count链中的证书数量
    ssl.cert.alg证书算法
    ssl.cert.expired真假; 证书过期与否
    ssl.cert.serial证书序列号为整数
    ssl.cert.extension证书扩展名
    ssl.cert.pubkey.bits公钥的位数
    ssl.cert.pubkey公钥类型的名称
    ssl.cipher.bits首选密码中的位数
    ssl.cipher.name首选密码的名称
    ssl.cipher.versionSSL版本的首选密码

    Telnet Facets

    名称描述类型
    telnet.option显示所有选项string
    telnet.do对方执行的请求或期望对方执行指示的选项string
    telnet.dont对方停止执行的请求或不再期望对方执行指定的选项string
    telnet.will服务器支持指定的选项string
    telnet.wont服务器不支持指定的选项string

    附录D:端口列表

    PortService
    7Echo
    11Systat
    13Daytime
    15Netstat
    17Quote of the day
    19Character generator
    21FTP
    22SSH
    23Telnet
    25SMTP
    26SSH
    37rdate
    49TACACS+
    53DNS
    67DHCP
    69TFTP, BitTorrent
    70Gopher
    79Finger
    80HTTP, malware
    81HTTP, malware
    82HTTP, malware
    83HTTP
    84HTTP
    88Kerberos
    102Siemens S7
    104DICOM
    110POP3
    111Portmapper
    113identd
    119NNTP
    123NTP
    129Password generator protocol
    137NetBIOS
    143IMAP
    161SNMP
    175IBM Network Job Entry
    179BGP
    195TA14-353a
    311OS X Server Manager
    389LDAP
    389CLDAP
    443HTTPS
    443QUIC
    444TA14-353a, Dell SonicWALL
    445SMB
    465SMTPS
    500IKE (VPN)
    502Modbus
    503Modbus
    515Line Printer Daemon
    520RIP
    523IBM DB2
    554RTSP
    587SMTP mail submission
    623IPMI
    626OS X serialnumbered
    636LDAPS
    666Telnet
    771Realport
    789Redlion Crimson3
    873rsync
    902VMWare authentication
    992Telnet (secure)
    993IMAP with SSL
    995POP3 with SSL
    1010malware
    1023Telnet
    1025Kamstrup
    1099Java RMI
    1177malware
    1200Codesys
    1234udpxy
    1400Sonos
    1434MS-SQL monitor
    1515malware
    1521Oracle TNS
    1604Citrix, malware
    1723PPTP
    1741CiscoWorks
    1833MQTT
    1900UPnP
    1911Niagara Fox
    1962PCworx
    1991malware
    2000iKettle, MikroTik bandwidth test
    2081Smarter Coffee
    2082cPanel
    2083cPanel
    2086WHM
    2087WHM
    2123GTPv1
    2152GTPv1
    2181Apache Zookeeper
    2222SSH, PLC5, EtherNet/IP
    2323Telnet
    2332Sierra wireless (Telnet)
    2375Docker
    2376Docker
    2379etcd
    2404IEC-104
    2455CoDeSys
    2480OrientDB
    2628Dictionary
    3000ntop
    3260iSCSI
    3306MySQL
    3310ClamAV
    3386GTPv1
    3388RDP
    3389RDP
    3460malware
    3541PBX GUI
    3542PBX GUI
    3689DACP
    3702Onvif
    3780Metasploit
    3787Ventrilo
    4000malware
    4022udpxy
    4040Deprecated Chef web interface
    4063ZeroC Glacier2
    4064ZeroC Glacier2 with SSL
    4070HID VertX/ Edge door controller
    4157DarkTrack RAT
    4369EPMD
    4443Symantec Data Center Security
    4444malware
    4500IKE NAT-T (VPN)
    4567Modem web interface
    4664Qasar
    4730Gearman
    4782Qasar
    4800Moxa Nport
    4840OPC UA
    4911Niagara Fox with SSL
    4949Munin
    5006MELSEC-Q
    5007MELSEC-Q
    5008NetMobility
    5009Apple Airport Administration
    5060SIP
    5094HART-IP
    5222XMPP
    5269XMPP Server-to-Server
    5353mDNS
    5357Microsoft-HTTPAPI/2.0
    5432PostgreSQL
    5577Flux LED
    5601Kibana
    5632PCAnywhere
    5672RabbitMQ
    5900VNC
    5901VNC
    5938TeamViewer
    5984CouchDB
    6000X11
    6001X11
    6379Redis
    6666Voldemort database, malware
    6667IRC
    6881BitTorrent DHT
    6969TFTP, BitTorrent
    7218Sierra wireless (Telnet)
    7474Neo4j database
    7548CWMP (HTTPS)
    7777Oracle
    7779Dell Service Tag API
    8008Chromecast
    8009Vizio HTTPS
    8010Intelbras DVR
    8060Roku web interface
    8069OpenERP
    8087Riak
    8090Insteon HUB
    8099Yahoo SmartTV
    8112Deluge (HTTP)
    8126StatsD
    8139Puppet agent
    8140Puppet master
    8181GlassFish Server (HTTPS)
    8333Bitcoin
    8334Bitcoin node dashboard (HTTP)
    8443HTTPS
    8554RTSP
    8800HTTP
    8880Websphere SOAP
    8888HTTP, Andromouse
    8889SmartThings Remote Access
    9000Vizio HTTPS
    9001Tor OR
    9002Tor OR
    9009Julia
    9042Cassandra CQL
    9051Tor Control
    9100Printer Job Language
    9151Tor Control
    9160Apache Cassandra
    9191Sierra wireless (HTTP)
    9418Git
    9443Sierra wireless (HTTPS)
    9595LANDesk Management Agent
    9600OMRON
    9633DarkTrack RAT
    9869OpenNebula
    10001Automated Tank Gauge
    10001Ubiquiti
    10243Microsoft-HTTPAPI/2.0
    10554RTSP
    11211Memcache
    12345malware
    17000Bose SoundTouch
    17185VxWorks WDBRPC
    12345Sierra wireless (Telnet)
    11300Beanstalk
    13579Media player classic web interface
    14147Filezilla FTP
    16010Apache Hbase
    16992Intel AMT
    16993Intel AMT
    18245General Electric SRTP
    20000DNP3
    20547ProconOS
    21025Starbound
    21379Matrikon OPC
    23023Telnet
    23424Serviio
    25105Insteon Hub
    25565Minecraft
    27015Steam A2S server query, Steam RCon
    27016Steam A2S server query
    27017MongoDB
    28015Steam A2S server query
    28017MongoDB (HTTP)
    30313Gardasoft Lighting
    30718Lantronix Setup
    32400Plex
    37777Dahuva DVR
    44818EtherNet/IP
    47808Bacnet
    49152Supermicro (HTTP)
    49153WeMo Link
    50070HDFS Namenode
    51106Deluge (HTTP)
    53413Netis backdoor
    54138Toshiba PoS
    55443McAfee
    55553Metasploit
    55554Metasploit
    62078Apple iDevice
    64738Mumble
    {
        "hostnames": [],
        "title": "",
        "ip": 2928565374,
        "isp": "iWeb Technologies",
        "transport": "tcp",
        "data": "HTTP/1.1 200 OK\r\nExpires: Sat, 26 Mar 2016 11:56:36 GMT\r\nExpire\
    s: Fri, 28 May 1999 00:00:00 GMT\r\nCache-Control: max-age=2592000\r\nCache-Cont\
    rol: no-store, no-cache, must-revalidate\r\nCache-Control: post-check=0, pre-che\
    ck=0\r\nLast-Modified: Thu, 25 Feb 2016 11:56:36 GMT\r\nPragma: no-cache\r\nP3P:\
     CP=\"NON COR CURa ADMa OUR NOR UNI COM NAV STA\"\r\nContent-type: text/html\r\n\
    Transfer-Encoding: chunked\r\nDate: Thu, 25 Feb 2016 11:56:36 GMT\r\nServer: sw-\
    cp-server\r\n\r\n",
     "asn": "AS32613",
        "port": 8443,
        "ssl": {
            "chain": ["-----BEGIN CERTIFICATE-----\nMIIDszCCApsCBFBTb4swDQYJKoZIhvcN\
    AQEFBQAwgZ0xCzAJBgNVBAYTAlVTMREw\nDwYDVQQIEwhWaXJnaW5pYTEQMA4GA1UEBxMHSGVybmRvbj\
    ESMBAGA1UEChMJUGFy\nYWxsZWxzMRgwFgYDVQQLEw9QYXJhbGxlbHMgUGFuZWwxGDAWBgNVBAMTD1Bh\
    cmFs\nbGVscyBQYW5lbDEhMB8GCSqGSIb3DQEJARYSaW5mb0BwYXJhbGxlbHMuY29tMB4X\nDTEyMDkx\
    NDE3NTUyM1oXDTEzMDkxNDE3NTUyM1owgZ0xCzAJBgNVBAYTAlVTMREw\nDwYDVQQIEwhWaXJnaW5pYT\
    EQMA4GA1UEBxMHSGVybmRvbjESMBAGA1UEChMJUGFy\nYWxsZWxzMRgwFgYDVQQLEw9QYXJhbGxlbHMg\
    UGFuZWwxGDAWBgNVBAMTD1BhcmFs\nbGVscyBQYW5lbDEhMB8GCSqGSIb3DQEJARYSaW5mb0BwYXJhbG\
    xlbHMuY29tMIIB\nIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxc9Vy/qajKtFFnHxGOFPHTxm\
    \nSOnsffWBTBfyXnK3h8u041VxvZDh3XkpA+ptg2fWOuIT0TTYuqw+tqiDmg8YTsHy\njcpMFBtXV2cV\
    dhKXaS3YYlM7dP3gMmkGmH+ZvCgCYc7L9MIJxYJy6Zeuh67YxEMV\ngiU8mZpvc70Cg5WeW1uBCXtUAi\
    jDLsVWnhsV3YuxlweEvkRpAk3EHehKbvgMnEZS\nQ30QySe0GAqC7bWzKrwsJAOUk/+Js18+3QKb/LmD\
    a9cRjtFCTo6hYfPbfHj8RxQh\n4Xmnn/CtZ48wRQTqKXSO6+Zk3OuU7/jX1Gt/jxN6n77673e6uCsggT\
    wut/EtNwID\nAQABMA0GCSqGSIb3DQEBBQUAA4IBAQBb/yTy76Ykwr7DBOPAXc766n73OsZizjAt\n1k\
    mx7LxgN3X/wFxD53ir+sdOqbPgJl3edrE/ZG9dNl6LhUBbUK+9s6z9QicEfSxo\n4uQpFSywbGGmXInE\
    ZmyT4SsOLi/hNgy68f49LO1h6rn/p7QgIKd31g7189ZfFkFb\nRdD49s1l/Cc5Nm4XapUVvmnS91MlPk\
    /OOIg1Lu1rYkuc8sIoZdPbep52H3Ga7TjG\nkmO7nUIii0goB7TQ63mU67+NWHAmQQ8CtCDCN49kJyen\
    1WFjD6Je2U4q0IFQrxHw\nMy+tquo/n/sa+NV8QOj1gMVcFsLhYm7Z5ZONg0QFXSAL+Eyj/AwZ\n----\
    -END CERTIFICATE-----\n"],
            "cipher": {
                "version": "TLSv1/SSLv3",
                "bits": 256,
                "name": "DHE-RSA-AES256-GCM-SHA384"
            },
            "alpn": [],
            "dhparams": {
                "prime": "b10b8f96a080e01dde92de5eae5d54ec52c99fbcfb06a3c69a6a9dca52\
    d23b616073e28675a23d189838ef1e2ee652c013ecb4aea906112324975c3cd49b83bfaccbdd7d90\
    c4bd7098488e9c219a73724effd6fae5644738faa31a4ff55bccc0a151af5f0dc8b4bd45bf35c1a65e68cfda76d4da708df1fb2bc2e4a4371",
                "public_key": "2e30a6e455730b2f24bdaf5986b9f0876068d4aa7a4e15c9a1b9c\
    a05a420e8fd3b496f7781a9423d3475f0bedee83f0391aaa95a738c8f0e250a8869a86d41bdb0194\
    66dba5c641e4b2b4b82db4cc2d4ea8d9804ec00514f30a4b6ce170b81c3e1ce4b3d17647c8e5b8f6\
    65bb7f588100bcc9a447d34d728c3709fd8a5b7753b",
                "bits": 1024,
                "generator": "a4d1cbd5c3fd34126765a442efb99905f8104dd258ac507fd6406c\
    ff14266d31266fea1e5c41564b777e690f5504f213160217b4b01b886a5e91547f9e2749f4d7fbd7\
    d3b9a92ee1909d0d2263f80a76a6a24c087a091f531dbf0a0169b6a28ad662a4d18e73afa32d779d\
    5918d08bc8858f4dcef97c2a24855e6eeb22b3b2e5",
                "fingerprint": "RFC5114/1024-bit MODP Group with 160-bit Prime Order\
     Subgroup"
            },
            "versions": ["TLSv1", "-SSLv2", "SSLv3", "TLSv1.1", "TLSv1.2"]
        },
        "html": "\n\t\t<html><head>\n\t\t<meta charset=\"utf-8\">\n\t\t<meta http-eq\
    uiv=\"X-UA-Compatible\" 
    content=\"IE=edge,chrome=1\">\n\t\t<title></title>\n\t\t\
    <script language=\"javascript\" type=\"text/javascript\" src=\"/javascript/commo\
    n.js?plesk_version=psa-11.0.9-110120608.16\"/></script>\n\t\t<script language=\"\
    javascript\" type=\"text/javascript\" src=\"/javascript/prototype.js?plesk_versi\
    on=psa-11.0.9-110120608.16\"></script>\n\t\t<script>\n\t\t\tvar opt_no_frames = \
    false;\n\t\t\tvar opt_integrated_mode = false;\n\t\t</script>\n\t\t\n\t\t</head>\
    <body onLoad=\";top.location='/login.php3?window_id=&amp;requested_url=https%3A%\
    2F%2F174.142.92.126%3A8443%2F';\"></body><noscript>You will be redirected to the\
     new address in 15 seconds... If you are not automatically taken to the new loca\
    tion, please enable javascript or click the hyperlink <a href=\"/login.php3?wind\
    ow_id=&amp;requested_url=https%3A%2F%2F174.142.92.126%3A8443%2F\" target=\"top\"\
    >/login.php3?window_id=&amp;requested_url=https%3A%2F%2F174.142.92.126%3A8443%2F\
    </a>.</noscript></html><!--_____________________________________________________\
    ________________________________________________________________________________\
    ________________________________________________________________________________\
    _________________________IE error page size limitation__________________________\
    ________________________________________________________________________________\
    ________________________________________________________________________________\
    ____________________________________________________-->",
        "location": {
            "city": null,
            "region_code": "QC",
            "area_code": null,
            "longitude": -73.5833,
            "country_code3": "CAN",
            "latitude": 45.5,
            "postal_code": "H3G",
            "dma_code": null,
            "country_code": "CA",
            "country_name": "Canada"
        },
        "timestamp": "2016-02-25T11:56:52.548187",
        "domains": [],
        "org": "iWeb Technologies",
         "os": null,
        "_shodan": {
            "options": {},
            "module": "https",
            "crawler": "122dd688b363c3b45b0e7582622da1e725444808"
        },
        "opts": {
            "heartbleed": "2016/02/25 03:56:45 ([]uint8) {\n 00000000  02 00 74 63 6\
    5 6e 73 75  73 2e 73 68 6f 64 61 6e  |..tcensus.shodan|\n 00000010  2e 69 6f 53 \
    45 43 55 52  49 54 59 20 53 55 52 56  |.ioSECURITY SURV|\n 00000020  45 59 fe 7a\
     a2 0d fa ed  93 42 ed 18 b0 15 7d 6e  |EY.z.....B....}n|\n 00000030  29 08 f6 f\
    8 ce 00 b1 94  b5 4b 47 ac dd 18 aa b9  |)........KG.....|\n 00000040  db 1c 01 \
    45 95 10 e0 a2  43 fe 8e ac 88 2f e8 75  |...E....C..../.u|\n 00000050  8b 19 5f\
     8c e0 8a 80 61  56 3c 68 0f e1 1f 73 9e  |.._....aV<h...s.|\n 00000060  61 4f d\
    a db 90 ce 84 e3  79 5f 9d 6c a0 90 ff fa  |aO......y_.l....|\n 00000070  d8 16 \
    e8 76 07 b2 e5 5e  8e 3e a4 45 61 2f 6a 2d  |...v...^.>.Ea/j-|\n 00000080  5d 11\
    74 94 03 3c 5d                              |].t..<]|\n}\n\n2016/02/25 03:56:45\
     174.142.92.126:8443 - VULNERABLE\n",
            "vulns": ["CVE-2014-0160"]
        },
        "ip_str": "174.142.92.126"
    }

    常用命令

    shodan search --limit 10 --fields ip_str,port country:kr device:router
    city:shanghai
    port:8080
    os:windows xp
    device:webcan,router,switch
    has_vuln:true
    
    shodan host ip
    
    Windows RDP CVE-2019-0708(bluekeep):
    shodan search '"x03ixO0lx00lx0blx06lxdO\xOOlx00\x124\x00"'
    shodan download name.file --limit number content 
    shodan parse --fields ip_str file.location > 0708.txt
    msf:
    set rhosts file:file.location
    file formats: 
    ip1
    ip2
    
    shodan search --limit 30 --fields ip_str "authentication disabled" port:5900 #搜索vnc空密码的服务器
    shodan download --limit 30 xxx 
    shodan count xxx
    shodan parse --fileds ip_str "file.location" > vnc.txt #解析下载的文件并把指定字段写入vnc.txt
    shodan honeypot 8.8.8.8 #查看服务器是否是蜜罐
    shodan host 8.8.8.8 --history #查看服务器的历史信息:以前的漏洞,部署过的服务等
    shodan myip #查看联公网的出口ip
    shodan --limit 10 --fields ip_str,port http.title:hacked by
    shodan --limit 10 --fields ip_str,port "cisco -authorized port:23"
    shodan --limit 10 --fields ip_str,port net:208.88.84.0/24 #搜索网段
    has_screenhot:true encrypted attention #搜索有快照的服务器,并且根据快照OCR文字进行过滤,比如回收站
    has_screenhot:true screenshot.label:ics #搜索工控设备
    shodan stats --facets ssl.version country:cn has_ssl:true #统计分析

    VS Google搜索

    https://www.exploit-db.com/google-hacking-database

    有很多google高级语法可以借鉴