• H
  • i
  • ,
  • L
  • R
  • a
  • b
  • b
  • i
  • t
热爱技术,追求自由!
发布时间: 2021-04-19 11:18:20

参数_sig分析

  • 使用GDA打开分析app源码,豆瓣没有进行加密,直接全局_sig关键字,分析一下就能定位到加密函数的位置com.douban.frodo.network.ApiSignatureHelper,下看源码发现使用sha1算法进行hash,还有一个固定的字符串作为密钥,直接使用frida进行hook就能获取密钥。属于比较简单的那种啦

sha1 算法python实现

def make_digest(message, key):
    key = bytes(key, 'UTF-8')
    message = bytes(message, 'UTF-8')
    digester = hmac.new(key, message, hashlib.sha1)
    signature1 = digester.digest()
    signature2 = base64.b64encode(signature1)
    return str(signature2, 'UTF-8')

算法可以直接在网上就能找到,需要注意的是豆瓣的在获得这些原始的值在拼接url的时候会进行一些特殊字符的转换,比如空格会被替换成+,=替换成%3D,不注意这些也会请求失败。剩下就是分析每个连接的请求格式,参数是什么,直接hook加密的函数,然后把参数打印出来,把需要的请求连接的参数打印复制,然后按照格式生成sig加密参数即可 一下是完整项目的源码

import requests
import time
import hmac
import base64
import hashlib
import redis
import json
import traceback
import random

requests.packages.urllib3.disable_warnings()
redis_key = "list:doupan"
redis_user_key = "list:doupan:user"
r = redis.StrictRedis(host='127.0.0.1', port=6379, decode_responses=True)

auth = None
auth_list = ["4ae2de71dda2cc54******", "96ec93cfa99b9521********"]

headers = {
    "Authorization": f"Bearer {auth}",
    "User-Agent": "api-client/1 com.douban.frodo/7.3.0(207) Android/29 product/sirius vendor/Xiaomi model/MI 8 SE  rom/miui6  network/wifi  udid/228e53eeaa5de163d9d0c39c712a301f77352075  platform/mobile",
    "Host": 'frodo.douban.com',
    "Connection": "Keep-Alive",
    "Accept-Encoding": "gzip"

}


def get_proxy():
    return {'http': '127.0.0.1:1080', 'https:': '127.0.0.1:1080'}


def make_digest(message, key):
    key = bytes(key, 'UTF-8')
    message = bytes(message, 'UTF-8')
    digester = hmac.new(key, message, hashlib.sha1)
    signature1 = digester.digest()
    signature2 = base64.b64encode(signature1)
    return str(signature2, 'UTF-8')


def get_sig(data):
    return make_digest(data, 'bf7dddc7c9cfe6f7')


def genarate():
    r.delete(redis_key)
    for i in range(0, 6900, 30):
        print(i)
        r.lpush(redis_key, i)
    print('生产完毕')


def genatrate_user():
    r.delete(redis_user_key)
    with open('members.txt', 'r') as f:
        for line in f.readlines():
            g_json = json.loads(line)
            for mem in g_json['members']:
                print(mem)
                r.lpush(redis_user_key, json.dumps(mem))
    print('生产完毕')


def get_url_by_query_data(query_data, base_url, t):
    sig = get_sig(query_data).replace('=', '%3D')
    url = base_url + '&' + f'_sig={sig}' + '&' + f'_ts={t}'
    url = url.replace('+', '%2B')
    return url


def t_10():
    return int(time.time())


def get_json_by_url(url):
    res = requests.get(url=url, headers=headers, proxies=get_proxy(), verify=False)
    return res.json()


def get_user_subject_json_by_uid(uid):
    silence_url = f"https://frodo.douban.com/api/v2/user/{uid}/subjectfeed/timeslices?apikey=0dad551ec0f84ed02907ff5c42e8ec70&channel=Xiaomi_Market&udid=228e53eeaa5de163d9d0c39c712a301f77352075&os_rom=miui6&oaid=18052974ff1b7b8e&timezone=Asia%2FShanghai"
    t = t_10()
    query_data = f"GET&%2Fapi%2Fv2%2Fuser%2F{uid}%2Fsubjectfeed%2Ftimeslices&{auth}&{t}"
    url = get_url_by_query_data(query_data, silence_url, t)
    res = requests.get(url=url, headers=headers, proxies=get_proxy(), verify=False)
    recent = res.json()['timeslices'][0]['slice']
    t = t_10()
    book_url = f"https://frodo.douban.com/api/v2/user/{uid}/subjectfeed/items?slice={recent}&apikey=0dad551ec0f84ed02907ff5c42e8ec70&channel=Xiaomi_Market&udid=228e53eeaa5de163d9d0c39c712a301f77352075&os_rom=miui6&oaid=18052974ff1b7b8e&timezone=Asia%2FShanghai"
    query_data = f"GET&%2Fapi%2Fv2%2Fuser%2F{uid}%2Fsubjectfeed%2Fitems&{auth}&{t}"
    url = get_url_by_query_data(query_data, book_url, t)
    return get_json_by_url(url)


def get_user_profile_json_by_uid(uid):
    user_url = f"https://frodo.douban.com/api/v2/user/{uid}?apikey=0dad551ec0f84ed02907ff5c42e8ec70&channel=Xiaomi_Market&udid=228e53eeaa5de163d9d0c39c712a301f77352075&os_rom=miui6&oaid=18052974ff1b7b8e&timezone=Asia%2FShanghai"
    t = t_10()
    query_data = f"GET&%2Fapi%2Fv2%2Fuser%2F{uid}&{auth}&{t}"
    url = get_url_by_query_data(query_data, user_url, t)
    return get_json_by_url(url)


def get_user_group_json_by_uid(uid):
    group_url = f"https://frodo.douban.com/api/v2/group/user/{uid}/profile_group_info?source=members_list&count=20&source_group_id=721085&apikey=0dad551ec0f84ed02907ff5c42e8ec70&channel=Xiaomi_Market&udid=228e53eeaa5de163d9d0c39c712a301f77352075&os_rom=miui6&oaid=18052974ff1b7b8e&timezone=Asia%2FShanghai"
    t = t_10()
    query_data = f"GET&%2Fapi%2Fv2%2Fgroup%2Fuser%2F{uid}%2Fprofile_group_info&{auth}&{t}"
    url = get_url_by_query_data(query_data, group_url, t)
    return get_json_by_url(url)


def get_stat_json_by_uid(uid):
    movie_url = f"https://frodo.douban.com/api/v2/user/{uid}/collection_stats?type=movie&udid=228e53eeaa5de163d9d0c39c712a301f77352075&rom=miui6&apikey=0dad551ec0f84ed02907ff5c42e8ec70&s=rexxar_new&channel=Xiaomi_Market&timezone=Asia%2FShanghai&device_id=228e53eeaa5de163d9d0c39c712a301f77352075&os_rom=miui6&oaid=18052974ff1b7b8e&apple=fcd16d795c0d89eb4fd85bbe36790e3a&mooncake=0f607264fc6318a92b9e13c65db7cd3c&sugar=46002&loc_id=118111"
    book_url = f"https://frodo.douban.com/api/v2/user/{uid}/collection_stats?type=book&udid=228e53eeaa5de163d9d0c39c712a301f77352075&rom=miui6&apikey=0dad551ec0f84ed02907ff5c42e8ec70&s=rexxar_new&channel=Xiaomi_Market&timezone=Asia%2FShanghai&device_id=228e53eeaa5de163d9d0c39c712a301f77352075&os_rom=miui6&oaid=18052974ff1b7b8e&apple=fcd16d795c0d89eb4fd85bbe36790e3a&mooncake=0f607264fc6318a92b9e13c65db7cd3c&sugar=46002&loc_id=118111"
    music_url = f"https://frodo.douban.com/api/v2/user/{uid}/collection_stats?type=music&udid=228e53eeaa5de163d9d0c39c712a301f77352075&rom=miui6&apikey=0dad551ec0f84ed02907ff5c42e8ec70&s=rexxar_new&channel=Xiaomi_Market&timezone=Asia%2FShanghai&device_id=228e53eeaa5de163d9d0c39c712a301f77352075&os_rom=miui6&oaid=18052974ff1b7b8e&apple=fcd16d795c0d89eb4fd85bbe36790e3a&mooncake=0f607264fc6318a92b9e13c65db7cd3c&sugar=46002&loc_id=118111"
    t = t_10()
    query_data = f"GET&%2Fapi%2Fv2%2Fuser%2F{uid}%2Fcollection_stats&{auth}&{t}"
    url = get_url_by_query_data(query_data, movie_url, t)
    movie_json = get_json_by_url(url)
    t = t_10()
    query_data = f"GET&%2Fapi%2Fv2%2Fuser%2F{uid}%2Fcollection_stats&{auth}&{t}"
    url = get_url_by_query_data(query_data, book_url, t)
    book_json = get_json_by_url(url)
    t = t_10()
    query_data = f"GET&%2Fapi%2Fv2%2Fuser%2F{uid}%2Fcollection_stats&{auth}&{t}"
    url = get_url_by_query_data(query_data, music_url, t)
    music_json = get_json_by_url(url)
    res = {}
    genres_key = 'genres'
    if genres_key in movie_json.keys():
        res['movie'] = movie_json['genres']
    else:
        res['movie'] = {}
    if genres_key in book_json.keys():
        res['book'] = book_json['genres']
    else:
        res['book'] = []
    if genres_key in music_json.keys():
        res['music'] = music_json['genres']
    else:
        res['music'] = {}
    return res


def print_format_json(j):
    print(json.dumps(j, sort_keys=True, indent=2, ensure_ascii=False))


def members_worker():
    while True:
        i = r.rpop(redis_key)
        try:
            base_url = f"https://frodo.douban.com/api/v2/group/721085/members?start={i}&count=30&period=default&apikey=0dad551ec0f84ed02907ff5c42e8ec70&channel=Xiaomi_Market&udid=228e53eeaa5de163d9d0c39c712a301f77352075&os_rom=miui6&oaid=18052974ff1b7b8e&timezone=Asia%2FShanghai"
            t = t_10()
            query_data = f"GET&%2Fapi%2Fv2%2Fgroup%2F721085%2Fmembers&96ec93cfa99b952bc4c5bb6efcd2bf81&{t}"
            sig = get_sig(query_data).replace(' ', '+')
            url = base_url + '&' + f'_sig={sig}' + '&' + f'_ts={t}'
            url = url.replace('+', '%2B')
            res = requests.get(url=url, headers=headers, proxies=get_proxy(), verify=False)
            res_json = res.json()
            if 'code' in res_json.keys():
                raise Exception('bad code')
            with open('members.txt', 'a') as f:
                f.write(json.dumps(res_json))
                f.write('\n')
            time.sleep(1)
        except Exception as e:
            print(str(e))
            r.lpush(redis_key, i)


def user_workers():
    while True:
        try:
            global auth, headers
            r_int = random.randint(0, len(auth_list) - 1)
            auth = auth_list[r_int]
            headers = {
                "Authorization": f"Bearer {auth}",
                "User-Agent": "api-client/1 com.douban.frodo/7.3.0(207) Android/29 product/sirius vendor/Xiaomi model/MI 8 SE  rom/miui6  network/wifi  udid/228e53eeaa5de163d9d0c39c712a301f77352075  platform/mobile",
                "Host": 'frodo.douban.com',
                "Connection": "Keep-Alive",
                "Accept-Encoding": "gzip"

            }
            user = r.rpop(redis_user_key)
            print(user)
            if not user:
                print('任务列表为空')
            user = eval(user)
            uid = user['id']
            name = user['name']
            reg_time = user['reg_time']
            gender = user['gender']
            user_profile = get_user_profile_json_by_uid(uid)
            birthday = user_profile['birthday'] or ' '
            hometown = user_profile['hometown'] or ' '
            join_group_count = user_profile['joined_group_count']
            status_count = user_profile['statuses_count']
            movie_count = user_profile['movie_collected_count']
            book_collected_count = user_profile['book_collected_count']
            music_count = user_profile['music_collected_count']
            followers_count = user_profile['followers_count']
            following_count = user_profile['following_count']
            groups = get_user_group_json_by_uid(uid)
            stat_json = get_stat_json_by_uid(uid)
            movie_tag = ''
            count = 0
            for m in stat_json['movie']:
                count += 1
                movie_tag += m['name'] + ':' + str(m['value'])
                movie_tag += ';'
                if count >= 5:
                    break
            book_tag = ''
            count = 0
            for b in stat_json['book']:
                count += 1
                book_tag += b['name'] + ':' + str(b['value'])
                book_tag += ';'
                if count >= 5:
                    break
            music_tag = ''
            count = 0
            for mu in stat_json['music']:
                count += 1
                music_tag += mu['name'] + ':' + str(mu['value'])
                music_tag += ';'
                if count >= 5:
                    break
            group_str = ''
            count = 0

            for g in groups['groups'][1:]:
                count += 1
                group_str += g['name'] + ';'
                if count >= 10:
                    break
            res = [uid, name, gender, birthday, hometown, reg_time, join_group_count, group_str, status_count,
                   movie_count,
                   book_collected_count, music_count]
            res.extend([movie_tag, book_tag, music_tag, following_count, followers_count])
            line = ','.join(map(str, res))
            with open('doupan_user.csv', 'a', encoding='utf8') as f:
                f.write(line)
                f.write('\n')
            print(f'[* OK ]{uid}')
            # 限制速率,不然会请求失败
        except Exception as e:
            # 重新放入
            print(f'[* bad {uid}]')
            r.lpush(redis_user_key, user)
            traceback.print_exc()


def run():
    from threading import Thread
    # 限制速度
    max_concurrence = 1
    # target = members_worker
    target = user_workers
    task_list = []
    for i in range(max_concurrence):
        t = Thread(target=target, args=())
        t.start()
        task_list.append(t)
    for t in task_list:
        t.join()


def write(line):
    with open('doupan_user.csv', 'a', encoding='utf8') as f:
        f.write(line)
        f.write('\n')


def read():
    count = 0
    with open('doupan_user.csv', 'rb') as f:
        for line in f.readlines():
            try:
                utf_8_str = line.decode('utf8')
                with open('doupan_utf8.csv', 'a', encoding='utf-8-sig', newline='') as f2:

                    f2.write(utf_8_str)
            except Exception as e:
                count += 1

    print(count)


def test():
    global auth, headers
    r = random.randint(0, len(auth_list) - 1)
    auth = auth_list[r]
    headers = {
        "Authorization": f"Bearer {auth}",
        "User-Agent": "api-client/1 com.douban.frodo/7.3.0(207) Android/29 product/sirius vendor/Xiaomi model/MI 8 SE  rom/miui6  network/wifi  udid/228e53eeaa5de163d9d0c39c712a301f77352075  platform/mobile",
        "Host": 'frodo.douban.com',
        "Connection": "Keep-Alive",
        "Accept-Encoding": "gzip"

    }
    uid = 171928829
    print(auth)
    print_format_json(get_user_group_json_by_uid(uid))
    print_format_json(get_user_subject_json_by_uid(uid))
    print_format_json(get_stat_json_by_uid(uid))
    # write()


if __name__ == '__main__':
    # genatrate_user()
    # run()
    # read()
    # test()
    run()

发布时间: 2021-02-21 16:44:36

知乎查词的作用

  • 能在一分钟内抓取某个关键词下的上万个相关回答,然后统计分析词频

实现原理

  • 没有采用抓取网页端的方式,因为网页端的有反爬措施,而且频率快了容易封号,封IP,所有知识查词从安卓端入手,用frida过滤SSL验证,知乎安卓端使用的是OKHTTP3.0还是很容易过滤的, 解决抓包问题之后就很简单,知乎并没有参数加密,直接获取api请求即可.
  • 使用tornado进行异步请求处理,MongoDB进行存储数据
  • 代码主要使用redis做url队列,然后用类似url分发处理函数进行url返回内容处理.

核心源代码

from utils import header2json, save_response
from tornado import queues
import time
from tornado import gen
import aiohttp
import re
import parsel

base_url = "https://api.zhihu.com/search_v3?correction=1&t=general&q={}"
header_str = """
authority: api.zhihu.com
scheme: https
x-api-version: 3.0.65
user-agent: com.zhihu.android/Futureve/5.6.2 Mozilla/5.0 (Linux; Android 10; MI 8 SE Build/QKQ1.190828.002; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/83.0.4103.101 Mobile Safari/537.36
x-app-version: 5.6.2
x-app-za: OS=Android&Release=10&Model=MI+8+SE&VersionName=5.6.2&VersionCode=575&Width=1080&Height=2115&Installer=xiaomi-preinstall&WebView=83.0.4103.101DeviceType=AndroidPhoneBrand=Xiaomi
x-app-flavor: xiaomi-preinstall
x-app-build: release
x-network-type: WiFi
x-suger: QU5EUk9JRF9JRD00MmZhNmJjZjQwNTJkYTVkO01BQz0wMjowMDowMDowMDowMDowMA==
x-udid: AECfdPcTkhJLBULRdqBjxqlATYNw3yOAeLw=
authorization: Bearer **********
cookie: q_c1=5faaf4e0868f4d9298bdb87eee51f210|1612358076000|1612358076000; z_c0=2|1:0|10:1612358075|4:z_c0|92:Z3QyLjBBQUFBQUNvRzVUNFNraFAzZEo5QUFBQUFBQXhOVlFKZ0FnQkdlNUptWGQxV19RRXNvc285d1FTcjdxR01FZz09|36e4e1997aab7ededeb8a7ff270341919e95137728d7328b893a8b0ab37a205d; KLBRSID=b33d76655747159914ef8c32323d16fd|1612358604|1612358075; _xsrf=bbglFNjIyPkZB9NKl4HO3D470Axl863m
accept-encoding: gzip
"""




def print_exception_info(url, e, rsp=None):
    print('解析数据出错{}:{}:{}'.format(url, e, str(rsp)[:70]))
    dead.add(url)


async def get_json_from_url(url, method='get', data=None):
    async with  aiohttp.ClientSession() as session:
        if method == 'get':
            try:
                response = await session.get(url=url, headers=headers)
            except Exception as e:
                print('请求出错{}:{}'.format(url, str(e)))
                return None
        elif method == 'post':
            try:
                response = await session.post(url=url, headers=headers, data=data)
            except Exception as e:
                print('请求出错{}:{}'.format(url, str(e)))
                return None
        return await response.json()


async def get_html_from_url(url, method='get', data=None):
    async with aiohttp.ClientSession() as session:
        if method == 'get':
            try:
                response = await session.get(url=url, headers=headers)
            except Exception as e:
                print('请求出错{}:{}'.format(url, str(e)))
                return None
        elif method == 'post':
            try:
                response = await session.post(url=url, headers=headers, data=data)
            except Exception as e:
                print('请求出错{}:{}'.format(url, str(e)))
                return None
        return await response.text(encoding='utf8')


async def get_search_url(search_url):
    rsp_json = await get_json_from_url(search_url)
    if not rsp_json:
        return
    try:
        if rsp_json['paging']['is_end'] == 'false' or rsp_json['paging']['is_end'] is False:
            next_search_url = rsp_json['paging'].get('next')
            await q.put(next_search_url)
    except Exception as e:
        print_exception_info(search_url, e, rsp_json)
        return

    try:
        for question in rsp_json['data'][1:]:
            try:
                await q.put(question['object']['question']['url'])
            except Exception as e:
                print('1')
                pass
    except Exception as e:
        print_exception_info(search_url, e, rsp_json)


async def get_answer_batch_url(question_url):
    rsp_json = await get_json_from_url(question_url)
    if not rsp_json:
        return
    try:
        anwser_count = int(rsp_json['answer_count'])
        if str(question_url).endswith('/'):
            anwer_base_url = question_url + "answers?order_by=&offset={}"
        else:
            anwer_base_url = question_url + "/answers?order_by=&offset={}"
        for i in range(1, int(anwser_count / 5)):
            await q.put(anwer_base_url.format(i * 5))
    except Exception as e:
        print_exception_info(question_url, e, rsp_json)


async def get_answer_url(answer_batch_url):
    rsp_json = await get_json_from_url(answer_batch_url)
    if not rsp_json:
        return
    try:
        for anwser in rsp_json['data']:
            answer_base_urt = "https://www.zhihu.com/appview/answer/{}?appview=1&config=%7B%22content_padding_top%22%3A144%2C%22content_padding_bottom%22%3A56%2C%22content_padding_left%22%3A16%2C%22content_padding_right%22%3A16%2C%22title_font_size%22%3A22%2C%22body_font_size%22%3A16%2C%22is_dark_theme%22%3Afalse%2C%22can_auto_load_image%22%3Atrue%2C%22app_info%22%3A%22OS%3DAndroid%26Release%3D10%26Model%3DMI%2B8%2BSE%26VersionName%3D5.6.2%26VersionCode%3D575%26Width%3D1080%26Height%3D2115%26Installer%3Dxiaomi-preinstall%26WebView%3D83.0.4103.101DeviceType%3DAndroidPhoneBrand%3DXiaomi%22%2C%22X-SUGER%22%3A%22QU5EUk9JRF9JRD00MmZhNmJjZjQwNTJkYTVkO01BQz0wMjowMDowMDowMDowMDowMA%3D%3D%22%7D&type=0"
            await q.put(answer_base_urt.format(anwser['id']))
    except Exception as e:
        print_exception_info(answer_batch_url, e, rsp_json)


async def get_answer_data(answer_url):
    rsp_text = await get_html_from_url(answer_url)
    if not rsp_text:
        return
    html = parsel.Selector(rsp_text)
    text_list = html.css('".RichText p::text"').extract()
    text = '\n'.join(text_list)
    save_response(answer_url,text)


async def deal_url(url):
    if url in feting:
        return
    feting.add(url)
    print('正在处理{}'.format(url))
    if "search_v3" in url:
        print('搜索url地址')
        await get_search_url(url)
    elif re.match(r"^https://api.zhihu.com/questions/\d+[0-9/]$", url):
        print('问题url地址')
        await get_answer_batch_url(url)
    elif re.match(r"^https://api.zhihu.com/questions/\d+/answers\?order_by=&offset=\d+[0-9/]$",
                  url):
        print('batch回答url')
        await get_answer_url(url)
    elif "appview" in url:
        print('回答url地址')
        await get_answer_data(url)
    print('处理完成{}'.format(url))
    feted.add(url)


async def main():
    async def worker():
        async for url in q:
            if url is None:
                return
            try:
                await deal_url(url)
            except Exception as e:
                print('Exception :{}', str(e))
                dead.append(url)

    for i in search_word:
        await q.put(base_url.format(i))
    workers = gen.multi([worker() for _ in range(max_workers_num)])
    from datetime import timedelta
    await q.join(timeout=timedelta(seconds=300))
    print('共获取urls数量为: {}'.format(len(feted)))
    print('失败数量为:{}'.format(len(dead)))
    print('抓取用时{}'.format(time.time() - start))
    for _ in range(max_workers_num):
        await q.put(None)
    await workers


async def test():
    rsp = await  get_html_from_url(
        url="https://www.zhihu.com/appview/answer/927792157?appview=1&config=%7B%22content_padding_top%22%3A144%2C%22content_padding_bottom%22%3A56%2C%22content_padding_left%22%3A16%2C%22content_padding_right%22%3A16%2C%22title_font_size%22%3A22%2C%22body_font_size%22%3A16%2C%22is_dark_theme%22%3Afalse%2C%22can_auto_load_image%22%3Atrue%2C%22app_info%22%3A%22OS%3DAndroid%26Release%3D10%26Model%3DMI%2B8%2BSE%26VersionName%3D5.6.2%26VersionCode%3D575%26Width%3D1080%26Height%3D2115%26Installer%3Dxiaomi-preinstall%26WebView%3D83.0.4103.101DeviceType%3DAndroidPhoneBrand%3DXiaomi%22%2C%22X-SUGER%22%3A%22QU5EUk9JRF9JRD00MmZhNmJjZjQwNTJkYTVkO01BQz0wMjowMDowMDowMDowMDowMA%3D%3D%22%7D&type=0")
    html = parsel.Selector(rsp)
    text = html.css(".RichText p::text").extract()

    print(text)



if __name__ == '__main__':
    import tornado.ioloop
    q = queues.Queue()
    start = time.time()
    search_word = ['零食']
    max_workers_num = 50
    feting, feted, dead = set(), set(), set()
    headers = header2json(header_str)
    ioloop = tornado.ioloop.IOLoop().current()
    ioloop.run_sync(main)
    # ioloop.run_sync(test)


发布时间: 2021-02-20 21:50:06

  • 最近为了练习安卓逆向,在攻防世界,看雪题库做了一些题,其中大部分都涉及到so文件加密函数的还原,如果是像AES,MD5这种算法还好,要是那种自定义的加密算法,你要获取flag必须得逆向写一个还原算法,那个是相当得痛苦啊 *直到我发现一个angr,新的世界

原理

  • angr 的原理通俗上里理解就是找到输入,加密处理,输出,通过分析汇编代码找到输入的位置,然后找到输出的结果,然后通过暴力破解的方式找到正确的输入. 省去了逆向还原算法的时间 一下是官方文档的一个例子
import os
import angr
from angr.procedures.java import JavaSimProcedure
from angr.engines.soot.values import SimSootValue_ThisRef
from archinfo.arch_soot import SootArgument, SootMethodDescriptor


file_dir = os.path.dirname(os.path.realpath(__file__))


result = None

class Dummy_String_valueOf(JavaSimProcedure):
    __provides__ = (
        ("java.lang.String", "valueOf(int)"),
    )

    def run(self, intv): # pylint: disable=W0221
        global result
        result = intv
        return ""


def test_androidnative1():
    sdk_path = os.path.join(os.path.expanduser("~"), "Android/Sdk/platforms/")
    if not os.path.exists(sdk_path):
        print("cannot run test_apk_loading since there is no Android SDK folder")
        return

    apk_location = os.path.join(file_dir, "androidnative1.apk")
    loading_opts = {'android_sdk': sdk_path,
                    'entry_point': 'com.angr.nativetest1.MainActivity.onCreate',
                    'entry_point_params': ('android.os.Bundle', ),
                    'supported_jni_archs': ['x86']}
    project = angr.Project(apk_location, main_opts=loading_opts)
    project.hook(SootMethodDescriptor(class_name="java.lang.String", name="valueOf", params=('int',)).address(), Dummy_String_valueOf())

    blank_state = project.factory.blank_state()
    a1 = SimSootValue_ThisRef.new_object(blank_state, 'com.angr.androidnative1.MainActivity')
    a2 = SimSootValue_ThisRef.new_object(blank_state, 'android.os.Bundle', symbolic = True)
    args = [SootArgument(arg, arg.type) for arg in [a1, a2]]
    entry = project.factory.entry_state(args = args)
    simgr = project.factory.simgr(entry)

    simgr.run()

    int_result = simgr.deadended[0].solver.eval(result)
    assert int_result == 221


def test():
    test_androidnative1()


if __name__ == "__main__":
    import logging
    logging.getLogger("angr.engines.soot.engine").setLevel("DEBUG")
    test()
  • 基本很多CTF的题都可以采用angr进行暴力破解获取flag

发布时间: 2021-01-09 20:12:54

安装相关工具

frida工具安装

  • pip install frida
  • pip install frida-tools

安装frida-server

https://github.com/frida/frida/releases找到自己对应server下载之后,放入到模拟器或者有root权限的手机当中,然后执行./frida-server

使用流程

如果是使用模拟器的话比如雷电模拟器首先需要adb connect 127.0.0.1:62001 连接到模拟器。

启动frida-server

  • adb shell
  • cd /data/local/
  • ./frida-server

测试是否连接成功

  • frida-ps -U|grep com

编写hook代码

常用模板代码

import frida, sys




"""
var string_class = Java.use("java.lang.String")
var my_string = string_class.$new("My TeSt String#####"); 
"""

choose_code ="""
Java.choose("com.yaotong.crackme" , {
  onMatch : function(instance){ //This function will be called for every instance found by frida
    console.log("Found instance: "+instance);
    console.log("Result of secret func: " + instance.securityCheck());
  },
  onComplete:function(){}

});

"""

js_code = """
Java.perform(function () {


    var mac = Java.use('com.yaotong.crackme.MainActivity')
    if(mac == null){
        console.log('未找到',mac)

    }

    mac.securityCheck.overload("java.lang.String").implementation = function(x){
        send('crack successful')
        console.log(x)
        return true;
    }

});
"""

process = frida.get_remote_device().attach('com.yaotong.crackme')
script = process.create_script(js_code)
def on_message(message, data):
    if message['type'] == 'send':
        print("[*] {0}".format(message['payload']))
    else:
        print(message)
script.on('message', on_message)
print('开始hook')
script.load()
sys.stdin.read()

端口转发

adb forward tcp:27042 tcp:27042 目的是python程序的hook代码与frida-server进行通信

adb 语句相关

adb shell dumpsys activity |grep findstr "Fouces"

发布时间: 2021-01-07 10:21:39

工具准备

  • Kali的ISO镜像文件一份,U盘一个,rufus刻录工具
  • 镜像下载慢的话最好采用torrent和迅雷方式下载

刻录系统

  • 打开rufus选择U盘,选择下载好的镜像文件,分区选择GTP,文件系统选择NTFS,刻录方式选择DD模式.开始刻录.

装机准备

  • win+x然后选择磁盘管理,你需要空出一块16g以上的空间,可以选择压缩卷,和删除卷两种方式,处理完之后记住你空出的磁盘位置.

开始装机

  • 这个时候应该应该刻录完了,关机重启安装自己的电脑型号进入BOIS系统选择从USB进入,然后就进入了kali的装机环境,安装提示进行,最后磁盘安装位置选择之前空出来的磁盘,模式选择从一整块磁盘位置安装,这样你就不需要手动设置挂载点.
  • 然后休息几分钟
  • BIGO,装好啦

使用准备

  • Kali系统使用的跟ubantu是一样的apt源,所有首先更新换上国内的源,然后安装google,输入法等就ok啦.

发布时间: 2020-12-10 20:44:01

  • 原来的使用flask编写的博客就暂时不用了,博客内容都迁移到新的系统了.
  • 记录一下这次新的博客系统的架构吧

设计理念

  • 极简,极简,极简,还是TM的极简
  • 尽量避免任何的第三方框架的引入,所需功能全部尽可能的用最短的代码实现.

后端

  • tornado+aiomysql+markdown
  • 在tornado的基础上,所有的请求处理函数都变成了异步,同理数据库查询插入也都必须使用异步.别问我为什么都采用异步的方式,问就是TM的响应速度真的快,虽然博客好像也不需要并发的处理情况[doge]

  • 后端代码我自己构思一套非常好用的组织架构的方法,采用插件类似的组织架构,能够不断添加新的模块,而且也不会产生url多了以后非常难管理的情况,甚至你可以为每个模块都起一样名字的处理类

前端

  • 原生js,css

开源地址

  • https://github.com/litter-rabbit/tornado_blog

发布时间: 2020-10-08 22:22:18

shortcuts模块源码解读

核心代码

def render(request, template_name, context=None, content_type=None, status=None, using=None):
    """
    Return a HttpResponse whose content is filled with the result of calling
    django.template.loader.render_to_string() with the passed arguments.
    """
    content = loader.render_to_string(template_name, context, request, using=using)
    return HttpResponse(content, content_type, status)


def redirect(to, *args, permanent=False, **kwargs):
    """
    Return an HttpResponseRedirect to the appropriate URL for the arguments
    passed.

    The arguments could be:

        * A model: the model's `get_absolute_url()` function will be called.

        * A view name, possibly with arguments: `urls.reverse()` will be used
          to reverse-resolve the name.

        * A URL, which will be used as-is for the redirect location.

    Issues a temporary redirect by default; pass permanent=True to issue a
    permanent redirect.
    """
    redirect_class = HttpResponsePermanentRedirect if permanent else HttpResponseRedirect
    return redirect_class(resolve_url(to, *args, **kwargs))


def _get_queryset(klass):
    """
    Return a QuerySet or a Manager.
    Duck typing in action: any class with a `get()` method (for
    get_object_or_404) or a `filter()` method (for get_list_or_404) might do
    the job.
    """
    # If it is a model class or anything else with ._default_manager
    if hasattr(klass, '_default_manager'):
        return klass._default_manager.all()
    return klass


def get_object_or_404(klass, *args, **kwargs):
    """
    Use get() to return an object, or raise a Http404 exception if the object
    does not exist.

    klass may be a Model, Manager, or QuerySet object. All other passed
    arguments and keyword arguments are used in the get() query.

    Like with QuerySet.get(), MultipleObjectsReturned is raised if more than
    one object is found.
    """
    queryset = _get_queryset(klass)
    if not hasattr(queryset, 'get'):
        klass__name = klass.__name__ if isinstance(klass, type) else klass.__class__.__name__
        raise ValueError(
            "First argument to get_object_or_404() must be a Model, Manager, "
            "or QuerySet, not '%s'." % klass__name
        )
    try:
        return queryset.get(*args, **kwargs)
    except queryset.model.DoesNotExist:
        raise Http404('No %s matches the given query.' % queryset.model._meta.object_name)


def get_list_or_404(klass, *args, **kwargs):
    """
    Use filter() to return a list of objects, or raise a Http404 exception if
    the list is empty.

    klass may be a Model, Manager, or QuerySet object. All other passed
    arguments and keyword arguments are used in the filter() query.
    """
    queryset = _get_queryset(klass)
    if not hasattr(queryset, 'filter'):
        klass__name = klass.__name__ if isinstance(klass, type) else klass.__class__.__name__
        raise ValueError(
            "First argument to get_list_or_404() must be a Model, Manager, or "
            "QuerySet, not '%s'." % klass__name
        )
    obj_list = list(queryset.filter(*args, **kwargs))
    if not obj_list:
        raise Http404('No %s matches the given query.' % queryset.model._meta.object_name)
    return obj_list


def resolve_url(to, *args, **kwargs):
    """
    Return a URL appropriate for the arguments passed.

    The arguments could be:

        * A model: the model's `get_absolute_url()` function will be called.

        * A view name, possibly with arguments: `urls.reverse()` will be used
          to reverse-resolve the name.

        * A URL, which will be returned as-is.
    """
    # If it's a model, use get_absolute_url()
    if hasattr(to, 'get_absolute_url'):
        return to.get_absolute_url()

    if isinstance(to, Promise):
        # Expand the lazy instance, as it can cause issues when it is passed
        # further to some Python functions like urlparse.
        to = str(to)

    if isinstance(to, str):
        # Handle relative URLs
        if to.startswith(('./', '../')):
            return to

    # Next try a reverse URL resolution.
    try:
        return reverse(to, args=args, kwargs=kwargs)
    except NoReverseMatch:
        # If this is a callable, re-raise.
        if callable(to):
            raise
        # If this doesn't "feel" like a URL, re-raise.
        if '/' not in to and '.' not in to:
            raise

    # Finally, fall back and assume it's a URL
    return to
  • shortcuts这里有几个常用的函数非常值得由这个模块进行切入了解Django源码

render函数

可以看到非常简单,http请求都是返回一个Httpresponse对象,为了把html文件渲染进去,先调用了render_to_string()方法,将html的内容转化成字符串

redirect函数

其实也是返回一个Httpresponse对象,然后调用了resovle_url函数获取对应的url视图的内容.

发布时间: 2020-10-06 22:12:27

为了提高自己的编程水平,决定阅读一个大型的开源框架,选了好久在flask和Django最终还是选择Django,主要Django相对于flask来说还是相对比较容易理解的

阅读准备

  • 克隆项目 git clone https://github.com/django/django

  • 找到最开始的tag,然后checkout它,这个方法是我看Flask实战书籍作者推荐的开源项目阅读方式,因为一开始的代码量比较少,可以先看最初的版本来整体了解框架的架构,熟悉之后再阅读最新的tag

阅读计划

  • 阅读源码框架不能随便读,也要带有目的的读,我采用的方法是先是阅读启动的命令相关的代码,再阅读常用api的实现,最后阅读数据库如何处理部分.

源码初探

  • django manange.py runserver命令源码实现

核心代码


    def execute(self):
        """
        Given the command-line arguments, figure out which subcommand is being
        run, create a parser appropriate to that command, and run it.
        """
        try:
            subcommand = self.argv[1]
        except IndexError:
            subcommand = 'help'  # Display help if no arguments were given.

        # Preprocess options to extract --settings and --pythonpath.
        # These options could affect the commands that are available, so they
        # must be processed early.
        parser = CommandParser(usage='%(prog)s subcommand [options] [args]', add_help=False, allow_abbrev=False)
        parser.add_argument('--settings')
        parser.add_argument('--pythonpath')
        parser.add_argument('args', nargs='*')  # catch-all
        try:
            options, args = parser.parse_known_args(self.argv[2:])
            handle_default_options(options)
        except CommandError:
            pass  # Ignore any option errors at this point.

        try:
            settings.INSTALLED_APPS
        except ImproperlyConfigured as exc:
            self.settings_exception = exc
        except ImportError as exc:
            self.settings_exception = exc

        if settings.configured:
            # Start the auto-reloading dev server even if the code is broken.
            # The hardcoded condition is a code smell but we can't rely on a
            # flag on the command class because we haven't located it yet.
            if subcommand == 'runserver' and '--noreload' not in self.argv:
                try:
                    autoreload.check_errors(django.setup)()
                except Exception:
                    # The exception will be raised later in the child process
                    # started by the autoreloader. Pretend it didn't happen by
                    # loading an empty list of applications.
                    apps.all_models = defaultdict(dict)
                    apps.app_configs = {}
                    apps.apps_ready = apps.models_ready = apps.ready = True

                    # Remove options not compatible with the built-in runserver
                    # (e.g. options for the contrib.staticfiles' runserver).
                    # Changes here require manually testing as described in
                    # #27522.
                    _parser = self.fetch_command('runserver').create_parser('django', 'runserver')
                    _options, _args = _parser.parse_known_args(self.argv[2:])
                    for _arg in _args:
                        self.argv.remove(_arg)

            # In all other cases, django.setup() is required to succeed.
            else:
                django.setup()

        self.autocomplete()

        if subcommand == 'help':
            if '--commands' in args:
                sys.stdout.write(self.main_help_text(commands_only=True) + '\n')
            elif not options.args:
                sys.stdout.write(self.main_help_text() + '\n')
            else:
                self.fetch_command(options.args[0]).print_help(self.prog_name, options.args[0])
        # Special-cases: We want 'django-admin --version' and
        # 'django-admin --help' to work, for backwards compatibility.
        elif subcommand == 'version' or self.argv[1:] == ['--version']:
            sys.stdout.write(django.get_version() + '\n')
        elif self.argv[1:] in (['--help'], ['-h']):
            sys.stdout.write(self.main_help_text() + '\n')
        else:
            self.fetch_command(subcommand).run_from_argv(self.argv)

源码解析

核心fetch_command()函数根据输入的subcomand 获取对应命令模块,run_argv()先进行预处里,进行一些默认设置操作,然后每个不同的命令模块具体实现都实现了handle函数,这里有点Java的味道了,然后run_args调用self.handle(args,*kwargs),获取不同命令的输出,这样就完成了django的命令的架构,可以看到实现方式是非常的巧妙.

发布时间: 2020-03-11 15:45:14

第一步

建立项目文件夹

  • mkdir /usr/local/projects/django_project

第二步

建立python 虚拟环境

  • cd django_project
  • python3 -m venv env

激活虚拟环境

  • source env/bin/activate

上传项目文件

使用git管理(建议)

  • git clone https://github.com/

使用sz 上传工具(不建议)

  • yum install lrzsz
  • rz 上传项目压缩包
  • tar -xvf project.tar.gz

第三步

安装依赖

  • cd django_projects

上传之前记得本地项目先生成依赖文件

  • pip freeze > requirements.txt

使用pip 安装依赖

  • pip install -r requirements.txt

第四步

运行项目

第一种方式,使用django自带运行命令

  • python manage.py runserver 0.0.0.0:8000 然后浏览器输入服务器ip加8000端口进行测试访问进行测试

第二种方式(在第一种测试通过后使用),使用gunicorn +搭配nginx进行部署

配置nginx

新建nginx配置文件

  • vim /etc/gninx/conf.d/dajngo_project.conf

nginx配置模板

server {
listen 80;
server_name www.lrabbit.life;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
gzip on;
gzip_min_length 1k;
gzip_comp_level 7;
gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript ;
gzip_static on;
gzip_vary on;
gzip_buffers 2 4k;
gzip_http_version 1.1;

   location / {
proxy_pass http://127.0.0.1:8000;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /static { #
    alias /usr/local/projects/django_project/static;
    expires 30d;
}
}

重启nginx

  • systemctl restart nginx

启动django项目

生成静态文件目录

  • python manage.py collectstatic

启动gunicornm,切换到项目主目录下

  • gunicorn -w 4 -b 127.0.0.1:8000 wsgi:application (wsgi为djagno中的wsgi.py文件,如果你的项目结构为djagno/mysite/wsgi.py,而你 在django目录下,与mysite同级目录则就为 gunicorn -w 4 -b 127.0.0.1:8000 mysite.wsgi:application )

最后浏览器输入服务器ip即可访问

发布时间: 2020-02-12 20:11:16

相信如果是第一次学习接口的同学可能会产生一个疑惑?什么疑惑呢?

首先接口长成这样

 interface Exampleinterface
{

    void something();
}

interface Exampleinterace1
{
    void something1();
}
  • 我们可以通过implements关键字,实现多个接口继承。 那想必抽象类大家都学过了,表示一种更高层次的类,往往充当占位的角色,功能与接口十分相似,只是声明和继承的关键子不一样。那为什么不直接继承多个抽象类,像这样
abstract class Ex0
{
    public abstract void something();
}


abstract class Ex1
{
    abstract void something();
}

class Test extends Ex0,Ex1//会报错
{
    public void something() {

    }
}

这是因为Java不支持一个类多个继承类。为什么Java不支持多继承呢?

好,既然Java不支持我们先看看那些语言支持吧,这里我们就以Python,和C++来说明,多继承如何工作的。 首先需要了解一下多继承的二义性问题 * 二义性问题:当b1,b2都继承A时,假设A类有个f()方法都被b1,b2重写。此时c在同时继承b1,b2当调用f()方法时,就无法确定调用那个方法。

首先Python多继承也会遇到二义性的问题,这里Python用的及时MRO机制,最早MRO利用的深度优先算法,但是有缺陷,只能对一个类进行重写,后来改成c3算法,主要利用的是广度优先算法,就很好确立了方法调用的顺序。

而C++的多继承则是同过虚继承来解决这个问题。二者在面对多继承的二义性问题时都十分复杂难以理解,笔者看了半天也就明白个大概,就不仔细说了。所以Java语言的设计者在面对 这个问题的时候,坚持着Java是简单易学的特性,放弃了多继承这个特性,转而利用多继承接口来实现多继承的类似功能。

发布时间: 2019-12-03 20:15:48

lambda表示式,相比都大家都经常用过,今天我就来详细说明一下。

首先lambda就是希腊字母”λ“,lambda表示式简单点说就是一个带参数的表达式,那为什么用”λ“呢?这个字母最早是Church开始使用的,不过Church也是受一本书《权威数学》的^表示自由自由变量启发,然后自己改用希腊字母λ来表示,渐渐地,带有参数的表达式就被称为lambda表达式。

好,知道了咋来的我们来看一一下基本语法。

每种语言的lambda表达式都差不多,我就以我自己了解的python和Java来说明一下吧。

首先是Java

基本格式 参数->表达式
例
1.(int x,int y)->x-y;/lambda表达式都显式有return,即相当于返回了一个值(x-y)

2.()->{...};//即使不带有参数,括号也必须有

3.event->{...};

Python

1.lambda x:Math(x)
2.lambda x,y:x+y

两种语言的lambda表达式的在使用场景差别不太大,但是还是有点略微不同。Python的官方文档解释吧lambda当做一个匿名函数,而Java往往是作为函数式接口的替代。

接下来我就详细解释下lambda在Java中的使用场景。

首先介绍一下什么是函数式接口:

public interfacce IntConsumer
{
   void accept(i);
}

这种只含有一个抽象方法的接口(Java中接口默认都是抽象方法)称为函数式接口。Intconsumer是Java函数接口中的一个,此外还有Runable等等。

如以下方法 public static void test(int n,Runable A){ for(int i=0;i<n;i++) A.run();//这里run方法都是Java语言预先规定好的,INtConsumer则是 accept(); }

lambda表达式实际上有三个部分构成:1参数,表达式,自由变量的值。

当lambda引用一个变量值的时候,就相当于自动拷贝一份,且为final,不可改变的。

lambda总结起来作用就是延迟代码的运行,就是在特定条件需要的时候,调用lambda中的代码部分。

发布时间: 2018-07-27 10:40:14

第一题

  • 给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。 如果你最多只允许完成一笔交易(即买入和卖出一支股票),设计一个算法来计算你所能获取的最大利润。

class Solution:
    def maxProfit(self, prices: List[int]) -> int:

        min_price = 299999999
        max_profit = 0

        for i in range(len(prices)):
            if prices[i]<=min_price:
                min_price=prices[i]

            if prices[i] -min_price>=max_profit:
                max_profit = prices[i] - min_price

        return max_profit

思路总结

  • 类似动态规划的感觉,遍历价格的时候同时更新最小价格和最大利润,这样就可以在O(N)的时间内完成.如何采用双重循环的方式O(N*N-(N/2(N-1)))的时间复杂度.

第二题

  • 给定一个数组,它的第 i 个元素是一支给定股票第 i 天的格。设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        profit =0
        for i in range(1,len(prices)):
            if prices[i]>prices[i-1]:
                profit += prices[i]-prices[i-1]
        return profit

思路总结

  • 这题算法不难,理解题目意思就可以了,最大利润那我只需要比前一天的价格高就卖出这样就可以得到最大的利润

第三题

  • 给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 的那 两个 整数,并返回它们的数组下标。

class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:

        m = dict()
        for i in range(len(nums)):
            if target-nums[i] in m.keys():
                return [m[target-nums[i]],i]
            else:
                m[nums[i]]=i

思路总结

  • 遍历的同时把之前遍历的值和对应的索引保存在dict中,然后之后遍历的同时判断一下是否相等就可以了.这题因为是升序排列的数组,还可以使用首尾指针的方式.

第四题

  • 给定一个整数数组,你需要寻找一个连续的子数组,如果对这个子数组进行升序排序,那么整个数组都会变为升序排序。 你找到的子数组应是最短的,请输出它的长度。

class Solution:
    def findUnsortedSubarray(self, nums: List[int]) -> int:
        snum = sorted(nums)
        start = 0
        end = len(nums)
        for i in range(len(nums)):
            if nums[i]!=snum[i]:
                start = i
                break

        for i in range(len(nums)-1,0,-1):
            if nums[i]!=snum[i]:
                end = i
                break
        return end - start + 1 if end > start else 0

思路总结

  • 重新排序得到一个新的数组,然后对比前后找到变化了的位置.

发布时间: 2018-07-24 10:53:02

第一题

给定一个范围在  1 ≤ a[i] ≤ n ( n = 数组大小 ) 的 整型数组,数组中的元素一些出现了两次,另一些只出现一次。 找到所有在 [1, n] 范围之间没有出现在数组中的数字。

class Solution:
    def findDisappearedNumbers(self, nums: List[int]) -> List[int]:
        for i in range(len(nums)):
            nums[abs(nums[i])-1] = -abs(nums[abs(nums[i])-1])
        res = []
        for i in range(len(nums)):
            if nums[i]>0:
                res.append(i+1)
        return res

解题思路

  • 题目要求不适应额外空间,所以这题不能使用dict来标记是否出现,所以只能通过对出现过的数字找到对应的索引,把这个索引位置的数字置为绝对值的负数,然后重新再遍历取出为负数的索引大小,就是没有出现的数字.

第二题

给定一个未经排序的整数数组,找到最长且连续的的递增序列

解`

class Solution:
    def findLengthOfLCIS(self, nums: List[int]) -> int:
        if not nums:
            return 0
        cur = nums[0]
        count=1
        maxcount=0
        for pre in nums[1:]:
            if pre>cur:
                count+=1
            else:
                if count>maxcount:
                    maxcount=count
                count=1
            cur = pre
        return max(count,maxcount)

解题思路

  • 每次遇到不符合的重新计数即可,只是注意最后需要再次比较count和maxcount的大小.

第三题

将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树。 本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。

class Solution:
    def sortedArrayToBST(self, nums: List[int]) -> TreeNode:

        if not nums:
            return None

        mid = len(nums)//2
        num1 = nums[0:mid]
        num2 = nums[mid+1:]
        root = TreeNode(nums[mid])
        root.left = self.sortedArrayToBST(num1)
        root.right = self.sortedArrayToBST(num2)
        return root

解题思路

  • 利用递归的思路为树的根节点和左右字节点赋值即可.

发布时间: 2018-07-22 21:28:35

基础知识

  • 三范式 1.每一列不可拆分,比如地址,江西省南昌市,如果你认为,父母可以拆分为父亲与母亲.2.每一列都依赖与主属性所确定,比如学号可以确定学生姓名,但是不能确定课程号3.不能存在转递属性,比如学号系名称系主任,这样就存在转递属性
  • binlog级别 1.statement:记录sql语句2.row记录每一个变得的数据,mixted,将row和statement结合的方式

引擎

  • Myisam与Innodb区别
  • 储存方式:文件格式,myisam可以被压缩
  • myisam不支持事务和外键
  • Innodb是聚族索引,主键索引存储着行数据,聚簇索引:将数据存储与索引放到了一块,找到索引也就找到了数据

索引

  • 索引就是在mysql中就是用B树或者B+树实现的快速查找数据的目录.数据都存储在树的叶子点上所有可以根据所有来遍历树进行快速查找.
  • 分类
  • 唯一索引:ALTER TABLE table_name ADD UNIQUE (column1,column2);
  • 普通索引:ALTER TABLE table_name ADD INDEX index_name (column);
  • 全文索引:ALTER TABLE table_name ADD FULLTEXT (column);
  • 原理
  • 对索引上的列的数据生成hash值,作为key,原始数据作为value.
  • 最左侧原则:
  • 联合索引是在最左边的索引基础上建立,索引必须包含最左侧的索引.

事务

  • 四个特性
  • 一致性,原子性,持久性,隔离性,
  • 出现的问题
  • 脏读:一个事务中更新某些数据,但是没有提交,另一个事务中读取这个更新的数据.
  • 不可重复读:一个事务中两次相同的查询的数据不一样
  • 幻读:一个事务中相同的查询得到的数据行数不一样.

  • 行级锁,InnoDB
  • 表级锁:InnoDb,MyISAM
  • 悲观锁:假定会发生冲突,查询数据的时候就进行加索
  • 乐观锁:修改数据的时候进行加锁,用在写比较多的场景

sql优化

  • where 语句
  • 避免与null值做判断,in,>=,!=,在=的左边进行函数相关操作

数据库优化

  • 读写分离,缓存,分库分表

发布时间: 2018-07-20 10:48:23

第一题

给定一个二叉树,返回其节点值自底向上的层次遍历。

class Solution:
    def levelOrderBottom(self, root: TreeNode) -> List[List[int]]:

        if not root:
            return []

        stack = [root]
        res = []
        while stack:
            next_stack = []
            temp_val = []

            for node in stack:

                temp_val.append(node.val)
                if node.left:
                    next_stack.append(node.left)
                if node.right:
                    next_stack.append(node.right)

            stack = next_stack
            res.insert(0,temp_val)

        return res

第二题

给定一个二叉树,返回所有从根节点到叶子节点的路径。

class Solution:
    def binaryTreePaths(self, root: TreeNode) -> List[str]:
        if not root:
            return []
        if not root.left and not root.right:
            return [str(root.val)]

        paths = []

        if root.left:
            for i in self.binaryTreePaths(root.left):
                paths.append(str(root.val)+'->'+i)
        if root.right:
            for i in self.binaryTreePaths(root.right):
                paths.append(str(root.val)+'->'+i)

        return paths

第三题

给定一个有相同值的二叉搜索树(BST),找出 BST 中的所有众数(出现频率最高的元素)。

class Solution:
    def findMode(self, root: TreeNode) -> List[int]:
        vals = []
        if not root:
            return []
        def trace(root):
            if not root:
                return
            vals.append(root.val)
            if root.left:
                trace(root.left)
            if root.right:
                trace(root.right)
        trace(root)
        from collections import Counter
        res = []
        c =  Counter(vals)
        maxcount = c.most_common(1)[0][1]
        for i in c.most_common():
            if i[1]==maxcount:
                res.append(i[0])
        return res

发布时间: 2018-05-01 20:22:24

  • 赶在过年前终于完成了博客的更新,看了一堆教程视频,慢慢的自己也终于会写一点了.
  • 这次我的博客采用的是Python+Flask的模式进行编写的,因为第一次写网站这种东西,借鉴了很多网上大佬的代码,不过还是学到了很多东西的

  • 前端部分是我花的时间最久的地方了,应该这么说,后端可能就两天,前端写了快半个月了,虽然一直断断续续的写,不过不懂前端的css和各种东西真的是出各种问题啊,以后得抓紧学一下前端得东西

  • 不管怎么样,我的第一个个人网站还是完成了