Pwnable.kr 解题笔记
1. fd
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char buf[32];
int main(int argc, char* argv[], char* envp[]){
if(argc<2){
printf("pass argv[1] a number\n");
return 0;
}
int fd = atoi( argv[1] ) - 0x1234;
int len = 0;
len = read(fd, buf, 32);
if(!strcmp("LETMEWIN\n", buf)){
printf("good job :)\n");
system("/bin/cat flag");
exit(0);
}
printf("learn about Linux file IO\n");
return 0;
}
writeup:
fd = atoi(input) - 0x1234
len = read(fd, buf, 32)
需要 buf == "LETMEWIN\n"
注意:
fd = 0 时,是stdin,标准输入流。这是可以用户控制
2. collision
#include <stdio.h>
#include <string.h>
unsigned long hashcode = 0x21DD09EC;
unsigned long check_password(const char* p){
int* ip = (int*)p;
int i;
int res=0;
for(i=0; i<5; i++){
res += ip[i];
}
return res;
}
int main(int argc, char* argv[]){
if(argc<2){
printf("usage : %s [passcode]\n", argv[0]);
return 0;
}
if(strlen(argv[1]) != 20){
printf("passcode length should be 20 bytes\n");
return 0;
}
if(hashcode == check_password( argv[1] )){
system("/bin/cat flag");
return 0;
}
else
printf("wrong passcode.\n");
return 0;
}
writeup:
chat* x = input
strlen(x) = 20
int tmp = (int*) x
x += 4
result += x
result = 0x21DD09EC
得出,5个长度为4的数相加值为result
4X + Y = result
3. bof
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void func(int key){
char overflowme[32];
printf("overflow me : ");
gets(overflowme); // smash me!
if(key == 0xcafebabe){
system("/bin/sh");
}
else{
printf("Nah..\n");
}
}
int main(int argc, char* argv[]){
func(0xdeadbeef);
return 0;
}
分析:
传递的参数是0xdeadbeef
,后面检查的是key == 0xcafebabe
, 然后在get(overflowme)
处,有明显的用户可控的溢出点,这里需要的是,使得输入能够覆盖到func到参数处,需要确定的关键信息有:
有了以上两个数据,就能得到key到func函数参数的距离,然后就可以很简单的构造exp了。
overflowme距离ebp是0x2c,func参数距离ebp是0x8,所以overflowme距离func参数距离是0x2c+0x8。
4 flag
逆向题,UPX壳,直接upx -d
可脱,手动脱壳可以简单描述为三个小向上跳转之后有一个远跳,在那边下断点dump内存即可。
脱壳之后的程序调试一下即可看见flag值。
5. passcode
#include <stdio.h>
#include <stdlib.h>
void login(){
int passcode1;
int passcode2;
printf("enter passcode1 : ");
scanf("%d", passcode1);
fflush(stdin);
// ha! mommy told me that 32bit is vulnerable to bruteforcing :)
printf("enter passcode2 : ");
scanf("%d", passcode2);
printf("checking...\n");
if(passcode1==338150 && passcode2==13371337){
printf("Login OK!\n");
system("/bin/cat flag");
}
else{
printf("Login Failed!\n");
exit(0);
}
}
void welcome(){
char name[100];
printf("enter you name : ");
scanf("%100s", name);
printf("Welcome %s!\n", name);
}
int main(){
printf("Toddler's Secure Login System 1.0 beta.\n");
welcome();
login();
// something after login...
printf("Now I can safely trust you that you have credential :)\n");
return 0;
}
5.1 分析
这里详细一点分析。
5.1.1 scanf
首先是scanf()
函数,参考RE4B解释一下。
#include <stdio.h>
int main() {
int x;
printf("Enter X:\n");
scanf("%d", &x);
printf("You entered %d...\n", x);
return 0;
}
man scanf
可以看到如下描述:
The scanf() family of functions scans input according to format as de-scribed below. This format may contain conversion specifications; the results from such conversions,if any, are stored in the locations pointed to by the pointer arguments that follow format.Each pointer argument must be of a type that is appropriate for the value returned by the corresponding conversion specification.
其函数声明如下:
int scanf(const char *format, ...);
在format
之后的参数正常情况下应该是一个指向可写的内存地址,如上例中所示,首先定义了int
类型的变量x
,它存储在栈上,简单调试可以看到存储位置是ebp - 0x10
,在调用scanf
函数时,是使用lea eax, [ebp-0x10];push eax
完成该调用,上述调用可以抽象为:scanf(&[ebp-0x1fb9], &[ebp-0x10])
。
而在本题中有:
int passcode1;
int passcode2;
scanf("%d", passcode1);
scanf("%d", passcode2);
char name[100];
scanf("%100s", name);
期望的用户输入是一个数据,但是实际上的用户输入的是数据的地址,scanf会取到用户的地址,在上例中,将代码修改为scanf("%d", x);
有:
入栈道是x的值而非地址。
Disassemble & GOT
静态分析:
开启了canary和栈不可执行。但是没有PIE和FORTIFY。
main函数没什么可分析的。
welcome函数:name
距离ebp
的偏移为0x70
。
login函数:passcode1
距离ebp
的偏移是0x10
,passcode2
距离ebp
的偏移是0xc
,注意前面说的,passcode
传递的是地址,我们实际上修改的是这个地址上的数据,可以理解为[ebp-0x10] = *passcode1
。如果我们可以控制ebp-0x10
的值,我们就可以做到任意地址写了(首先在ebp-0x10处写入我们想写的地址,然后通过passcode1写入值,passcode2同理)。
动态调试:
welcome函数和login函数具有相同的ebp,同时有如下栈结构:
ebp ------->
|
|
ebp - 0x10 | *passcode1
|
|
...
|
|
ebp - 0x70 | name[100]
name[96]正好在*passcode上,所有在输入name的时候,控制后四位的数据即可控制任意地址写。
那么,怎么做才能执行system("/bin/cat flag")
呢?
首先要注意栈不可执行。然后直接写入代码地址是不可行的,因为此时EIP没有办法跳转到代码地址上。
如果想要控制任意地址写到任意执行,必须想办法写入一个EIP一定会跳入的地方。在本题中PLT,GOT都是可写的,在之前的文章有介绍过,PLT表项执行的是jmp GOT的操作,而GOT表项中写入的是对应函数的实际地址,因此这里写题目中的GOT表项即可。
选择fflush即可,需要注意的是scanf接受的数据是%d,即十进制数据。
整理一下:
0x0804a000。
system("/bin/cat flag")
地址:0x080485e3
,注意要转化为10进制。这里还有其他解法,不过思路是相似的。
6 random
int main(){
unsigned int random;
random = rand(); // random value!
unsigned int key=0;
scanf("%d", &key);
if( (key ^ random) == 0xdeadbeef ){
printf("Good!\n");
system("/bin/cat flag");
return 0;
}
printf("Wrong, maybe you should try 2^32 cases.\n");
return 0;
}
分析:
本题利用的是伪随机数来控制生成random来解题。
rand()
使用的时候是通过srand()
来设置种子,正常的情况下应该在每次使用rand()的时候通过srand
重新设置一次seed,但是在本题中并没有这么做,因此每次生成的时候数都是相同的。
同时A ^ B = C ==> A = B ^ C
.
下断,EAX存储的就是random。
key = random ^ value
看雪ID:安和桥南
https://bbs.kanxue.com/user-home-882195.htm
# 往期推荐
3、Large Bin Attack学习(_int_malloc源码细读 )
4、CVE-2022-2588 Dirty Cred漏洞分析与复现
5、开发常识 | 彻底理清 CreateFile 读写权限与共享模式的关系
球分享
球点赞
球在看
点击阅读原文查看更多