链表-反转链表
采用双指针方法,并且设置一个临时值保存cur->next的值
1 | struct ListNode* reverseList(struct ListNode* head){ |
1 | struct ListNode* reverse(struct ListNode* pre, struct ListNode* cur){ |
采用双指针方法,并且设置一个临时值保存cur->next的值
1 | struct ListNode* reverseList(struct ListNode* head){ |
1 | struct ListNode* reverse(struct ListNode* pre, struct ListNode* cur){ |
体验一下c和c++的不同
1 | class MyLinkedList { |
1 | typedef struct { |
1 | class Solution { |
1 | struct ListNode* removeElements(struct ListNode* head, int val){ |
1 | class Solution: |
python可真是简单了,c/c++需要自己释放删掉的元素。
这道题刚刚看到的时候很纳闷,一脸的什么玩意,仔细理清楚思路和规律以及限制就知道了
生成一个 n×n 空矩阵 matrix,随后模拟整个向内环绕的填入过程:定义当前左右上下边界 left,right,above,below,初始值 num = 1,迭代终止值 tar = n * n;当 num <= tar 时,始终按照 从左到右 从上到下 从右到左 从下到上 填入顺序循环,每次填入后:执行 num += 1:得到下一个需要填入的数字;
更新边界:例如从左到右填完后,上边界 t += 1,相当于上边界向内缩 1。
1 | class Solution { |
1.先查壳,看到是C#写的程序,IDA分析也分析不出来个啥,就是个GUI,主要功能还是在那个dll,dll导出函数如下,主要看看checkcode
分析之前用dnspy看看exe主要逻辑,当输入的code和id满足条件的时候就会调用dll里的checkcode
进行逻辑判断,运行调试时,程序窗口不会出现,应该是有反调试。
输入的ID和code进行异或之后等于给定的array即可,异或次数为MAX(len(ID),len(Code)),长度不够的与自身长度进行取模
id固定为9个字节,code应该会比ID长(因为AES需要十六字节)
1 | array = [7,90,115,1,117,99,114,97,24] |
2.用IDA打开dll,点进checkcode
,然后用FindCrypt插件查看到用了AES加密
函数最后将加密之后的输入和一个32字节的数据进行比较并返回结果。
x64dbg打开程序,用API断点到IsDebuggerPresent
,我们需要修改PEB结构中的BeingDebugged改为0,下面是函数的实现
所以我们输入以下命令,然后将值修改为0.(CTRL+E)
如上就可以跟踪到dll中的某个反调试函数。
复制当前指令的首字节地址,用ida打开dll并跳转到复制的地址。按照x64dbg的代码将函数名字修改如下
现在需要点击X,查找一下哪里引用了该函数
发现在sub_18000013F0
中用了该函数,并且采用的是创建线程的方式,将createThread
第五个参数patch为0x00000004(CREATE_SUSPENDED)
因为dll是64位的,所以查了一下调用约定,前四位参数按顺序存放在rcx, rdx, r8, r9,后面的部分存放在堆栈,下面图中,rsp+20h就是第五个参数,rsp+28h就是第六个参数(调试发现rax为0000000000000000,eax是rax后面8位0)
发现修改后代码占据大小变了。所以直接全部nop然后保存
动态调试修改后的dll,就可以绕过反调试部分,其实后面发现不需要绕过该部分反调试,因为iv是全局变量,dll一家在就有数据了,所以可能通过实际加载的Imagebase计算获得key和iv向量的地址,提取数据即可。右键直接在转储中跟随,就可以得到key和iv的值
通过这部分的分析我们得获得正确的code,图中圈起来的就是我们前面所提取的iv和key.
函数在后面会和另一个字符串比较
参考了网上的博客写了如下解决方法,计算得code
1 | # This is a sample Python script. |
然后有个外层逻辑,也就是前面提到的,dnspy已经反编得很好认了,python代码如下
1 | from z3 import * |
得到得ID是ginkgo_CX
所以得到flag{ginkgo_CX@Meaningless_!$!%*@^%#%_Cod}
python代码中肯呢会提示没有模块名叫Crypto,如果要安装的话肯能会出现这样的问题,按照第二张图片的命令安装就好了
1 | pip install pycryptodome -i http://pypi.douban.com/simple/ --trusted-host pypi.douban.com |
1 | class Solution { |
上面又是暴力的方法,复杂度O(n^2),使用双指针试试,设定两个指针left 和right分别为子数组的开始和结束位置,最开始都为0,每一轮迭代都将nums[right]加到sum中,如果sum>=target,那么就更新子数组长度以及将nums[left]移出sum,并将nums[left]右移,
1 | class Solution { |
今天看到一个很好的非常基础详细的贴子关于栈与栈帧,文中对于栈帧做了一个小总结
栈帧就是利用EBP(栈帧指针,注意不是ESP)寄存器访问栈内局部变量、参数、函数返回地址等的手段。ESP寄存器承担着栈顶指针的作用,而EBP寄存器则负责行驶栈帧指针的职能。在程序运行中,ESP寄存器的值随时会变化,访问栈中函数的局部变量、参数时,若以ESP的值为基准编写程序会十分困难,并且也很难使CPU引用到准确的地址。所以,调用某函数时,先要把用作基准点(函数起始地址)的ESP值保存到EBP,并维持在函数内部。这样,无论ESP的值如何变化,以EBP的值为基准能够安全访问到相关函数的局部变量、参数、返回地址。
函数调用经常是嵌套的,在同一时刻,堆栈中会有多个函数的信息。每个未完成运行的函数占用一个独立的连续区域,称作栈帧 (Stack Frame)。栈帧是堆栈的逻辑片段,当调用函数时逻辑栈帧被压入堆栈, 当函数返回时逻辑栈帧被从堆栈中弹出。栈帧存放着函数参数,局部变量及恢复前一栈帧所需要的数据等。
栈帧的边界由栈帧基地址指针 EBP 和堆栈指针 ESP 界定 (指针存放在相应寄存器中)。EBP 指向当前栈帧底部 (高地址),在当前栈帧内位置固定;ESP 指向当前栈帧顶部 (低地址),当程序执行时 ESP 会随着数据的入栈和出栈而移动。因此函数中对大部分数据的访问都基于 EBP 进行。
1 | class Solution { |
如果用c++的话,如上的方法是最简单的,但是呢时间复杂度不符合进阶要求。
采用双指针方法,因为数组是有序的,头和尾的平方才有可能是最大值,不可能在中间,所以可以设定一个左指针和右指针,再定义一个数组来存放平方值,数组和原数组大小一样,设定一个k指针指向数组最终位置
1 | class Solution { |
1.先查壳,看到是Enigma Virtual Box,是一个虚拟文件打包系统,可以将程序和配套文件打包成一个可执行文件
WinMain
函数里看不出来个啥,用OD试试。先查找所有字符串,看到这里大概是flag,点击去
然后到IDA中找到相应位置,f5反汇编得到如下
这里是函数sub_4012F0()
,可以看到很明显用的是Base58加密,加密后与“56fkoP8KhwCf3v7CEz”字符串进行对比,所以直接找一个网络工具解码得到
flag{12t4tww3r5e77}
1 | int removeElement(int* nums, int numsSize, int val){ |
这样的党法有一点笨拙,复杂度太高(O(n^2)),看了一下题解的双指针方法:通过一个右指针和左指针在一个For循环下完成两个for循环的工作
右指针 right 指向当前将要处理的元素,左指针left 指向下一个将要赋值的位置。
如果右指针指向的元素不等于val,它一定是输出数组的一个元素,我们就将右指针指向的元素复制到左指针位置,然后将左右指针同时右移;
如果右指针指向的元素等于 val,它不能在输出数组里,此时左指针不动,右指针右移一位。
1 | int removeElement(int* nums, int numsSize, int val) { |