2019西湖论剑杯-re-easyCPP
这题的难点在对C++的STL容器的了解
1 | std::vector<T> values;//创建存放T类型元素的vertor容器。 |
1 | v10 = argv; |
整个流程大概就是先创建几个空的vector容器,然后一个容器存放输入的16个数字,然后一个容器存放斐波那契数列,用transform将除了第一个元素外的每一个元素加上第一个元素的值,然后都赋值给一个新的vector容器,之后用accumulate将函数倒序赋值给V19(这一步我看不懂,accumulate是用来计算累加值的,但是这里只用了v19一个参数就实现了倒序,这里纯粹看堆内数据才看出来是倒序),然后让v16=v19,最后比较V16和V13,v13就是斐波那契数列,如果相等就通过,不相等就不通过。
精简后的过程就是:输入16个数字,后面十五个数字都加上第一个数字,然后整个数列颠倒,最后和斐波那契数列比较。
由此构造需要输入的数组:
987
-377
-610
-754
-843
-898
-932
-953
-966
-974
-979
-982
-984
-985
-986
-986
就是颠倒的斐波那契数列,除第一个元素外,其它元素都要减去第一个元素。最后得到Your flag is:flag{987-377-843-953-979-985}
2019西湖论剑杯-pwn-store
基本格式化任意地址泄露和基本rop
1 | __int64 __fastcall main(__int64 a1, char **a2, char **a3) |
这是main函数,一开始被两个没有置NULL的free迷惑,以为是use after free,赛后经别人提醒才发现是格式化漏洞,当初没好好学,记成了printf里变量没有&才是格式化漏洞,实际只要printf里只有变量就存在格式化漏洞,格式化漏洞的大致原理就是直接printf(&s)的情况下,printf首先会检测变量内是否存在%,如果有就向后检测需要输出哪种类型的变量,比如人为构造s=”%s”,那么printf检测到%s,就会从栈内输出一个字符串类型的变量,相当于printf(“%s”,stack[0]),如果是%x或者%p就能泄露32位和64位系统的栈内值,%n$x中的n$代表偏移n个变量,由此可以实现栈内任意地址泄露。(但是0$的位置我一直没摸清楚,只能通过多泄露几个值对比栈内值得到偏移,但x32系统下一般是调用printf函数时栈内第2个位置)
1 | char *sub_400915() |
因为这一题开了canary,所以需要泄露canary的值,然后在栈溢出的时候用已知的canary值覆盖就能绕过检测了,同一个进程内栈的canary都是相同的。
这里我先通过输入
1 | %0$p%1$p%2$p%3$p%4$p%5$p%6$p%7$p%8$p%9$p%10$p |
确认printf参数在栈内的起始位置
可以看到%7$p
对应的是0x7fffffffe078的0x603260
相应的%15$p
和%23$p
就是canary的值,%25$p
就是start+231的地址。
知道了canary的值和start+231的址,在之后的栈溢出中就是构造payload了。
1 | char *sub_4009A0() |
可以看到s离rbp只有0x90,而v1是可以等于任意大小的,非常简单的栈溢出,我决定返回system,在失败了几次后我才想起来这是x64系统,函数的参数传递不再是用栈,而是用寄存器。
X64参数传递:
当参数少于7个时,参数从左到右放入寄存器: rdi, rsi, rdx, rcx, r8, r9。
当参数为7个以上时, 前 6 个与前面一样, 但后面的依次从 “右向左” 放入栈中,即和32位汇编一样。
1 | mov edi, offset aYouCanSpeakYou ; "You can speak your story:" |
通过阅读结尾的汇编,发现最后会把字符串s存进rax,然后mov rdi,rax。所以我们在输入s时需要先输入”/bin/sh\x00”
最后脚本如下:
1 | from pwn import * |
2019嘉韦思杯final-pwn-Bin
两题pwn只做出来了这一题基本的ret2shellcode,第二题的tcache之前没学过,这次比赛也让我意识到自己总是在重复做一些基本的堆栈溢出,能力已经很久没有提升了,而且密码学自己学了des后就停滞了,这次遇到了简单的AES却没能做出来,决定开始少参加比赛,多潜心学习,而不是继续浪费时间在那些简单题目上了。
这题唯一的难点在要注意到hint
1 | int __cdecl main(int argc, const char **argv, const char **envp) |
程序很简单,溢出buf就好了,而且什么保护都没开,直接shellcode就行了,但是因为不知道栈内地址,所以没办法返回shell code的起始地址,然后看到了之前忽略的放在main函数之前的hint函数
1 | .text:080484ED hint proc near |
然后就明白了,利用这个函数,就可以让程序返回到栈顶(esp)
构造脚本
1 | from pwn import * |
DDCTF-pwn-xpwn
保护就开了NX栈不可执行,那就用ret2syscall
这题唯一比较有意思的是ret前的汇编跟别的题目稍微有点不同
1 | .text:0804873A lea esp, [ebp-8] |
这是main函数的结尾,可以看到先是esp=[ebp-8],接着pop ecx,然后esp=[ecx-4],也相当于ret前栈顶的值变成了[[ebp-8]-4]的值,所以我们要在ebp-0x8的位置填上ropchain_addr+4,这样栈顶会变成ropchain_addr+4的地址,然后被pop给ecx,最后再esp=[ecx-4]=[ropchain_addr+4-4]=[ropchain_addr],而地址ropchain_addr内就存放着exa_popret_addr的地址值,成功执行rop链。
解决了返回值的问题,还有一点就是如何泄露栈内的值
1 | int __cdecl main(int a1) |
看题目,存在栈溢出,sub_80485DB内可以泄露栈内值
1 | int __cdecl sub_80485DB(FILE *stream, FILE *a2) |
这里的read不会在字符串末尾自动加上\x00,所以fprintf输出buf时会把栈内值都一起读出来,遇到\x00才终止,经过几次调试,会发现40个字符后会泄露ebp和setbuf+21的地址值,这样一来就获得了栈地址,也能通过setbuf+21的地址值计算出libc的基址了
最后构造脚本
1 | from pwn import * |
后来觉得rop链太长了,ret2libc也行
1 | from pwn import * |
感冒10天好了之后现在开始潜心修炼安卓和密码学
评论加载中